如何批量更新Entity Framework中的记录?
How to Bulk Update records in Entity Framework?
我正在尝试使用 Entity Framework 批量更新记录。我试过 Entity Framework.Extensions Update
方法。
Update
方法能够批量更新一组具有相同更新值的记录。
示例:
Id - Quantity
Record 1 - A - 10
Record 2 - B - 20
Record 3 - C - 30
我们可以通过简单调用
批量更新以上所有记录
Records.Update(new => Record { Quantity = 100 });
如何使用 Entityframework.Extensions
或任何其他方法批量更新不同数量的每条记录,从而更快地完成批量更新?
using (yourDbEntities db = new yourDbEntities())
{
db.Database.ExecuteSqlCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
}
yourDbContext.ExecuteStoreCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
批量更新可以通过简单的 EF 而不是单独的扩展方法分三步完成:-
- 首先加载所有实体。
- 对每个实体进行 Foreach 并更改其字段值。
- 在 Foreach 保存上下文更改一次后。
这将在单个批次中发送多个更新查询。
如果您不想使用 SQL 语句,您可以使用 Attach 方法来更新实体而无需先加载它:
using (myDbEntities db = new myDbEntities())
{
try
{
//disable detection of changes to improve performance
db.Configuration.AutoDetectChangesEnabled = false;
//for all the entities to update...
MyObjectEntity entityToUpdate = new MyObjectEntity() {Id=123, Quantity=100};
db.MyObjectEntity.Attach(entityToUpdate);
//then perform the update
db.SaveChanges();
}
finally
{
//re-enable detection of changes
db.Configuration.AutoDetectChangesEnabled = true;
}
}
如果您只想修改几个属性,请使用这种方式:
foreach (var vSelectedDok in doks)
{
//disable detection of changes to improve performance
vDal.Configuration.AutoDetectChangesEnabled = false;
vDal.Dokumente.Attach(vSelectedDok);
vDal.Entry(vSelectedDok).Property(x=>x.Status).IsModified=true;
vDal.Entry(vSelectedDok).Property(x => x.LastDateChanged).IsModified = true;
}
vDal.SaveChanges();
在 EF 6 中,我们在每个 table 中都有 AddRange 方法。文档表明此方法比使用许多添加方法快得多。因此,可以使用单个 sql 语句将所有 updatable 记录插入临时 table 并批量更新主 table。
编辑:Document 表明 AddRange 仅优化变化检测。它不会改变将更改应用到数据库的方式。
a) EFCore.BulkExtensions - BatchUpdateAsync
_dbContext.Set<MyObjectEntity>().BatchUpdateAsync( x => new MyObjectEntity{ Id=123, Quantity=100 });
https://github.com/borisdj/EFCore.BulkExtensions
”EntityFrameworkCore 扩展:批量操作(插入、更新、删除、读取、更新插入、同步)和批量操作(删除、更新)。
库是轻量级且非常高效的,具有所有主要使用的 CRUD 操作。
入选 Microsoft 推荐的前 20 个 EF Core 扩展。
b) 或 EF 扩展 - UpdateFromQuery
_dbContext.Set<MyObjectEntity>().UpdateFromQuery( x => new MyObjectEntity{ Id=123, Quantity=100 });
资源:
https://entityframework-extensions.net/update-from-query
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.
我发现 easy way to do that 没有任何第 3 方包:
通过添加一种通用扩展方法SetValue
,您可以简单地编写:
示例:
void Main()
{
var dc = this; // context
var p = dc.Purchases.Where(x=>x.Description.ToLower()=="bike")
.SetValue(w => w.Description = "Bicycle");
p.Dump();
dc.SubmitChanges();
}
如您所见,任何符合Where
条件的值都可以显式设置为新值,因此这里的Bike
将被Bicycle
替换。您可以在之后查询 table 以查看更改是否真的持续存在。
当然,你也可以省略Where
语句,如果你想更改所有记录,如:
dc.Records.SetValue(x => x.Quantity = 100);
dc.SubmitChanges();
Entity framework (EF) / LINQ 跟踪这些更改,当您调用 .SubmitChanges() - 如果您使用的是 LinqPad,您可以在 SQL 选项卡中看到 - 它会创建 SQL代码如下:
-- Region Parameters
DECLARE @p0 Int = 3
DECLARE @p1 VarChar(1000) = 'Bicycle'
-- EndRegion
UPDATE [Purchase]
SET [Description] = @p1
WHERE [ID] = @p0
对于小的更改,这没问题,但对于大的 tables 它变得低效,因为它使用 ID 列来识别和更改记录,而不是 .SetValue 定义的描述列.
理论上 EF 可以对此进行优化,但如您所见,它并没有这样做。
因此,如果您想要真正的批量操作,您需要 运行 一个 SQL 命令,或者创建一个您通过 EF 调用的存储过程(用于复杂查询)。
扩展方法SetValue
这个扩展方法可以解决问题(不需要其他第 3 方包):
// see: https://visualstudiomagazine.com/articles/2019/07/01/updating-linq.aspx
public static class Extensions
{
public static IEnumerable<T> SetValue<T>(this IEnumerable<T> items,
Action<T> updateMethod)
{
foreach (T item in items)
{
updateMethod(item);
}
return items;
}
}
注:
上面的示例使用 Nutshell 示例数据库,您可以按照 this link and the code is written for LinqPad 6 轻松创建该数据库,但也可以轻松进行调整(LinqPad 6 使用 .NET Core,但您可以尝试使用LinqPad 5 以及 .NET Framework)。
EFCore 中将有built-in BulkUpdate()
和BulkDelete
方法,这些方法将在EFCore 7.0 中交付
context.Customers.Where(...).BulkDelete();
context.Customers.Where(...).BulkUpdate(c => new Customer { Age = c.Age + 1 });
context.Customers.Where(...).BulkUpdate(c => new { Age = c.Age + 1 });
可能通过使用 UpdateRange([NotNullAttribute] params TEntity[] entities)
private void bulkTagUpdate()
{
RfidTag tag = new RfidTag
{
Id = 1,
Status ="tight",
TagId = "234353444",
LocationId = "1",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
};
RfidTag tag2 = new RfidTag
{
Id = 2,
Status = "tight",
TagId = "3454544",
LocationId = "1",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
};
List<RfidTag> tagList = new List<RfidTag>();
tagList.Add(tag);
tagList.Add(tag2);
using (rfid_statusContext context = new rfid_statusContext())
{
context.RfidTags.UpdateRange(tagList);
context.SaveChanges();
MessageBox.Show("Update successfull !");
}
}
我正在尝试使用 Entity Framework 批量更新记录。我试过 Entity Framework.Extensions Update
方法。
Update
方法能够批量更新一组具有相同更新值的记录。
示例:
Id - Quantity
Record 1 - A - 10
Record 2 - B - 20
Record 3 - C - 30
我们可以通过简单调用
批量更新以上所有记录Records.Update(new => Record { Quantity = 100 });
如何使用 Entityframework.Extensions
或任何其他方法批量更新不同数量的每条记录,从而更快地完成批量更新?
using (yourDbEntities db = new yourDbEntities())
{
db.Database.ExecuteSqlCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
}
yourDbContext.ExecuteStoreCommand("UPDATE YourTABLE SET Quantity = {0} WHERE Id = {1}", quantity, id);
批量更新可以通过简单的 EF 而不是单独的扩展方法分三步完成:-
- 首先加载所有实体。
- 对每个实体进行 Foreach 并更改其字段值。
- 在 Foreach 保存上下文更改一次后。
这将在单个批次中发送多个更新查询。
如果您不想使用 SQL 语句,您可以使用 Attach 方法来更新实体而无需先加载它:
using (myDbEntities db = new myDbEntities())
{
try
{
//disable detection of changes to improve performance
db.Configuration.AutoDetectChangesEnabled = false;
//for all the entities to update...
MyObjectEntity entityToUpdate = new MyObjectEntity() {Id=123, Quantity=100};
db.MyObjectEntity.Attach(entityToUpdate);
//then perform the update
db.SaveChanges();
}
finally
{
//re-enable detection of changes
db.Configuration.AutoDetectChangesEnabled = true;
}
}
如果您只想修改几个属性,请使用这种方式:
foreach (var vSelectedDok in doks)
{
//disable detection of changes to improve performance
vDal.Configuration.AutoDetectChangesEnabled = false;
vDal.Dokumente.Attach(vSelectedDok);
vDal.Entry(vSelectedDok).Property(x=>x.Status).IsModified=true;
vDal.Entry(vSelectedDok).Property(x => x.LastDateChanged).IsModified = true;
}
vDal.SaveChanges();
在 EF 6 中,我们在每个 table 中都有 AddRange 方法。文档表明此方法比使用许多添加方法快得多。因此,可以使用单个 sql 语句将所有 updatable 记录插入临时 table 并批量更新主 table。
编辑:Document 表明 AddRange 仅优化变化检测。它不会改变将更改应用到数据库的方式。
a) EFCore.BulkExtensions - BatchUpdateAsync
_dbContext.Set<MyObjectEntity>().BatchUpdateAsync( x => new MyObjectEntity{ Id=123, Quantity=100 });
https://github.com/borisdj/EFCore.BulkExtensions
”EntityFrameworkCore 扩展:批量操作(插入、更新、删除、读取、更新插入、同步)和批量操作(删除、更新)。 库是轻量级且非常高效的,具有所有主要使用的 CRUD 操作。 入选 Microsoft 推荐的前 20 个 EF Core 扩展。
b) 或 EF 扩展 - UpdateFromQuery
_dbContext.Set<MyObjectEntity>().UpdateFromQuery( x => new MyObjectEntity{ Id=123, Quantity=100 });
资源:
https://entityframework-extensions.net/update-from-query
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.
我发现 easy way to do that 没有任何第 3 方包:
通过添加一种通用扩展方法SetValue
,您可以简单地编写:
示例:
void Main()
{
var dc = this; // context
var p = dc.Purchases.Where(x=>x.Description.ToLower()=="bike")
.SetValue(w => w.Description = "Bicycle");
p.Dump();
dc.SubmitChanges();
}
如您所见,任何符合Where
条件的值都可以显式设置为新值,因此这里的Bike
将被Bicycle
替换。您可以在之后查询 table 以查看更改是否真的持续存在。
当然,你也可以省略Where
语句,如果你想更改所有记录,如:
dc.Records.SetValue(x => x.Quantity = 100);
dc.SubmitChanges();
Entity framework (EF) / LINQ 跟踪这些更改,当您调用 .SubmitChanges() - 如果您使用的是 LinqPad,您可以在 SQL 选项卡中看到 - 它会创建 SQL代码如下:
-- Region Parameters
DECLARE @p0 Int = 3
DECLARE @p1 VarChar(1000) = 'Bicycle'
-- EndRegion
UPDATE [Purchase]
SET [Description] = @p1
WHERE [ID] = @p0
对于小的更改,这没问题,但对于大的 tables 它变得低效,因为它使用 ID 列来识别和更改记录,而不是 .SetValue 定义的描述列.
理论上 EF 可以对此进行优化,但如您所见,它并没有这样做。 因此,如果您想要真正的批量操作,您需要 运行 一个 SQL 命令,或者创建一个您通过 EF 调用的存储过程(用于复杂查询)。
扩展方法SetValue
这个扩展方法可以解决问题(不需要其他第 3 方包):
// see: https://visualstudiomagazine.com/articles/2019/07/01/updating-linq.aspx
public static class Extensions
{
public static IEnumerable<T> SetValue<T>(this IEnumerable<T> items,
Action<T> updateMethod)
{
foreach (T item in items)
{
updateMethod(item);
}
return items;
}
}
注: 上面的示例使用 Nutshell 示例数据库,您可以按照 this link and the code is written for LinqPad 6 轻松创建该数据库,但也可以轻松进行调整(LinqPad 6 使用 .NET Core,但您可以尝试使用LinqPad 5 以及 .NET Framework)。
EFCore 中将有built-in BulkUpdate()
和BulkDelete
方法,这些方法将在EFCore 7.0 中交付
context.Customers.Where(...).BulkDelete();
context.Customers.Where(...).BulkUpdate(c => new Customer { Age = c.Age + 1 });
context.Customers.Where(...).BulkUpdate(c => new { Age = c.Age + 1 });
可能通过使用 UpdateRange([NotNullAttribute] params TEntity[] entities)
private void bulkTagUpdate()
{
RfidTag tag = new RfidTag
{
Id = 1,
Status ="tight",
TagId = "234353444",
LocationId = "1",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
};
RfidTag tag2 = new RfidTag
{
Id = 2,
Status = "tight",
TagId = "3454544",
LocationId = "1",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
};
List<RfidTag> tagList = new List<RfidTag>();
tagList.Add(tag);
tagList.Add(tag2);
using (rfid_statusContext context = new rfid_statusContext())
{
context.RfidTags.UpdateRange(tagList);
context.SaveChanges();
MessageBox.Show("Update successfull !");
}
}