我目前正在使用EF扩展。有一件事我不明白;它应该有助于性能";
然而,将一百万条以上的记录放入List变量本身就是内存问题。那么,如果想要更新数百万条记录,而不将所有内容都保存在内存中,如何有效地做到这一点呢?
我们是否应该使用for loop
,并批量更新,比如10000?EFExtensions BulkUpdate是否具有任何本机功能来支持此功能?
示例:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics'); // this creates IQueryable
await productUpdate.ForEachAsync(c => c.ProductBrand = 'ABC Company');
_dbContext.BulkUpdateAsync(productUpdate.ToList());
资源:
https://entityframework-extensions.net/bulk-update
这实际上是EF不具备的功能。EF的数据库交互从记录对象开始,并从那里开始。如果实体没有被更改跟踪(因此被加载(,EF就不能生成部分UPDATE(即不覆盖所有内容(,同样,它也不能基于条件而不是键来删除记录。
对于条件更新/删除逻辑(如(,没有等效的EF(不加载所有这些记录(
UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'
或
DELETE FROM People
WHERE FirstName = 'Robert'
使用EF方法这样做需要加载所有这些实体,只需将它们发送回数据库(带有更新或删除(,这是对带宽和性能的浪费,正如您已经发现的那样。
我在这里找到的最好的解决方案是绕过EF的LINQ友好方法,而是自己执行原始SQL。这仍然可以使用EF上下文来完成。
using (var ctx = new MyContext())
{
string updateCommand = "UPDATE People SET FirstName = 'Bob' WHERE FirstName = 'Robert'";
int noOfRowsUpdated = ctx.Database.ExecuteSqlCommand(updateCommand);
string deleteCommand = "DELETE FROM People WHERE FirstName = 'Robert'";
int noOfRowsDeleted = ctx.Database.ExecuteSqlCommand(deleteCommand);
}
点击此处了解更多信息。当然,不要忘记在相关的地方防止SQL注入。
运行原始SQL的具体语法可能因EF/EF Core的版本而异,但据我所知,所有版本都允许您执行原始SQL。
我不能具体评论EF Extensions或BulkUpdate的性能,我也不会从他们那里购买。
根据他们的文档,他们似乎没有具有正确签名的方法来允许条件更新/删除逻辑。
BulkUpdate
似乎不允许您输入逻辑条件(UPDATE命令中的WHERE(来优化它BulkDelete
仍然有一个BatchSize
设置,这表明他们仍然在一次处理一条记录(好吧,我想是每批(,而不是使用带有条件的单个DELETE查询(WHERE子句(
根据问题中的预期代码,EF扩展并没有真正满足您的需求。简单地在数据库上执行原始SQL更具性能,也更便宜,因为这绕过了EF加载实体的需要。
更新
我可能会得到纠正,对条件更新逻辑有一些支持,如下所示。然而,我不清楚这个例子是否仍然在内存中加载所有内容,以及如果你已经在内存中全部加载了条件WHERE逻辑的目的是什么(为什么不在内存中使用LINQ呢?(
然而,即使这在不加载实体的情况下有效,它仍然是:
- 更受限制(与SQL允许任何有效的布尔条件相比,只允许相等检查(
- 相对复杂(我不喜欢它们的语法,也许这是主观的(
- 而且成本更高(仍然是付费图书馆(
与滚动自己的原始SQL查询相比。我仍然建议在这里滚动您自己的原始SQL,但这只是我的意见。
我发现了;适当的";EF扩展使用类似查询的条件进行批量更新的方法:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics')
.UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });
这应该会产生一个正确的SQLUPDATE ... SET ... WHERE
,而不需要首先加载实体,根据文档:
为什么
UpdateFromQuery
比SaveChanges
、BulkSaveChanges
和BulkUpdate
快?CCD_ 10直接在SQL中执行语句,例如CCD_。
其他操作通常需要一个或多个数据库往返,这会降低性能。
您可以检查这个dotnet fiddle示例的工作语法,该示例改编自他们的BulkUpdate
示例。
其他注意事项
遗憾的是,没有提及对此进行的批处理操作。
在进行这样的大更新之前,可能值得考虑停用此列上的索引,然后重新生成它们。如果你有很多,这是特别有用的。
小心
Where
中的条件,如果EF不能将其转换为SQL,那么它将在客户端完成,这意味着";通常的";糟糕的往返;加载-内存更改-更新";