我正在使用EF 4.1一次性插入大量数据,包含在一个事务中(例如200万行)。现在我想添加UPDATE逻辑。请注意,根据数据量,禁用更改跟踪。在我的头顶,我会做这样的事情:
// Obviously simplified code...
public void AddOrUpdate(Foo foo)
{
if(!db.Foos.Any(x => someEqualityTest(foo)))
{
db.Foos.Add(foo);
}
else
{
var f = db.Foos.First(x => someEqualityTest(foo));
f = foo;
}
db.SaveChanges();
}
关于如何改进这方面的任何想法?
答案 0 :(得分:2)
我会将插入内容与更新分开。
对于插入,我建议使用SqlBulkCopy插入所有尚未存在的记录,并且它将更快地方式。
首先,DbContext中的Bulk Insert方法:
public class YourDbContext : DbContext
{
public void BulkInsert<T>(string tableName, IList<T> list)
{
using (var bulkCopy = new SqlBulkCopy(base.Database.Connection))
{
bulkCopy.BatchSize = list.Count;
bulkCopy.DestinationTableName = tableName;
var table = new DataTable();
var props = TypeDescriptor.GetProperties(typeof(T))
// Dirty hack to make sure we only have system
// data types (i.e. filter out the
// relationships/collections)
.Cast<PropertyDescriptor>()
.Where(p => "System" == p.PropertyType.Namespace)
.ToArray();
foreach (var prop in props)
{
bulkCopy.ColumnMappings.Add(prop.Name, prop.Name);
var type = Nullable.GetUnderlyingType(prop.PropertyType)
?? prop.PropertyType;
table.Columns.Add(prop.Name, type);
}
var values = new object[props.Length];
foreach (var item in list)
{
for (var i = 0; i < values.Length; i++)
{
values[i] = props[i].GetValue(item);
}
table.Rows.Add(values);
}
bulkCopy.WriteToServer(table);
}
}
}
然后,对于您的插入/更新:
public void AddOrUpdate(IList<Foo> foos)
{
var foosToUpdate = db.Foos.Where(x => foos.Contains(x)).ToList();
var foosToInsert = foos.Except(foosToUpdate).ToList();
foreach (var foo in foosToUpdate)
{
var f = db.Foos.First(x => someEqualityTest(x));
// update the existing foo `f` with values from `foo`
}
// Insert the new Foos to the table named "Foos"
db.BulkInsert("Foos", foosToinsert);
db.SaveChanges();
}
答案 1 :(得分:1)
您的更新......
var f = db.Foos.First(x => someEqualityTest(foo));
f = foo;
...将无效,因为您根本没有更改已加载和附加的对象f
,只需使用分离的对象f
覆盖变量foo
。附加的对象仍然在上下文中,但是在加载后它没有被更改,并且您没有指向它的变量。 SaveChanges
在这种情况下无能为力。
您拥有的“标准选项”是:
var f = db.Foos.First(x => someEqualityTest(foo));
db.Entry(f).State = EntityState.Modified;
或只是
db.Entry(foo).State = EntityState.Modified;
// attaches as Modified, no need to load f
这会将所有属性标记为已修改 - 无论它们是否真的发生了更改 - 并且会将每列的UPDATE发送到数据库。
第二个选项,它只会将真正更改的属性标记为已修改,并且仅为更改的列发送UPDATE:
var f = db.Foos.First(x => someEqualityTest(foo));
db.Entry(f).CurrentValues.SetValues(foo);
现在,有200万个对象需要更新,你没有“标准”情况,两个选项 - 特别是第二个可能在内部使用反射来匹配源和目标对象的属性名称 - 的选项可能太慢了
更新性能的最佳选择是更改跟踪代理。这意味着您需要将实体类中的每个属性标记为virtual
(不仅是导航属性,还要标记标量属性),并且不要禁用更改跟踪代理的创建(它由默认值)。
当您从数据库加载对象f
时,EF将创建一个动态代理对象(从您的实体派生),类似于延迟加载代理,其中代码注入每个属性设置器以维护标志该物业已经更改或没有。
代理提供的更改跟踪比基于快照的更改跟踪(在SaveChanges
或DetectChanges
中发生)快得多。
如果您使用更改跟踪代理,我不确定上述两个选项是否更快。您可能需要手动分配属性以获得最佳性能:
var f = db.Foos.First(x => someEqualityTest(foo));
f.Property1 = foo.Property1;
f.Property2 = foo.Property2;
// ...
f.PropertyN = foo.PropertyN;
根据我在几千个对象的类似更新情况下的经验,没有真正的替代方案可以更改关于性能的跟踪代理。