C#实体框架:大容量扩展输入内存问题



我目前正在使用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,而不需要首先加载实体,根据文档:

为什么UpdateFromQuerySaveChangesBulkSaveChangesBulkUpdate快?

CCD_ 10直接在SQL中执行语句,例如CCD_。

其他操作通常需要一个或多个数据库往返,这会降低性能。

您可以检查这个dotnet fiddle示例的工作语法,该示例改编自他们的BulkUpdate示例。

其他注意事项

  • 遗憾的是,没有提及对此进行的批处理操作。

  • 在进行这样的大更新之前,可能值得考虑停用此列上的索引,然后重新生成它们。如果你有很多,这是特别有用的。

  • 小心Where中的条件,如果EF不能将其转换为SQL,那么它将在客户端完成,这意味着";通常的";糟糕的往返;加载-内存更改-更新";

最新更新