现在我循环遍历每个实体并调用
ExecuteUpdateAsync
,然后在循环之后我调用 CommitAsync
,但我想知道是否有更好的方法来更新 ~500 条记录?
我看到了这个批量扩展库,但从未使用过它,不知道它是否有帮助?
此外,此代码在云中的 Azure 函数中运行,因此它实际上并不依赖于时间。我不在乎运行时间是 10 秒还是 1 分钟,只要它不会让使用共享数据库的网站的其他用户陷入数据库困境即可。
我通过 Code First 方法使用 EF Core,因此我想留在 EF Core 的范围内。
最后,我的数据不是来自我的数据库,而是来自被调用以获取数据的 Web 服务。
// About 500 transfers, data doesn't come from DB
var transfers = new List<(string, decimal?, long, string, string)>();
using var transaction = await _dbContext.Database.BeginTransactionAsync();
try
{
foreach (var transfer in transfers)
{
var stripeTransfersResult = await _dbContext.StripeTransfers
.Where(p => p.TransferId == transfer.Item1)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.ExchangeRate, transfer.Item2)
.SetProperty(p => p.ExchangeAmount, transfer.Item3)
.SetProperty(p => p.ExchangeCurrency, transfer.Item4)
.SetProperty(p => p.StripePayoutId, transfer.Item5)
);
}
await transaction.CommitAsync(cancellationToken);
}
catch (DbException ex)
{
// handle exception
}
据我所知,事实上,您应该使用
ExecuteUpdateAsync
和 SetProperty
模式进行批量更新,但我相信这实际上意味着批量更新,我们为所有项目设置相同的值包含在批次中。
ExecuteUpdateAsync
& SetProperty
方法生成以下 SQL Query
:
UPDATE [StripeTransfers] SET (ExchangeRate, ExchangeAmount...) = (Value1, Value2...);
就您而言,这不会真正按预期工作。据我所知,通过将
foreach
方法包装在 transaction
中,您应该能够利用这样一个事实:您可以节省第一次往返数据库的开销,存储内存中的对象,并更新 ef change tracker
,但您仍将为每个对象执行 update
语句。尽管如此,这似乎是最优化的解决方案。
您还可以在 Microsoft 文档中找到有关 EF 批处理行为的更多信息此处。
ExecuteUpdate 并不最适合您的需求。 当拥有大数据集(例如,需要将列设置为相同值或以某个数字递增)时,ExecuteUpdate 非常有用。
当需要用特定值更新每一行时,常规方法是将所有这些加载到内存中,进行更改,然后调用 SaveChanges。 示例:
transfers = transfers.OrderBy(a => a.Item1);
itemIds = transfers.Select(a => a.Item1).ToList();
var entities = await _dbContext.StripeTransfers.Where(a => itemIds.Contains(a.TransferId)).OrderBy(a => a.TransferId).ToListAsync();//SQL IN
foreach (int i = 0; i < entities.Count(); i++)
{
entity = entities[i];
transfer = transfers[i];
entity.ExchangeRate = transfer.Item2;
entity.ExchangeAmount = transfer.Item3;
entity.ExchangeCurrency = transfer.Item4;
entity.StripePayoutId = transfer.Item5;
}
await _dbContext.SaveChangesAsync();
还有一种方法可以使用 Attach,例如:
var m = new myModel { PrimaryKey = 1, OtherProperty = 5 };
ctx.Attach(m);
ctx.entry(m).Property(nameof(m.OtherProperty)).IsModified = true;
ctx.SaveChanges();
但是这些可能会有点慢,为了加快操作速度,您可以按以下方式使用 BulkExtensions 库:
var entities = new List<StripeTransfer>();
foreach (var transfer in transfers)
{
var entity = new StripeTransfer();
entity.TransferId= transfer.Item1;
entity.ExchangeRate = transfer.Item2;
entity.ExchangeAmount = transfer.Item3;
entity.ExchangeCurrency = transfer.Item4;
entity.StripePayoutId = transfer.Item5;
entities.Add(entity);
}
var bulkConfig = new BulkConfig {
UpdateByProperties = new List<string> { nameof(Item.TransferId) },
PropertiesToInclude= new List<string> { nameof(Item.ExchangeRate), nameof(Item.ExchangeAmount), nameof(Item.ExchangeCurrency), nameof(Item.StripePayoutId) },
};
await _dbContext.BulkUpdateAsync(entities, bulkConfig);
PS 我是作者。