提高实体框架中大容量插入的性能

我想插入20000条记录在一个实体框架表,它需要大约2分钟。除了使用 SP 来提高它的性能之外,还有其他的方法吗。这是我的暗号:

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

目前还没有更好的方法,但是通过将 SaveChanges 移动到 for 循环中大约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,使其更接近于更好的性能。它不会大大提高速度,但它将允许您显示一些用户的进展,并使其更加用户友好。

当这样做时,没有办法强迫 EF 提高性能。问题是 EF 在单独的数据库往返过程中执行每个插入。很棒吧?甚至数据集也支持批处理。检查 这篇文章找到一些变通方法。另一种解决方法是使用自定义存储过程接受表值参数,但是这需要原始的 ADO.NET。

也许这个 回答能帮到你。看来你想定期处理上下文。这是因为随着附属实体的增长,上下文变得越来越大。

有几个改进的机会(如果您正在使用 DbContext) :

套装:

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

在包含100个插入的软件包中执行 SaveChanges()... 或者您可以尝试使用包含1000个项目的软件包,然后查看性能的变化。

Since during all this inserts, the context is the same and it is getting bigger, you can 每插入1000次重新生成上下文对象。 var yourContext = new YourContext(); I think this is the big gain.

Doing this improvements in an importing data process of mine, took it from 7 minutes to 6 seconds.

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

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

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

这篇文章比较了 SqlBulkCopy 类和 EF 对于父子关系的对象(也描述了实现大容量插入所需的设计变化) : 如何向 SQLServer 数据库大容量插入复杂对象

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

这个版本是 EF5的

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);
}
}

这个版本是 EF6的

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;
}
}

尝试使用大容量插入... 。

Http://code.msdn.microsoft.com/linqentitydatareader

If you have a collection of entities e.g storeEntities you can store them using SqlBulkCopy as follows

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

这个密码有个漏洞。确保实体的实体框架定义与表定义完全相关,确保实体的属性在实体模型中的顺序与 SQLServer 表中的列相同。如果不这样做,将导致异常。

在 Azure 环境中,Basic 网站只有1个实例。我试图在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();
});
 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();

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

  • 使用 Add 方法
  • 使用 SaveChanges

使用 Add 方法

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

见: http://entityframework.net/improve-ef-add-performance

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

  • 添加(取105,000毫秒)
  • AddRange (take ~120ms)

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

问题是 Add 方法在每个添加的实体上都尝试检测更改,而 AddRange 在所有实体都添加到上下文之后才会检测更改一次。

Common solutions are:

  • Use AddRange over Add
  • 将 AutoDetectChanges 设置为 false
  • SPLIT SaveChanges in multiple batches

使用 SaveChanges

尚未为大容量操作创建实体框架。对于保存的每个实体,将执行一次数据库往返。

So, if you want to insert 20,000 records, you will perform 20,000 database round-trip which is INSANE!

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

  • 实体框架扩展(推荐)
  • 公用事业
  • 实体框架

见: 实体框架大容量插入库

在选择大容量插入库时要小心。只有实体框架扩展支持所有类型的关联和继承,并且它是唯一仍然支持的。


Disclaimer: I'm the owner of 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;
});

编辑: 在评论中回答问题

为您创建的库的每个大容量插入是否有推荐的最大大小

Not too high, not too low. There isn't a particular value that fit in all scenarios since it depends on multiple factors such as row size, index, trigger, etc.

通常建议在4000左右。

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

可以使用实体框架事务。如果启动了一个事务,我们的库将使用该事务。但是要小心,花费太多时间的事务也会带来一些问题,比如行/索引/表锁。

虽然很晚才回复,但我发帖回答是因为我也遭受了同样的痛苦。 我已经为此创建了一个新的 GitHub 项目,到目前为止,它使用 SqlBulkCopy 透明地支持 Sql 服务器的批量插入/更新/删除。

Https://github.com/mhanafy/entityextensions

还有其他好处,希望,它将延长,做更多的轨道。

使用它就像

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

Hope it helps!