提高entity framework中的批量插入性能
我想通过entity framework在表中插入20000条logging,大约需要2分钟。 除了使用SP来提高性能之外,还有什么办法可以改善性能。 这是我的代码:
foreach (Employees item in sequence) { t = new Employees (); t.Text = item.Text; dataContext.Employees.AddObject(t); } dataContext.SaveChanges();
有几个改进的机会(如果你正在使用DbContext
):
组:
yourContext.Configuration.AutoDetectChangesEnabled = false; yourContext.Configuration.ValidateOnSaveEnabled = false;
在100个插入的包中执行SaveChanges()
…或者您可以尝试使用1000个包的包,并查看性能的变化。
由于在所有这些插入过程中,上下文是相同的,并且变得越来越大,您可以每1000次插入重build您的上下文对象。 var yourContext = new YourContext();
我认为这是很大的收获。
在我的导入数据过程中做了这个改进,花了7分钟到6秒。
实际的数字…不能是100或1000在你的情况下…尝试它,并调整它。
这样做时,无法强制EF提高性能。 问题是,EF执行每个插入单独往返数据库。 真棒不是吗? 即使DataSets支持批处理。 检查这篇文章的一些解决方法。 另一个解决方法是使用自定义存储过程接受表值参数,但你需要原始的ADO.NET。
您可以使用批量插入扩展
这是一个小比较图
而且使用非常简单
context.BulkInsert(hugeAmountOfEntities);
希望这可以帮助
使用下面的代码可以扩展部分上下文类,方法是将实体对象的集合并将其批量复制到数据库。 只需从MyEntities中将类的名称replace为您的实体类所指定的名称,并将其添加到您的项目的正确名称空间中即可。 之后,您只需调用BulkInsertAll方法来交换要插入的实体对象。 不要重复使用上下文类,而应该在每次使用时创build一个新的实例。 至less在EF的某些版本中,这是必需的,因为与此处使用的SQLConnection关联的authentication数据在使用过一次类后会丢失。 我不知道为什么。
此版本适用于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; } }
也许这个答案在这里会帮助你。 似乎你想要周期性地处理上下文。 这是因为上下文变得越来越大。
在Azure环境中,基本网站有1个实例。我试图插入一批1000条logging,每次在25000条logging中使用for循环花了11.5分钟,但在并行执行中花了不到一分钟。所以我build议使用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(); });
更好的方法是完全跳过这个操作的entity framework,并依赖于SqlBulkCopy类。 其他操作可以像以前一样继续使用EF。
这增加了解决scheme的维护成本,但与使用EF相比,无论如何都有助于减less将大量对象集合插入数据库所需的时间1到2个数量级。
这里有一篇文章比较SqlBulkCopy类与EF与父子关系的对象(也描述实现批量插入所需的devise中的更改): 如何将复杂对象大容量插入到SQL Server数据库
目前还没有更好的方法,但是通过移动SaveChanges for 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以更接近更好的性能。 它不会大大提高速度,但它可以让你显示一些进步,使用户更友好。
尝试使用批量插入….
http://code.msdn.microsoft.com/LinqEntityDataReader
如果你有一个实体集合,例如storeEntities,你可以使用SqlBulkCopy来存储它们,如下所示
var bulkCopy = new SqlBulkCopy(connection); bulkCopy.DestinationTableName = TableName; var dataReader = storeEntities.AsDataReader(); bulkCopy.WriteToServer(dataReader);
这个代码有一个问题。 确保实体的entity framework定义与表定义完全相关,确保实体的属性在实体模型中的顺序与SQL Server表中的列的顺序相同。 不这样做将导致一个例外。
你的代码有两个主要的性能问题:
- 使用Add方法
- 使用SaveChanges
使用Add方法
添加方法变得越来越慢,在每个添加的实体。
请参阅: http : //www.zzzprojects.com/entity-framework/how/performance-add
例如,通过以下方式添加10,000个实体:
- 加(约105,000ms)
- AddRange(取〜120ms)
注意:实体尚未保存在数据库中!
问题是添加方法试图DetectChanges在添加的每个实体,而AddRange在所有实体添加到上下文后执行一次。
常见的解决办法是
- 使用AddRange添加
- 将AutoDetectChanges设置为false
- 分批分割SaveChanges
使用SaveChanges
尚未为批量操作创buildentity framework。 对于您保存的每个实体,执行数据库往返。
所以如果你想插入20,000条logging,你将执行20,000次数据库往返,这是INSANE !
有一些支持批量插入的第三方库可用:
- Z.EntityFramework.Extensions( 推荐 )
- EFUtilities
- EntityFramework.BulkInsert
请参阅: entity framework批量插入库
select批量插入库时要小心。 只有entity framework扩展支持所有types的关联和inheritance,而且它是唯一支持的。
免责声明 :我是entity framework扩展的所有者
这个库允许您执行您的场景所需的所有批量操作:
- 批量SaveChanges
- 大容量插入
- 批量删除
- 批量更新
- 批量合并
例
// 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; });
虽然答复迟了,但是我发布的答案是因为我遭受了同样的痛苦。 我为此创build了一个新的GitHub项目,截至目前,它支持透明地使用SqlBulkCopy批量插入/更新/删除Sql服务器。
https://github.com/MHanafy/EntityExtensions
还有其他的好东西,希望能延续下去。
使用它就像
var insertsAndupdates = new List<object>(); var deletes = new List<object>(); context.BulkUpdate(insertsAndupdates, deletes);
希望能帮助到你!
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();