在Entity框架中提高批量插入性能

时间:2011-05-24 07:41:42

标签: entity-framework entity

我想按实体框架在表中插入20000条记录,大约需要2分钟。除了使用SP来改善其性能之外,还有什么方法。这是我的代码:

 foreach (Employees item in sequence)
 {
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);                  
 }
 dataContext.SaveChanges();

12 个答案:

答案 0 :(得分:205)

有几次改进的机会(如果您使用的是DbContext):

设置:

yourContext.Configuration.AutoDetectChangesEnabled = false;
yourContext.Configuration.ValidateOnSaveEnabled = false;

在100个插件的包中执行SaveChanges() ... ,或者您可以尝试使用1000个项目的包,并查看效果的变化。

因为在所有这些插入过程中,上下文相同而且变得越来越大,您可以每1000次插入重建上下文对象。 var yourContext = new YourContext();我认为这是一个很大的收获。

在我的导入数据进程中进行此改进,从7分钟到6秒。

实际数字......在您的情况下不能是100或1000 ...尝试并调整它。

答案 1 :(得分:42)

以这种方式执行此操作时,无法强制EF提高性能。问题是EF在单独的往返数据库中执行每个插入。太棒了不是吗?甚至DataSet都支持批处理。检查this article以获取一些解决方法。另一种解决方法是使用自定义存储过程接受表值参数,但您需要原始ADO.NET。

答案 2 :(得分:33)

您可以使用bulk insert extension

这是一个小比较图表

EntityFramework.BulkInsert vs EF AddRange

并且使用非常简单

context.BulkInsert(hugeAmountOfEntities);

希望这会有所帮助

答案 3 :(得分:25)

使用下面的代码,您可以使用一种方法扩展部分上下文类,该方法将获取实体对象的集合并将它们批量复制到数据库。只需将类的名称从MyEntities替换为您的实体类的名称,并将其添加到项目的正确名称空间中。之后,您需要做的就是调用BulkInsertAll方法移交要插入的实体对象。不要重复使用上下文类,而是每次使用它时创建一个新实例。这是必需的,至少在某些版本的EF中是必需的,因为与此处使用的SQLConnection相关联的认证数据在使用该类一次后会丢失。我不知道为什么。

此版本适用于EF 5

public partial class MyEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5 * 60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("TypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.NonPublic | BindingFlags.Instance, null, instance, null);
    }
}

此版本适用于EF 6

public partial class CMLocalEntities
{
    public void BulkInsertAll<T>(T[] entities) where T : class
    {
        var conn = (SqlConnection)Database.Connection;

        conn.Open();

        Type t = typeof(T);
        Set(t).ToString();
        var objectContext = ((IObjectContextAdapter)this).ObjectContext;
        var workspace = objectContext.MetadataWorkspace;
        var mappings = GetMappings(workspace, objectContext.DefaultContainerName, typeof(T).Name);

        var tableName = GetTableName<T>();
        var bulkCopy = new SqlBulkCopy(conn) { DestinationTableName = tableName };

        // Foreign key relations show up as virtual declared 
        // properties and we want to ignore these.
        var properties = t.GetProperties().Where(p => !p.GetGetMethod().IsVirtual).ToArray();
        var table = new DataTable();
        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;

            // Nullable properties need special treatment.
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            // Since we cannot trust the CLR type properties to be in the same order as
            // the table columns we use the SqlBulkCopy column mappings.
            table.Columns.Add(new DataColumn(property.Name, propertyType));
            var clrPropertyName = property.Name;
            var tableColumnName = mappings[property.Name];
            bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(clrPropertyName, tableColumnName));
        }

        // Add all our entities to our data table
        foreach (var entity in entities)
        {
            var e = entity;
            table.Rows.Add(properties.Select(property => GetPropertyValue(property.GetValue(e, null))).ToArray());
        }

        // send it to the server for bulk execution
        bulkCopy.BulkCopyTimeout = 5*60;
        bulkCopy.WriteToServer(table);

        conn.Close();
    }

    private string GetTableName<T>() where T : class
    {
        var dbSet = Set<T>();
        var sql = dbSet.ToString();
        var regex = new Regex(@"FROM (?<table>.*) AS");
        var match = regex.Match(sql);
        return match.Groups["table"].Value;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }

    private Dictionary<string, string> GetMappings(MetadataWorkspace workspace, string containerName, string entityName)
    {
        var mappings = new Dictionary<string, string>();
        var storageMapping = workspace.GetItem<GlobalItem>(containerName, DataSpace.CSSpace);
        dynamic entitySetMaps = storageMapping.GetType().InvokeMember(
            "EntitySetMaps",
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance,
            null, storageMapping, null);

        foreach (var entitySetMap in entitySetMaps)
        {
            var typeMappings = GetArrayList("EntityTypeMappings", entitySetMap);
            dynamic typeMapping = typeMappings[0];
            dynamic types = GetArrayList("Types", typeMapping);

            if (types[0].Name == entityName)
            {
                var fragments = GetArrayList("MappingFragments", typeMapping);
                var fragment = fragments[0];
                var properties = GetArrayList("AllProperties", fragment);
                foreach (var property in properties)
                {
                    var edmProperty = GetProperty("EdmProperty", property);
                    var columnProperty = GetProperty("ColumnProperty", property);
                    mappings.Add(edmProperty.Name, columnProperty.Name);
                }
            }
        }

        return mappings;
    }

    private ArrayList GetArrayList(string property, object instance)
    {
        var type = instance.GetType();
        var objects = (IEnumerable)type.InvokeMember(
            property, 
            BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
        var list = new ArrayList();
        foreach (var o in objects)
        {
            list.Add(o);
        }
        return list;
    }

    private dynamic GetProperty(string property, object instance)
    {
        var type = instance.GetType();
        return type.InvokeMember(property, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance, null, instance, null);
    }

}

最后,给你一些Linq-To-Sql爱好者。

partial class MyDataContext
{
    partial void OnCreated()
    {
        CommandTimeout = 5 * 60;
    }

    public void BulkInsertAll<T>(IEnumerable<T> entities)
    {
        entities = entities.ToArray();

        string cs = Connection.ConnectionString;
        var conn = new SqlConnection(cs);
        conn.Open();

        Type t = typeof(T);

        var tableAttribute = (TableAttribute)t.GetCustomAttributes(
            typeof(TableAttribute), false).Single();
        var bulkCopy = new SqlBulkCopy(conn) { 
            DestinationTableName = tableAttribute.Name };

        var properties = t.GetProperties().Where(EventTypeFilter).ToArray();
        var table = new DataTable();

        foreach (var property in properties)
        {
            Type propertyType = property.PropertyType;
            if (propertyType.IsGenericType &&
                propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            table.Columns.Add(new DataColumn(property.Name, propertyType));
        }

        foreach (var entity in entities)
        {
            table.Rows.Add(properties.Select(
              property => GetPropertyValue(
              property.GetValue(entity, null))).ToArray());
        }

        bulkCopy.WriteToServer(table);
        conn.Close();
    }

    private bool EventTypeFilter(System.Reflection.PropertyInfo p)
    {
        var attribute = Attribute.GetCustomAttribute(p, 
            typeof (AssociationAttribute)) as AssociationAttribute;

        if (attribute == null) return true;
        if (attribute.IsForeignKey == false) return true; 

        return false;
    }

    private object GetPropertyValue(object o)
    {
        if (o == null)
            return DBNull.Value;
        return o;
    }
}

答案 4 :(得分:8)

也许这里answer会帮助你。似乎您想要定期处理上下文。这是因为随着附加实体的增长,上下文变得越来越大。

答案 5 :(得分:4)

目前没有更好的方法,但是通过将for循环中的SaveChanges移动到大约10个项目可能会有一个微小的改进。

int i = 0;

foreach (Employees item in sequence)
{
   t = new Employees ();
   t.Text = item.Text;
   dataContext.Employees.AddObject(t);   

   // this will add max 10 items together
   if((i % 10) == 0){
       dataContext.SaveChanges();
       // show some progress to user based on
       // value of i
   }
   i++;
}
dataContext.SaveChanges();

您可以调整10以更接近更好的性能。它不会大大提高速度,但它可以让您向用户显示一些进度并使其更加用户友好。

答案 6 :(得分:4)

更好的方法是完全跳过Entity Framework进行此操作,并依赖于SqlBulkCopy类。其他操作可以像以前一样继续使用EF。

这增加了解决方案的维护成本,但与使用EF相比,无论如何都有助于将大量对象插入数据库所需的时间减少一到两个数量级。

这篇文章将SqlBulkCopy类与EF进行比较,以确定具有父子关系的对象(还描述了实现批量插入所需的设计变更):How to Bulk Insert Complex Objects into SQL Server Database

答案 7 :(得分:4)

在Azure环境中,Basic网站有1个Instance.I试图在25000条记录中一次插入一批1000条记录,使用for循环需要11.5分钟,但并行执行需要不到一分钟。所以我建议使用TPL(任务并行库)。

         var count = (you collection / 1000) + 1;
         Parallel.For(0, count, x =>
        {
            ApplicationDbContext db1 = new ApplicationDbContext();
            db1.Configuration.AutoDetectChangesEnabled = false;

            var records = members.Skip(x * 1000).Take(1000).ToList();
            db1.Members.AddRange(records).AsParallel();

            db1.SaveChanges();
            db1.Dispose();
        });

答案 8 :(得分:4)

您的代码存在两个主要的性能问题:

  • 使用添加方法
  • 使用SaveChanges

使用添加方法

Add方法在您添加的每个实体中变得越来越慢。

请参阅:http://entityframework.net/improve-ef-add-performance

例如,通过以下方式添加10,000个实体

  • 添加(约~10.5万毫秒)
  • AddRange(需要〜120ms)

注意:实体尚未保存在数据库中!

问题是Add方法尝试在添加的每个实体处检测到DetectChanges,而AddRange在将所有实体添加到上下文后执行一次。

常见的解决方案是:

  • 使用AddRange而不是添加
  • SET AutoDetectChanges to false
  • SPLIT多个批次的SaveChanges

使用SaveChanges

尚未为批量操作创建实体框架。对于您保存的每个实体,都会执行数据库往返。

因此,如果要插入20,000条记录,您将执行20,000次数据库往返,即 INSANE

有一些支持批量插入的第三方库可用:

  • Z.EntityFramework.Extensions(推荐
  • EFUtilities
  • EntityFramework.BulkInsert

请参阅:Entity Framework Bulk Insert library

选择批量插入库时要小心。只有Entity Framework Extensions支持所有类型的关联和继承,并且它是唯一仍然受支持的。

免责声明:我是Entity Framework Extensions

的所有者

此库允许您执行场景所需的所有批量操作:

  • 批量保存更改
  • 批量插入
  • 批量删除
  • 批量更新
  • 批量合并

实施例

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

编辑:在评论中回答问题

  

对于您创建的库的每个批量插入是否有建议的最大大小

不是太高,也不是太低。没有特定的值适合所有场景,因为它取决于多个因素,如行大小,索引,触发器等。

通常建议大约4000。

  

还有一种方法可以将它全部绑定在一个事务中,而不用担心它超时

您可以使用Entity Framework事务。如果启动了交易,我们的库就会使用该交易。但是要小心,一个需要花费太多时间的事务也会出现诸如某些行/索引/表锁之类的问题。

答案 9 :(得分:3)

尝试使用批量插入....

http://code.msdn.microsoft.com/LinqEntityDataReader

如果您有一组实体,例如storeEntities,您可以使用SqlBulkCopy存储它们,如下所示

        var bulkCopy = new SqlBulkCopy(connection);
        bulkCopy.DestinationTableName = TableName;
        var dataReader = storeEntities.AsDataReader();
        bulkCopy.WriteToServer(dataReader);

这段代码有一个问题。确保实体的实体框架定义与表定义完全相关,确保实体模型中的实体属性与SQL Server表中的列的顺序相同。如果不这样做将导致异常。

答案 10 :(得分:0)

虽然迟到了回复,但我发布了答案,因为我遭受了同样的痛苦。 我为此创建了一个新的GitHub项目,截至目前,它支持使用SqlBulkCopy透明地为Sql服务器批量插入/更新/删除。

https://github.com/MHanafy/EntityExtensions

还有其他好东西,希望它会延伸到更多的轨道。

使用它就像

一样简单
var insertsAndupdates = new List<object>();
var deletes = new List<object>();
context.BulkUpdate(insertsAndupdates, deletes);

希望它有所帮助!

答案 11 :(得分:-2)

 Use : db.Set<tale>.AddRange(list); 
Ref :
TESTEntities db = new TESTEntities();
List<Person> persons = new List<Person> { 
new  Person{Name="p1",Place="palce"},
new  Person{Name="p2",Place="palce"},
new  Person{Name="p3",Place="palce"},
new  Person{Name="p4",Place="palce"},
new  Person{Name="p5",Place="palce"}
};
db.Set<Person>().AddRange(persons);
db.SaveChanges();