EF 4.1插入/更新逻辑最佳实践

时间:2012-03-28 16:51:51

标签: c# asp.net .net entity-framework entity-framework-4.1

我正在使用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();
}

关于如何改进这方面的任何想法?

2 个答案:

答案 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将创建一个动态代理对象(从您的实体派生),类似于延迟加载代理,其中代码注入每个属性设置器以维护标志该物业已经更改或没有。

代理提供的更改跟踪比基于快照的更改跟踪(在SaveChangesDetectChanges中发生)快得多。

如果您使用更改跟踪代理,我不确定上述两个选项是否更快。您可能需要手动分配属性以获得最佳性能:

var f = db.Foos.First(x => someEqualityTest(foo));
f.Property1 = foo.Property1;
f.Property2 = foo.Property2;
// ...
f.PropertyN = foo.PropertyN;

根据我在几千个对象的类似更新情况下的经验,没有真正的替代方案可以更改关于性能的跟踪代理。