C# Entity Framework: 批量扩展输入内存问题
C# Entity Framework: Bulk Extensions Input Memory Issue
我目前正在使用 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());
资源:
这实际上不是 EF 的目标。 EF 的数据库交互从记录对象开始,并从那里开始流动。如果实体没有更改跟踪(因此加载),EF 不能生成部分更新(即不覆盖所有内容),同样它不能基于条件而不是键删除记录。
条件 update/delete 逻辑(例如
)没有等效的 EF(不加载所有这些记录)
UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'
或
DELETE FROM People
WHERE FirstName = 'Robert'
使用 EF 方法执行此操作将要求您加载所有这些实体,只是为了将它们发送回(通过更新或删除)到数据库,正如您已经发现的那样,这是对带宽和性能的浪费.
我在这里找到的最佳解决方案是绕过 EF 的 LINQ-friendly 方法,而是自己执行原始 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);
}
更多信息here。当然 别忘了在相关的地方防止 SQL 注入 。
运行 raw SQL 的具体语法可能因 EF/EF 核心版本而异,但据我所知,所有版本都允许您执行 raw SQL.
我无法具体评论 EF Extensions 或 BulkUpdate 的性能,我也不会从他们那里购买。
根据他们的文档,他们似乎没有带有正确签名的方法来允许条件 update/delete 逻辑。
BulkUpdate
似乎不允许您输入允许您优化它的逻辑条件(UPDATE 命令中的 WHERE)。
BulkDelete
仍然有一个 BatchSize
设置,这表明他们仍然一次处理一个记录(好吧,我猜是每批),而不是使用单个 DELETE 查询条件(WHERE 子句)。
根据问题中的预期代码,EF Extensions 并不能真正满足您的需求。在数据库上简单地执行原始 SQL 性能更高且成本更低,因为这绕过了 EF 加载其实体的需要。
更新
我可能会纠正,一些 支持条件更新逻辑,如 here 所示。但是,我不清楚示例仍然将所有内容加载到内存中,如果您已经将所有内容加载到内存中,那么条件 WHERE 逻辑的目的是什么(为什么不使用 in-memory LINQ 呢?)
然而,即使这在不加载实体的情况下工作,它仍然是:
- 更多限制(只允许相等检查,相比之下 SQL 允许任何有效的布尔条件 SQL),
- 相对复杂(我不喜欢他们的语法,也许这是主观的)
- 而且更贵(仍然是付费图书馆)
与滚动您自己的原始 SQL 查询相比。我仍然建议在这里滚动你自己的原始 SQL,但这只是我的意见。
我找到了使用 query-like 条件进行批量更新的“正确”EF 扩展方法:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics')
.UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });
这应该会导致正确的 SQL UPDATE ... SET ... WHERE
,而不需要首先加载实体,根据 the documentation:
Why UpdateFromQuery
is faster than SaveChanges
, BulkSaveChanges
, and BulkUpdate
?
UpdateFromQuery
executes a statement directly in SQL such as UPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key]
.
Other operations normally require one or multiple database round-trips which makes the performance slower.
您可以检查此 dotnet fiddle example 上的工作语法,改编自 BulkUpdate
.
的示例
其他注意事项
不幸的是,没有提到批处理操作。
在进行这样的大更新之前,可能值得考虑停用您可能在此列上拥有的索引,然后再重建它们。如果您有很多,这将特别有用。
注意Where
里面的条件,如果不能被EF翻译成SQL,那么就在client side做,意思是“通常“可怕的往返”加载 - 内存变化 - 更新
我目前正在使用 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());
资源:
这实际上不是 EF 的目标。 EF 的数据库交互从记录对象开始,并从那里开始流动。如果实体没有更改跟踪(因此加载),EF 不能生成部分更新(即不覆盖所有内容),同样它不能基于条件而不是键删除记录。
条件 update/delete 逻辑(例如
)没有等效的 EF(不加载所有这些记录)UPDATE People
SET FirstName = 'Bob'
WHERE FirstName = 'Robert'
或
DELETE FROM People
WHERE FirstName = 'Robert'
使用 EF 方法执行此操作将要求您加载所有这些实体,只是为了将它们发送回(通过更新或删除)到数据库,正如您已经发现的那样,这是对带宽和性能的浪费.
我在这里找到的最佳解决方案是绕过 EF 的 LINQ-friendly 方法,而是自己执行原始 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);
}
更多信息here。当然 别忘了在相关的地方防止 SQL 注入 。
运行 raw SQL 的具体语法可能因 EF/EF 核心版本而异,但据我所知,所有版本都允许您执行 raw SQL.
我无法具体评论 EF Extensions 或 BulkUpdate 的性能,我也不会从他们那里购买。
根据他们的文档,他们似乎没有带有正确签名的方法来允许条件 update/delete 逻辑。
BulkUpdate
似乎不允许您输入允许您优化它的逻辑条件(UPDATE 命令中的 WHERE)。BulkDelete
仍然有一个BatchSize
设置,这表明他们仍然一次处理一个记录(好吧,我猜是每批),而不是使用单个 DELETE 查询条件(WHERE 子句)。
根据问题中的预期代码,EF Extensions 并不能真正满足您的需求。在数据库上简单地执行原始 SQL 性能更高且成本更低,因为这绕过了 EF 加载其实体的需要。
更新
我可能会纠正,一些 支持条件更新逻辑,如 here 所示。但是,我不清楚示例仍然将所有内容加载到内存中,如果您已经将所有内容加载到内存中,那么条件 WHERE 逻辑的目的是什么(为什么不使用 in-memory LINQ 呢?)
然而,即使这在不加载实体的情况下工作,它仍然是:
- 更多限制(只允许相等检查,相比之下 SQL 允许任何有效的布尔条件 SQL),
- 相对复杂(我不喜欢他们的语法,也许这是主观的)
- 而且更贵(仍然是付费图书馆)
与滚动您自己的原始 SQL 查询相比。我仍然建议在这里滚动你自己的原始 SQL,但这只是我的意见。
我找到了使用 query-like 条件进行批量更新的“正确”EF 扩展方法:
var productUpdate = _dbContext.Set<Product>()
.Where(x => x.ProductType == 'Electronics')
.UpdateFromQuery( x => new Product { ProductBrand = "ABC Company" });
这应该会导致正确的 SQL UPDATE ... SET ... WHERE
,而不需要首先加载实体,根据 the documentation:
Why
UpdateFromQuery
is faster thanSaveChanges
,BulkSaveChanges
, andBulkUpdate
?
UpdateFromQuery
executes a statement directly in SQL such asUPDATE [TableName] SET [SetColumnsAndValues] WHERE [Key]
.Other operations normally require one or multiple database round-trips which makes the performance slower.
您可以检查此 dotnet fiddle example 上的工作语法,改编自 BulkUpdate
.
其他注意事项
不幸的是,没有提到批处理操作。
在进行这样的大更新之前,可能值得考虑停用您可能在此列上拥有的索引,然后再重建它们。如果您有很多,这将特别有用。
注意
Where
里面的条件,如果不能被EF翻译成SQL,那么就在client side做,意思是“通常“可怕的往返”加载 - 内存变化 - 更新