我们的应用程序中有一个用例,其中用户触发了一个请求,该请求将导致插入 100 到 1000 行。
在插入之后,我们需要对象继续处理并创建更多对象,这些对象是原始插入对象的外键,或者换句话说,我们需要插入对象的主键 ID。
到目前为止,我们已经使用 EF 在 foreach 循环中执行此操作,这太慢了,大约需要 15-20 秒才能完成大约 600 行。(在阻止用户时,不好:()
原始代码(也处理更新,但我们不关心那里的性能,它不会阻止用户):
foreach (Location updatedLoc in locationsLoaded)
{
// find it in the collection from the database
Location fromDb = existingLocations.SingleOrDefault(loc => loc.ExtId.Equals(updatedLoc.ExtId));
// update or insert
if (fromDb != null)
{
// link ids for update
updatedLoc.Id = fromDb.Id;
// set values for update
db.Entry(fromDb).CurrentValues.SetValues(updatedLoc);
}
else
{
System.Diagnostics.Trace.WriteLine("Adding new location: " + updatedLoc.Name, "loadSimple");
// insert a new location <============ This is the bottleneck, takes about 20-40ms per row
db.Locations.Add(updatedLoc);
}
}
// This actually takes about 3 seconds for 600 rows, was actually acceptable
db.SaveChanges();
所以在研究了SO和互联网之后,我发现我以错误的方式使用EF,需要使用SqlBulkCopy
因此代码被重写了,过去大约需要 20 秒,现在需要大约 100 毫秒(!)
foreach (Location updatedLoc in locationsLoaded)
{
// find it in the collection from the database
Location fromDb = existingLocations.SingleOrDefault(loc => loc.ExtId.Equals(updatedLoc.ExtId));
// update or insert
if (fromDb != null)
{
// link ids for update
updatedLoc.Id = fromDb.Id;
// set values for update
db.Entry(fromDb).CurrentValues.SetValues(updatedLoc);
}
else
{
System.Diagnostics.Trace.WriteLine("Adding new location: " + updatedLoc.Name, "loadSimple");
// insert a new location
dataTable.Rows.Add(new object[] { \\the 14 fields of the location.. });
}
}
System.Diagnostics.Trace.WriteLine("preparing to bulk insert", "loadSimple");
// perform the bulk insert
using (var bulkCopy = new System.Data.SqlClient.SqlBulkCopy(System.Configuration.ConfigurationManager.ConnectionStrings["bulk-inserter"].ConnectionString))
{
bulkCopy.DestinationTableName = "Locations";
for (int i = 0; i < dataTable.Columns.Count; i++)
{
bulkCopy.ColumnMappings.Add(i, i + 1);
}
bulkCopy.WriteToServer(dataTable);
}
// for update
db.SaveChanges();
问题是,在大容量复制之后,Locations
作为 EF ORM 一部分的集合中的对象没有改变(这没关系,并且是预期的),但我需要插入的 id 才能继续处理这些对象。
一个简单的解决方案是立即从数据库中再次选择数据,我手头有数据,我可以简单地将其重新选择到不同的集合中。
但是该解决方案感觉不正确,是否无法将 id 作为插入的一部分。
编辑:简单的解决方案有效,请参阅下面关于如何轻松将其同步回 EF 的已接受答案。
也许我不应该使用 SqlBulkCopy (我预计最多大约 1000 行,不再)并使用其他东西?
请注意,一些相关的 SO 问题和解决方案似乎都远离了 EF..
- 可以在 SQL BulkCopy 之后取回 PrimayKey ID 吗?
- 提高实体框架中的批量插入性能
- 在实体框架中插入的最快方法(这是关于具有许多待处理插入的 SaveChanges() 性能,应该在每 X 次插入时调用它,而不是在处理结束时调用 1000s 待处理)