【问题标题】:How to Bulk Update records in Entity Framework?如何批量更新实体框架中的记录?
【发布时间】:2017-05-26 06:18:19
【问题描述】:

我正在尝试使用实体框架批量更新记录。我试过 Entity Framework.Extensions Update 方法。

Update 方法能够批量更新具有相同更新值集的记录集。

例子:

           Id -  Quantity
Record 1 - A  -  10
Record 2 - B  -  20
Record 3 - C  -  30

我们可以通过简单的调用批量更新以上所有记录

Records.Update(new => Record { Quantity = 100 });

如何使用Entityframework.Extensions或任何其他方法批量更新不同数量的每条记录,从而更快地完成批量更新?

【问题讨论】:

  • 用单独的数据更新每条记录意味着每条记录一个语句。最快的方法是在单个事务中执行所有语句,但这需要一些时间。您现在如何进行更新过程?
  • 目前我们正在使用实体框架,循环遍历每条记录更新数据库实体属性然后保存更改,此过程将针对所有50,000条记录执行,这需要更多时间。
  • 那么问题是更新的值来自哪里?他们来自另一张桌子吗?还是它们是原始值的函数(例如 2x)?每一行可以有不同的值,还是可以使用 EF 扩展更新行组?
  • 在 UI 处理数据并发送到服务进行更新后,记录才会出现,是的,每一行都可以有不同的值。尝试了 EF-Extended,但我能够更新具有相同值的记录组,但不能以批量格式更新具有不同值的每条记录。
  • 如果值来自 UI,那么您的实体是未附加的。所以基本上每条记录都会被选中然后更新,这肯定会大大减慢速度。 @GrégoryBourgin 的答案可能能够在不引入另一个库的情况下解决这个问题。由于每一行都需要不同的值,您将需要单独的更新语句 - 但如果您在修改每个手动附加的记录后调用 SaveChanges,那么您应该获得批量更新并且没有数据往返。

标签: c# entity-framework linq bulkupdate entity-framework-extended


【解决方案1】:

如果您不想使用 SQL 语句,可以使用 Attach 方法来更新实体,而无需先加载它:

using (myDbEntities db = new myDbEntities())
{
    try
    {
      //disable detection of changes to improve performance
      db.Configuration.AutoDetectChangesEnabled = false;

      //for all the entities to update...
      MyObjectEntity entityToUpdate = new MyObjectEntity() {Id=123, Quantity=100};
      db.MyObjectEntity.Attach(entityToUpdate);

      //then perform the update
      db.SaveChanges();
    }
    finally
    {
      //re-enable detection of changes
      db.Configuration.AutoDetectChangesEnabled = true;
    }
}

【讨论】:

  • 附加实体不足以触发更新,因为它默认附加为未更改。在拨打Attach 之后,您需要拨打db.Entry(entityToUpdate).Property(e => e.Quantity).IsModified = true。在 EF Core 中,您可以省略 Attach,因为对 Entry 的调用将附加它。
  • 嗨@DavidBrowne-Microsoft,你能回答这个问题吗?使用 Join 和 where 进行批量更新,stackoverflow.com/questions/63478557/…
  • 还有@DavidBrowne-Microsoft,我把你重要的 cmets 放到了答案中
  • 那么对 EF Core 使用这个 .Attach() 或 .Entry() 来处理多条记录并执行 SaveChanges() 会导致对数据库的一个更新命令?
  • @ΕГИІИО 我猜这是不正确的,它仍然对每个实体进行声明。不过,对于知道给予确认的人来说会很好。
【解决方案2】:

使用ExecuteSqlCommand:

using (yourDbEntities db = new yourDbEntities())
{
    db.Database.ExecuteSqlCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
}

ExecuteStoreCommand:

yourDbContext.ExecuteStoreCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);

【讨论】:

  • 这是最好的答案。它不是最高票数的唯一原因是,对于将其视为“黑客”的真正信徒来说,它“感觉”不正确。英孚应该让生活更轻松;当它没有然后放弃它并做有效的事情。
【解决方案3】:

如果您只想修改几个属性,请使用这种方式:

foreach (var vSelectedDok in doks)
{
    //disable detection of changes to improve performance
    vDal.Configuration.AutoDetectChangesEnabled = false;
    
    vDal.Dokumente.Attach(vSelectedDok);

    vDal.Entry(vSelectedDok).Property(x=>x.Status).IsModified=true;
    vDal.Entry(vSelectedDok).Property(x => x.LastDateChanged).IsModified = true;
}
vDal.SaveChanges();

【讨论】:

    【解决方案4】:

    a) EFCore.BulkExtensions - BatchUpdateAsync

    _dbContext.Set<MyObjectEntity>().BatchUpdateAsync( x => new MyObjectEntity{ Id=123, Quantity=100 });
    

    https://github.com/borisdj/EFCore.BulkExtensions

    "EntityFrameworkCore 扩展:批量操作(插入、更新、删除、读取、更新插入、同步)和批处理(删除、更新)。 库是轻量级且非常高效的,具有所有主要使用的 CRUD 操作。 入选 Microsoft 推荐的 20 大 EF Core Extensions。”

    b) 或 EF 扩展 - UpdateFromQuery

    _dbContext.Set<MyObjectEntity>().UpdateFromQuery( x => new MyObjectEntity{ Id=123, Quantity=100 });
    

    资源:

    https://entityframework-extensions.net/update-from-query

    https://stackoverflow.com/a/63460251/12425844

    为什么 UpdateFromQuery 比 SaveChanges、BulkSaveChanges 和 BulkUpdate 更快?

    UpdateFromQuery 直接在 SQL 中执行一条语句,例如 作为 UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key]。

    其他操作通常需要一次或多次数据库往返 这会使性能变慢。

    【讨论】:

      【解决方案5】:

      我发现一个easy way to do that 没有任何第三方包:
      通过添加一个通用扩展方法SetValue,您可以简单地编写:

      示例:

      void Main()
      {
          
          var dc = this; // context
          var p = dc.Purchases.Where(x=>x.Description.ToLower()=="bike")
                              .SetValue(w => w.Description = "Bicycle");
          p.Dump();
          dc.SubmitChanges();
      }
      

      如您所见,任何符合Where 条件的值都可以显式设置为新值,因此这里的Bike 将被Bicycle 替换。您可以在之后查询该表以查看真正持久的更改。

      当然,您也可以省略Where 语句,如果您想更改所有记录,例如:

      dc.Records.SetValue(x => x.Quantity = 100);
      dc.SubmitChanges();
      

      实体框架 (EF) / LINQ 会跟踪这些更改,当您调用 .SubmitChanges() 时 - 如果您使用的是 LinqPad,您可以在 SQL 选项卡中看到 - 它将创建 SQL 代码,如下所示:

      -- Region Parameters
      DECLARE @p0 Int = 3
      DECLARE @p1 VarChar(1000) = 'Bicycle'
      -- EndRegion
      UPDATE [Purchase]
      SET [Description] = @p1
      WHERE [ID] = @p0
      

      对于小的更改,这没问题,但对于大型表,它变得低效,因为它使用 ID 列来识别和更改记录,而不是 .SetValue 定义的 Description 列。

      理论上 EF 可以对此进行优化,但正如您所见,它并没有这样做。 因此,如果您想要真正的批量操作,则需要运行 SQL 命令或创建您通过 EF 调用的存储过程(用于复杂查询)。


      扩展方法SetValue

      这种扩展方法可以解决问题(不需要其他 3rd 方包):

      // see: https://visualstudiomagazine.com/articles/2019/07/01/updating-linq.aspx
      public static class Extensions
      {
          public static IEnumerable<T> SetValue<T>(this IEnumerable<T> items, 
                                                        Action<T> updateMethod)
          {
              foreach (T item in items)
              {
                  updateMethod(item);
              }
              return items;
          }
      }
      

      注意: 上面的示例使用 Nutshell 示例数据库,您可以按照 this link 轻松创建,代码是为 LinqPad 6 编写的,但可以轻松调整(LinqPad 6 使用 .NET Core,但您可以使用 LinqPad 5 以及 .NET Framework 进行尝试)。

      【讨论】:

      • 这是批量更新,不是批量更新,对吧?这意味着它会加载每条记录,对其进行更改,然后保存?
      • @stannius - 严格来说,是的。在许多情况下,这会很好地完成它的工作。但是,如果您有大量的表要更新(如 here 所述),请考虑在 SQL 中创建一个存储过程并从实体框架中调用它 - 或者,如果它只是一个 UPDATE 语句,请执行 SQL 命令而是 (ExecuteSqlCommand)。
      【解决方案6】:

      在 EF 6 中,我们在每个表中都有 AddRange 方法。文件表明这种方法比使用许多添加方法要快得多。因此,可以在一个临时表中插入所有可更新的记录,并使用一条 sql 语句批量更新主表。

      编辑:Document 建议 AddRange 仅优化更改检测。它不会更改将更改应用于数据库的方式。

      【讨论】:

      【解决方案7】:

      EFCore 中将有内置的 BulkUpdate()BulkDelete 方法,这些方法将在 EFCore 7.0 中提供

      context.Customers.Where(...).BulkDelete();
      context.Customers.Where(...).BulkUpdate(c => new Customer { Age = c.Age + 1 });
      context.Customers.Where(...).BulkUpdate(c => new { Age = c.Age + 1 });
      

      【讨论】:

        【解决方案8】:

        批量更新可以通过简单的 EF 分三步完成,而不是单独的扩展方法:-

        • 首先加载所有实体。
        • Foreach 每个实体并更改其字段值。
        • 在 Foreach 保存后,上下文更改一次。

        这将在一个批次中发送多个更新查询。

        【讨论】:

        • 您描述的是批量更新,而不是批量更新,这对于 50k 行来说很慢。
        • 为将来阅读此内容的任何人提供后代 - 这会将数据库中的每条记录选择到内存中,然后在 C# 中单独对每条记录进行操作,然后将所有记录作为连续的update 调用返回一个批处理 SQL 命令。编辑:对于这个问题,这可能是您可以使用实体框架执行此操作的唯一方法,但考虑到每条记录都必须往返而不是仅仅更新,因此有比在此处使用 EF 更好的解决方案
        猜你喜欢
        • 1970-01-01
        • 2018-12-03
        • 2021-09-21
        • 2012-07-11
        • 1970-01-01
        • 1970-01-01
        • 2022-01-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多