Bon*_*arp 640 c# sql entity-framework
我正在寻找插入Entity Framework的最快方法.
我问这个是因为你有一个活跃的TransactionScope并且插入很大(4000+).它可能持续超过10分钟(事务的默认超时),这将导致事务不完整.
Sla*_*uma 950
您对以下问题的评论中的评论:
"... SavingChanges(每条记录)......"
这是你能做的最糟糕的事情!调用SaveChanges()
每条记录可以减慢批量插入速度.我会做一些简单的测试,很可能会提高性能:
SaveChanges()
ALL记录后调用一次.SaveChanges()
例如100条记录.SaveChanges()
例如,调用100条记录并处理上下文并创建一个新记录.对于批量插入,我正在尝试使用这样的模式:
using (TransactionScope scope = new TransactionScope())
{
MyDbContext context = null;
try
{
context = new MyDbContext();
context.Configuration.AutoDetectChangesEnabled = false;
int count = 0;
foreach (var entityToInsert in someCollectionOfEntitiesToInsert)
{
++count;
context = AddToContext(context, entityToInsert, count, 100, true);
}
context.SaveChanges();
}
finally
{
if (context != null)
context.Dispose();
}
scope.Complete();
}
private MyDbContext AddToContext(MyDbContext context,
Entity entity, int count, int commitCount, bool recreateContext)
{
context.Set<Entity>().Add(entity);
if (count % commitCount == 0)
{
context.SaveChanges();
if (recreateContext)
{
context.Dispose();
context = new MyDbContext();
context.Configuration.AutoDetectChangesEnabled = false;
}
}
return context;
}
Run Code Online (Sandbox Code Playgroud)
我有一个测试程序,它将560.000个实体(9个标量属性,没有导航属性)插入到DB中.使用此代码,它可在不到3分钟的时间内完成.
对于性能而言,SaveChanges()
在"许多"记录("许多"大约100或1000)之后调用是很重要的.它还提高了在SaveChanges之后处理上下文并创建新上下文的性能.这清除了所有SaveChanges
实体的上下文,不这样做,实体仍然依附于状态中的上下文Unchanged
.在上下文中,附加实体的大小越来越大,逐步减慢插入速度.因此,在一段时间后清除它是有帮助的.
以下是我的560.000实体的一些测量:
上面第一个测试中的行为是性能非常非线性,并且随着时间的推移会极度降低.("许多小时"是估计,我从未完成此测试,我在20分钟后停在50.000个实体.)这种非线性行为在所有其他测试中并不那么重要.
ark*_*nia 174
这种组合足以提高速度.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
Run Code Online (Sandbox Code Playgroud)
max*_*ego 97
最快的方法是使用我开发的批量插入扩展.
它使用SqlBulkCopy和自定义datareader来获得最大性能.因此,它比使用常规插入或AddRange快20多倍
用法非常简单
context.BulkInsert(hugeAmountOfEntities);
Run Code Online (Sandbox Code Playgroud)
Ada*_*kis 80
你应该看看使用System.Data.SqlClient.SqlBulkCopy
这个.这是文档,当然还有很多在线教程.
对不起,我知道你正在寻找一个简单的答案让EF去做你想做的事,但批量操作并不是ORM的意思.
小智 48
我同意Adam Rackis.SqlBulkCopy
是将批量记录从一个数据源传输到另一个数据源的最快方法.我用这个来复制20K记录,花了不到3秒钟.看看下面的例子.
public static void InsertIntoMembers(DataTable dataTable)
{
using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework"))
{
SqlTransaction transaction = null;
connection.Open();
try
{
transaction = connection.BeginTransaction();
using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction))
{
sqlBulkCopy.DestinationTableName = "Members";
sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname");
sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname");
sqlBulkCopy.ColumnMappings.Add("DOB", "DOB");
sqlBulkCopy.ColumnMappings.Add("Gender", "Gender");
sqlBulkCopy.ColumnMappings.Add("Email", "Email");
sqlBulkCopy.ColumnMappings.Add("Address1", "Address1");
sqlBulkCopy.ColumnMappings.Add("Address2", "Address2");
sqlBulkCopy.ColumnMappings.Add("Address3", "Address3");
sqlBulkCopy.ColumnMappings.Add("Address4", "Address4");
sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode");
sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber");
sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber");
sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted");
sqlBulkCopy.WriteToServer(dataTable);
}
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
}
}
Run Code Online (Sandbox Code Playgroud)
小智 20
我建议这篇文章介绍如何使用EF进行批量插入.
他探索了这些领域,并对性能进行了比较:
Adm*_*vić 18
我已经调查了Slauma的答案(这很棒,谢谢你的想法),我已经减少了批量,直到我达到了最佳速度.看看Slauma的结果:
可以看出,当从1移动到10并且从10移动到100时速度增加,但是从100到1000,插入速度再次下降.
所以我专注于将批量大小减少到10到100之间的值时发生的事情,这是我的结果(我使用不同的行内容,因此我的时间具有不同的值):
Quantity | Batch size | Interval
1000 1 3
10000 1 34
100000 1 368
1000 5 1
10000 5 12
100000 5 133
1000 10 1
10000 10 11
100000 10 101
1000 20 1
10000 20 9
100000 20 92
1000 27 0
10000 27 9
100000 27 92
1000 30 0
10000 30 9
100000 30 92
1000 35 1
10000 35 9
100000 35 94
1000 50 1
10000 50 10
100000 50 106
1000 100 1
10000 100 14
100000 100 141
Run Code Online (Sandbox Code Playgroud)
根据我的结果,批量大小的实际最佳值约为30.它不到10和100.问题是,我不知道为什么30是最优的,也不能找到任何合理的解释.
Mik*_*son 18
正如其他人所说,如果你想要非常好的插入性能,SqlBulkCopy就是这样做的.
它实现起来有点麻烦,但有些库可以帮助您实现它.有一些,但这次我将无耻地插入我自己的库:https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities
您需要的唯一代码是:
using (var db = new YourDbContext())
{
EFBatchOperation.For(db, db.BlogPosts).InsertAll(list);
}
Run Code Online (Sandbox Code Playgroud)
那么速度有多快?很难说因为它取决于很多因素,计算机性能,网络,对象大小等等.我所做的性能测试表明,25k实体可以在localhost上以标准方式在10s左右插入,如果你优化你的EF配置,在其他答案中提到.EFUtilities大约需要300毫秒.更有趣的是,我使用这种方法在15秒内节省了大约300万个实体,平均每秒约20万个实体.
如果您需要插入相关数据,那么问题就在于此.这可以使用上面的方法有效地进入sql server,但它要求你有一个Id生成策略,让你在父代的app-code中生成id,这样你就可以设置外键.这可以使用GUID或HiLo id生成之类的东西来完成.
小智 14
Dispose()
如果您Add()
依赖于上下文中其他预加载实体(例如导航属性)的实体,则上下文会产生问题
我使用类似的概念来保持我的上下文小,以实现相同的性能
但是不是Dispose()
上下文而是重新创建,我只是分离已经存在的实体SaveChanges()
public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class {
const int CommitCount = 1000; //set your own best performance number here
int currentCount = 0;
while (currentCount < entities.Count())
{
//make sure it don't commit more than the entities you have
int commitCount = CommitCount;
if ((entities.Count - currentCount) < commitCount)
commitCount = entities.Count - currentCount;
//e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext
for (int i = currentCount; i < (currentCount + commitCount); i++)
_context.Entry(entities[i]).State = System.Data.EntityState.Added;
//same as calling _context.Set<TEntity>().Add(entities[i]);
//commit entities[n to n+999] to database
_context.SaveChanges();
//detach all entities in the context that committed to database
//so it won't overload the context
for (int i = currentCount; i < (currentCount + commitCount); i++)
_context.Entry(entities[i]).State = System.Data.EntityState.Detached;
currentCount += commitCount;
} }
Run Code Online (Sandbox Code Playgroud)
用try catch包装它,TrasactionScope()
如果你需要,不要在这里显示它们以保持代码干净
Xav*_*rAM 12
[2019 更新] EF Core 3.1
按照上面所说的,在 EF Core 中禁用 AutoDetectChangesEnabled 效果很好:插入时间除以 100(从几分钟到几秒,具有交叉表关系的 10k 记录)
更新后的代码是:
context.ChangeTracker.AutoDetectChangesEnabled = false;
foreach (IRecord record in records) {
//Add records to your database
}
context.ChangeTracker.DetectChanges();
context.SaveChanges();
context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable
Run Code Online (Sandbox Code Playgroud)
Man*_*pel 11
因为这里从来没有提到过,所以我想在这里推荐EFCore.BulkExtensions
context.BulkInsert(entitiesList); context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList); context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList); context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList); context.BulkInsertOrUpdateAsync(entitiesList); // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList); context.BulkReadAsync(entitiesList);
Run Code Online (Sandbox Code Playgroud)
Mic*_*ala 10
是的,SqlBulkUpdate
确实是完成此类任务最快的工具。我想在 .NET Core 中找到“最省力”的通用方法,因此我最终使用了来自 Marc Gravell 的名为 FastMember 的优秀库,并为实体框架 DB 上下文编写了一个微小的扩展方法。工作速度快如闪电:
using System.Collections.Generic;
using System.Linq;
using FastMember;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace Services.Extensions
{
public static class DbContextExtensions
{
public static void BulkCopyToServer<T>(this DbContext db, IEnumerable<T> collection)
{
var messageEntityType = db.Model.FindEntityType(typeof(T));
var tableName = messageEntityType.GetSchema() + "." + messageEntityType.GetTableName();
var tableColumnMappings = messageEntityType.GetProperties()
.ToDictionary(p => p.PropertyInfo.Name, p => p.GetColumnName());
using (var connection = new SqlConnection(db.Database.GetDbConnection().ConnectionString))
using (var bulkCopy = new SqlBulkCopy(connection))
{
foreach (var (field, column) in tableColumnMappings)
{
bulkCopy.ColumnMappings.Add(field, column);
}
using (var reader = ObjectReader.Create(collection, tableColumnMappings.Keys.ToArray()))
{
bulkCopy.DestinationTableName = tableName;
connection.Open();
bulkCopy.WriteToServer(reader);
connection.Close();
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我知道这是一个非常古老的问题,但是这里的一个人说开发了一种扩展方法来使用EF的批量插入,当我检查时,我发现该库今天的成本为599美元(对于一个开发人员).也许这对整个库来说都是有意义的,但是对于大量插入来说这太过分了.
这是我制作的一个非常简单的扩展方法.我首先使用它与数据库配对(不要先用代码测试,但我认为它的工作方式相同).YourEntities
使用您的上下文名称进行更改:
public partial class YourEntities : DbContext
{
public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities)
{
using (var conn = new SqlConnection(Database.Connection.ConnectionString))
{
await conn.OpenAsync();
Type t = typeof(T);
var bulkCopy = new SqlBulkCopy(conn)
{
DestinationTableName = GetTableName(t)
};
var table = new DataTable();
var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));
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 => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
}
bulkCopy.BulkCopyTimeout = 0;
await bulkCopy.WriteToServerAsync(table);
}
}
public void BulkInsertAll<T>(IEnumerable<T> entities)
{
using (var conn = new SqlConnection(Database.Connection.ConnectionString))
{
conn.Open();
Type t = typeof(T);
var bulkCopy = new SqlBulkCopy(conn)
{
DestinationTableName = GetTableName(t)
};
var table = new DataTable();
var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string));
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 => property.GetValue(entity, null) ?? DBNull.Value).ToArray());
}
bulkCopy.BulkCopyTimeout = 0;
bulkCopy.WriteToServer(table);
}
}
public string GetTableName(Type type)
{
var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));
var entityType = metadata
.GetItems<EntityType>(DataSpace.OSpace)
.Single(e => objectItemCollection.GetClrType(e) == type);
var entitySet = metadata
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == entityType.Name);
var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet);
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet;
return (string)table.MetadataProperties["Table"].Value ?? table.Name;
}
}
Run Code Online (Sandbox Code Playgroud)
您可以对任何继承的集合使用它IEnumerable
,如下所示:
await context.BulkInsertAllAsync(items);
Run Code Online (Sandbox Code Playgroud)
我正在寻找插入实体框架的最快方法
有一些支持批量插入的第三方库可用:
请参阅:实体框架批量插入库
选择批量插入库时要小心。只有实体框架扩展支持所有类型的关联和继承,并且它是唯一仍然受支持的一种。
免责声明:我是实体框架扩展的所有者
该库允许您执行场景所需的所有批量操作:
例子
// 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;
});
Run Code Online (Sandbox Code Playgroud)
保存列表的最快方法之一,您必须应用以下代码
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
Run Code Online (Sandbox Code Playgroud)
AutoDetectChangesEnabled = false
添加、添加范围和保存更改:不检测更改。
ValidateOnSaveEnabled = false;
不检测更改跟踪器
你必须添加nuget
Install-Package Z.EntityFramework.Extensions
Run Code Online (Sandbox Code Playgroud)
现在您可以使用以下代码
var context = new MyContext();
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
context.BulkInsert(list);
context.BulkSaveChanges();
Run Code Online (Sandbox Code Playgroud)
我对上面的@Slauma 的例子做了一个通用的扩展;
public static class DataExtensions
{
public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator)
{
context.Set(typeof(T)).Add((T)entity);
if (count % commitCount == 0)
{
context.SaveChanges();
if (recreateContext)
{
context.Dispose();
context = contextCreator.Invoke();
context.Configuration.AutoDetectChangesEnabled = false;
}
}
return context;
}
}
Run Code Online (Sandbox Code Playgroud)
用法:
public void AddEntities(List<YourEntity> entities)
{
using (var transactionScope = new TransactionScope())
{
DbContext context = new YourContext();
int count = 0;
foreach (var entity in entities)
{
++count;
context = context.AddToContext<TenancyNote>(entity, count, 100, true,
() => new YourContext());
}
context.SaveChanges();
transactionScope.Complete();
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
378868 次 |
最近记录: |