【问题标题】:Entity Framework RemoveRange is very slow实体框架 RemoveRange 非常慢
【发布时间】:2023-02-26 04:58:16
【问题描述】:

我正在从我的表中删除数百行。使用 ADO.Net 代码即 Delete from table where somecolumn = somestring 使用 Entity Framwork 即花费不到一秒的时间

MyDbContext context = new MyDbContext()    
context.SomeEntity.RemoveRange(context.SomeEntity.Where(i => i.somecolumn == somestring));
context.SaveChanges();

需要 8-10 秒。

谁能解释一下,或者我做错了什么。

【问题讨论】:

  • 分析您的数据库以查看实际发生的情况。通话速度慢吗?您正在检索大量数据吗?数据真的很广泛吗?请记住,您在这里有 2 笔交易。一种用于检索要删除的完整数据,另一种用于删除作为单独删除语句的数据(每个实体 1 个)。
  • 执行它的候选人,恕我直言;下载大量数据只是为了执行删除是相当次优的
  • 我经常使用的一个技巧是只检索我要删除的数据的 PK 值,然后手动创建要删除的实体的实例,在 DbContext 中跟踪它们,然后删除它们。如果一条记录可以包含大量数据并且您需要检索其中的许多记录只是为了删除,这会很方便。请务必在交易中禁用实体验证。除此之外,通过 sql 语句进行手动删除也很有效。

标签: c# entity-framework


【解决方案1】:

EF 是围绕提供映射到对象模型的关系数据而设计的。它不太适合大批量操作。尽管如此,您仍可以通过多种方式满足此类一次性要求。

第一种方法是使用存根进行删除。为此,您需要确保 DbContext 实例对于任何可能被删除的跟踪实例都是“干净的”,因此理想情况下 DbContext 的范围仅限于该方法。

using (var context = new SomeDbContext())
{
    var stubs = context.SomeEntities
        .Where(x => x.SomeColumn == someString)
        .Select(x => x.Id)
        .ToList()
        .Select(x => new SomeEntity { Id == x })
        .ToList();
}

现在您可以将其简化为:

var stubs = context.SomeEntities
    .Where(x => x.SomeColumn == someString)
    .Select(x => new SomeEntity { Id == x.Id })
    .ToList();

但是,您可能想要测试它以确保生成的 SQL 只是选择 ID(而不是整个实体)并且 context.SomeEntities.Local.Any() 仍然是错误的...第一个示例将仅确保查询加载 ID,然后继续使用该 ID 构建存根实体。这使得我们的数据“选择”尽可能高效。

从这里您应该能够在未跟踪的存根上使用RemoveRange

context.SomEntities.RemoveRamge(stubs);
context.SaveChanges();

重要的细节是 DbContext 不能跟踪任何这些实体,因为这会将这些存根临时附加到 DbContext。如果上下文已经在跟踪具有这些 ID 之一的实例,那么您将收到一个错误,指出一个或多个具有相同 ID 的实体已被跟踪。 (因此本地范围的 DbContext 可以避免这种情况)

执行此删除的另一种方法是发出直接的 SQL 操作。如果您的 DbContext 范围限定为请求或比此单个操作更长,那么应该这样做处理任何当前跟踪的实例。

第 1 步。如果您有注入的 DbContext,则处理任何被跟踪的实例:

var trackedInstances = context.SomeEntities.Local
     .Where(x => x.SomeColumn == someString);
     .ToList();

if (trackedInstances.Any())
    context.SomeInstances.RemoveRange(trackedInstances);

这将在不访问数据库的情况下检查 DbContext 中是否有任何被跟踪的实例。我们将要删除这些实例,以避免在 SaveChanges 调用期间可能将其中任何一个标记为已修改并触发异常。

第 2 步。构建并运行参数化原始 SQL 语句以清除数据库中所有剩余的行。

context.Database.ExecuteSqlCommand(@"DELETE FROM dbo.SomeEntities
        WHERE SomeColumn = @someString", new SqlParameter("someString", someString));
context.SaveChanges();

这里的重要细节是使用参数化查询。做不是使用字符串中嵌入的参数执行原始 SQL,因为这为 SQL 注入攻击敞开了大门。

IE。不要使用类似的东西:

context.Database.ExecuteSqlCommand($"DELETE FROM dbo.SomeEntities 
        WHERE SomeColumn = '{someString}'");
// or
context.Database.ExecuteSqlCommand("DELETE FROM dbo.SomeEntities 
        WHERE SomeColumn = '" + someString + "'");

【讨论】:

    【解决方案2】:

    最后,EF Core 7 引入了 ExecuteUpdate 和 Execute Delete 来增加更新和删除操作,目前使用 EntityFramework 进行批量更新和批量删除是最好的选择。

      MyDbContext context = new MyDbContext(); 
        var affectedRows = await context.SomeEntity
                           .Where(i => i.somecolumn == somestring).ExecuteDeleteAsync();
    

    https://medium.com/@mahsan-ghasemi-dev/bulk-update-and-bulk-delete-using-executeupdate-and-executedelete-in-ef-core7-8751e94e6953

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-04
      • 2023-03-16
      • 2023-03-19
      • 1970-01-01
      • 2013-01-30
      • 1970-01-01
      • 2015-08-06
      • 1970-01-01
      相关资源
      最近更新 更多