首页 > 解决方案 > 实体框架:如何提高批量更新性能?

问题描述

我有一些代码可以进行一些计算,并基于它用新值更新一个表中的列。一开始很快,但随着时间的推移,它需要的时间越来越长(性能似乎随着时间的推移呈指数级下降)

有没有办法提高性能?通过手动指定需要更新或类似的内容?

(到目前为止,我解决这个问题的最好方法是创建一个作为批量更新工作的存储过程,但我想知道在实体框架中是否有这样做的本机方法)

我的代码是这样的:

public void UpdateValues()
{
    var itemsPerBag = _dbContext.Items
                                .Where(i => i.needsToBeRecalculated)
                                .GroupBy(i => BagId)

    foreach (bag in itemsPerBag)
    {
        CalculateValue(bag); 
    }

    _dbContext.SaveChanges()
}

public void CalculateValue(IEnumerable<Item> bag)
{
    foreach (item in bag)
    {
        item.calculatedValue = CalculateValue(Item);
    }
}

这不是字面上的意思,但我正在为每个“组”进行更新,而不是一一进行,以尽量不让提交太大或太小。

我有大约 850 个“包”/保存和 25000 件物品。完成 11000 次更新需要 1 分钟,完成 25000 次更新需要 4 分钟。

我认为这是一个相当少量的数据,应该更快地完成,我正在做的计算非常简单。

编辑:

我设法将性能从 4 分钟提高到 20 秒的唯一方法是在数据库中创建一个存储过程来更新数据,并调用它而不是SaveChanges().

private async Task UpdatePlanItems(IEnumerable<Item> items)
{
   SqlParameter param = new SqlParameter();
   param.ParameterName = "@Items";
   param.SqlDbType = SqlDbType.Structured;
   param.Value = GetItemsTable(items);
   param.TypeName = "dbo.ItemUpdateType";

   await _databaseStatement.ExecuteAsync("EXEC dbo.usp_UpdateItemValue {0}", param);
 }

 private DataTable GetItemsTable(IEnumerable<Item> items)
 {
    var table = new DataTable();
    table.Columns.Add("ItemId", typeof(int));
    table.Columns.Add("Value", typeof(int));

    foreach (var item in items)
    {
       var row = table.NewRow();
       row["ItemId"] = item.ItemId;
       row["Value"] = item.Value;
       table.Rows.Add(row);
     }

     return table;
  }

在数据库上我必须运行这个:

CREATE TYPE [dbo].[ItemUpdateType] AS TABLE(
              [ItemId] [int] NULL,
              [Value] [int] NULL
)
GO

CREATE PROCEDURE [dbo].[usp_UpdateItemValue]
    (@PlanItems [dbo].ItemUpdateType READONLY) 
AS
BEGIN
    UPDATE p
    SET i.Value = s.Value
    FROM [dbo].[Item] i
    INNER JOIN @PlanItems s ON s.PlanItemId = i.PlanItemId
END

标签: c#entity-framework.net-core

解决方案


您不应该在每次更改后保存更改,而是在完成所有更改(或至少以 100/1000/... 的批次)后保存更改,因此您的代码应如下所示。否则,您将进行 n db 调用(每个项目 1 个)而不是仅 1 个(对于所有项目)

public void UpdateValues(){
  var itemsPerBag = _dbContext.Items.Where(i => i.needsToBeRecalculated)
                                 .GroupBy(i => BagId)

  foreach (bag in itemsPerBag){
    CalculateValue(bag);    
  }

  _dbContext.SaveChanges()
}

在没有提交到数据库的情况下进行太多更改(不分批进行)可能仍然很慢,特别是如果您有很多处于修改状态的数据项并且您可能希望禁用自动更改检测,因为它总是检查每个修改的项目而不是执行最后手动更改检测。如果您共享 DbContext 实例(您不应该这样做),您可能还需要重新启用自动更改检测

// Turn off automatic change detection
_dbContext.Configuration.AutoDetectChangesEnabled = false;

// All your operations (calculation/updating/adding items/...)
AllYourUpdatesToItems();

// Manually call detect changes so EF's SaveChanges() actually commits something
_dbContext.ChangeTracker.DetectChanges();
_dbContext.SaveChanges();

推荐阅读