1

我目前正在使用 EF 扩展。我不明白的一件事,“它应该有助于提高性能”

但是,将一百万多条记录放入 List 变量本身就是一个内存问题。因此,如果想要更新数百万条记录,而不将所有内容都保存在内存中,如何有效地做到这一点?

我们应该使用for loop, 并批量更新说 10,000 吗?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

4

2 回答 2

2

这实际上不是 EF 的用途。EF 的数据库交互从记录对象开始,并从那里流动。如果实体没有被跟踪(因此被加载),EF 不能生成部分更新(即不覆盖所有内容),同样它不能基于条件而不是键删除记录。

对于条件更新/删除逻辑,没有等效的 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 Extensions 并没有真正为您提供所需的东西。简单地在数据库上执行原始 SQL 性能更高,成本更低,因为这绕过了 EF 加载其实体的需要。

更新
我可能会更正,对条件更新逻辑有一些支持,如此处所示。但是,我不清楚该示例是否仍将所有内容加载到内存中,如果您已经将所有内容都加载到内存中,那么条件 WHERE 逻辑的目的是什么(为什么不使用内存中的 LINQ?)

但是,即使这在不加载实体的情况下工作,它仍然是:

  • 更受限制(与允许任何有效 SQL 的布尔条件的 SQL 相比,只允许相等检查),
  • 相对复杂(我不喜欢他们的语法,也许这是主观的)
  • 而且成本更高(仍然是付费图书馆)

与滚动您自己的原始 SQL 查询相比。我仍然建议在这里滚动你自己的原始 SQL,但这只是我的意见。

于 2020-08-17T23:43:10.767 回答
0

我找到了使用类似查询的条件进行批量更新的“正确”EF Extensions 方法:

var productUpdate = _dbContext.Set<Product>()
    .Where(x => x.ProductType == 'Electronics')
    .UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });

根据文档,这应该会产生正确的 SQL UPDATE ... SET ... WHERE,而无需先加载实体:

为什么UpdateFromQuerySaveChangesBulkSaveChanges和快BulkUpdate

UpdateFromQuery直接在 SQL 中执行语句,例如UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key].

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

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

其他注意事项

  • 不幸的是,没有提到批处理操作。

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

  • 小心中的条件Where,如果它不能被EF翻译成SQL,那么它将在客户端完成,这意味着“通常”可怕的往返“加载-内存更改-更新”

于 2020-08-18T00:27:28.727 回答