【问题标题】:Entity Framework navigation collections performance issue [duplicate]实体框架导航集合性能问题[重复]
【发布时间】:2019-11-12 23:42:09
【问题描述】:

我在实体框架中有一个多对多的关系,在映射中,EF 允许我们指定键和表名。在下面的示例中,一个用户可以拥有多个设备日志,而一个设备日志可以属于多个用户。

HasMany(e => e.DeviceLogs)
            .WithMany(e => e.Users)
            .Map(m => m.ToTable("UserDeviceLog")
                .MapLeftKey("UserId")
                .MapRightKey("LogId")
            );

现在,当我尝试从用户中删除设备日志时:

using (var context = new MyDbContext())
        {
            var user = context.Users.Find(userId);
            var deviceLog = context.DeviceLogs.Find(logId);

            user.DeviceLogs.Remove(deviceLog);
            context.SaveChanges();
        }

我知道我要从表“UserDeviceLog”中删除一个关系,而不是设备日志本身。 上面的代码 sn-p 有效,但问题是 Entity Framework 将加载所有设备日志,然后从表 UserDeviceLog 中删除一条记录。

执行上述逻辑时,我的应用程序被阻止。从截图中可以看到,两条 SQL 语句之间大约有 23 秒。对于指定的用户,它有大约 20K 条记录(第一条 SQL 语句)。我猜它是通过将这 20K 记录从 SQL Server 传输到我的应用程序而采取的,这导致我的应用程序非常慢。

虽然这是不必要的,但我该如何避免呢?我不想写一个普通的SQL删除语句,我更喜欢EF方式的解决方案。

谢谢!

【问题讨论】:

  • 使用标记为“重复”的帖子接受的答案中的ChangeRelationshipState 助手。例如如果您将其设为公共扩展方法,而不是user.DeviceLogs.Remove(deviceLog);,您将使用context.ChangeRelationshipState(user, deviceLog, e => e.DeviceLogs, EntityState.Deleted);(或等效的context.ChangeRelationshipState(deviceLog, user, e => e.Users, EntityState.Deleted);),这将删除链接而不会延迟加载集合。
  • @Ivan Stoev,谢谢!这个对我有用。顺便说一句,要删除所有链接而不延迟加载集合吗?
  • 最低要求是从 db 中获取带有 id 的列表,然后禁用延迟加载并使用状态管理器和伪造的“存根”实体实例进行一些技巧。换句话说,可能,但不是自然和棘手的。

标签: entity-framework navigation-properties


【解决方案1】:

这会发生,因为通过访问 user.DeviceLogs 您将延迟加载该集合。

如果在 DbContext 中无法将 UserDeviceLogs 作为 DbSet 访问(它通常不会作为多对多连接表/实体)...

你应该得到什么:

using (var context = new MyDbContext())
{
    var userDeviceLog = context.Users.Where(x => x.UserId == userId)
       .SelectMany(x => x.DeviceLogs.Where(l => l.LogId ==logId))
       .SingleOrDefault();

    if(userDeviceLog == null)
        return;

    context.Entry(userDeviceLog).State = EntityState.Deleted;
    context.SaveChanges();
}

如果 DeviceLog 引用任何也必须删除的引用,您也需要处理该删除。但这应该在不加载所有链接的情况下取消用户与设备日志的链接。

【讨论】:

  • 但是这段代码会删除另一个实体(DeviceLog对象),而不是链接。
  • 啊,我的错..您的映射没有 UserDeviceLog 实体,用户有一个映射到表的 DeviceLog 集合。上述内容适用于映射的 UserDeviceLog 实体,但这会引发令人讨厌的更改,您需要通过 UserDeviceLog.DeviceLog 选择获取 DeviceLog 的位置。如果没有那个加入实体,我能想到的唯一方法是通过ExecuteSqlCommand 针对上下文发出 SQL 删除。不漂亮,但是……
  • 我用来从集合中删除项目的方法可以在每个实体框架教程中找到。我不敢相信这不是最佳实践,但会导致很大的性能问题。而不是使用 ExecuteSqlCommand,我宁愿显式声明连接表(UserDeviceLog)并从 DbSet 中删除关系。
  • 如果您声明 UserDeviceLog 实体,您可能需要更改您的 User 和 DeviceLog 实体才能使用它,因为如果您有一个映射实体和同一个表的连接声明,EF 可能会抛出一个 wobbler。如果您映射该实体并将其与 User 和 DeviceLog 关系一起使用,那么我上面的示例将可以工作,而无需声明 UserDeviceLog 的附加 DbSet。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-10
  • 2011-11-16
相关资源
最近更新 更多