[英]SqlBulkCopy Multiple Tables Insert under single Transaction OR Bulk Insert Operation between Entity Framework and Classic Ado.net
我有两个表需要在我的应用程序运行时插入。
假设我有如下表格
我的问题是数据量。
我需要向 tbl_FirstTable 插入超过 10,000 行,向 tbl_SecondTable 插入超过 500,000 行。
所以首先,我使用实体框架如下。
public bool Save_tbl_FirstTable_Vs_tbl_SecondTable(List<tbl_FirstTable> List_tbl_FirstTable, List<tbl_SecondTable> List_tbl_SecondTable)
{
bool IsSuccessSave = false;
try
{
using (DummyDBClass_ObjectContext _DummyDBClass_ObjectContext = new DummyDBClass_ObjectContext())
{
foreach (tbl_FirstTable _tbl_FirstTable in List_tbl_FirstTable)
{
_DummyDBClass_ObjectContext.tbl_FirstTable.InsertOnSubmit(_tbl_FirstTable);
}
foreach (tbl_SecondTable _tbl_SecondTable in List_tbl_SecondTable)
{
_DummyDBClass_ObjectContext.tbl_SecondTable.InsertOnSubmit(_tbl_SecondTable);
}
_DummyDBClass_ObjectContext.SubmitChanges();
IsSuccessSave = true;
}
}
catch (Exception ex)
{
Log4NetWrapper.WriteError(string.Format("{0} : {1} : Exception={2}",
this.GetType().FullName,
(new StackTrace(new StackFrame(0))).GetFrame(0).GetMethod().Name.ToString(),
ex.Message.ToString()));
if (ex.InnerException != null)
{
Log4NetWrapper.WriteError(string.Format("{0} : {1} : InnerException Exception={2}",
this.GetType().FullName,
(new StackTrace(new StackFrame(0))).GetFrame(0).GetMethod().Name.ToString(),
ex.InnerException.Message.ToString()));
}
}
return IsSuccessSave;
}
那是我面临错误Time out exception
的地方。
我认为如果我使用下面的代码,该异常将得到解决。
DummyDBClass_ObjectContext.CommandTimeout = 1800; // 30 minutes
所以我用了它。 它解决了,但我面临另一个错误OutOfMemory Exception
。
所以我搜索了解决方案,幸运的是,我找到了以下文章。
根据那篇文章,我将代码从实体框架更改为经典 ADO.net 代码。
public bool Save_tbl_FirstTable_Vs_tbl_SecondTable(DataTable DT_tbl_FirstTable, DataTable DT_tbl_SecondTable)
{
bool IsSuccessSave = false;
SqlTransaction transaction = null;
try
{
using (DummyDBClass_ObjectContext _DummyDBClass_ObjectContext = new DummyDBClass_ObjectContext())
{
var connectionString = ((EntityConnection)_DummyDBClass_ObjectContext.Connection).StoreConnection.ConnectionString;
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
using (transaction = connection.BeginTransaction())
{
using (SqlBulkCopy bulkCopy_tbl_FirstTable = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy_tbl_FirstTable.BatchSize = 5000;
bulkCopy_tbl_FirstTable.DestinationTableName = "dbo.tbl_FirstTable";
bulkCopy_tbl_FirstTable.ColumnMappings.Add("ID", "ID");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("UploadFileID", "UploadFileID");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("Active", "Active");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("CreatedUserID", "CreatedUserID");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("CreatedDate", "CreatedDate");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("UpdatedUserID", "UpdatedUserID");
bulkCopy_tbl_FirstTable.ColumnMappings.Add("UpdatedDate", "UpdatedDate");
bulkCopy_tbl_FirstTable.WriteToServer(DT_tbl_FirstTable);
}
using (SqlBulkCopy bulkCopy_tbl_SecondTable = new SqlBulkCopy(connection, SqlBulkCopyOptions.KeepIdentity, transaction))
{
bulkCopy_tbl_SecondTable.BatchSize = 5000;
bulkCopy_tbl_SecondTable.DestinationTableName = "dbo.tbl_SecondTable";
bulkCopy_tbl_SecondTable.ColumnMappings.Add("ID", "ID");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("UploadFileDetailID", "UploadFileDetailID");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("CompaignFieldMasterID", "CompaignFieldMasterID");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("Value", "Value");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("Active", "Active");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("CreatedUserID", "CreatedUserID");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("CreatedDate", "CreatedDate");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("UpdatedUserID", "UpdatedUserID");
bulkCopy_tbl_SecondTable.ColumnMappings.Add("UpdatedDate", "UpdatedDate");
bulkCopy_tbl_SecondTable.WriteToServer(DT_tbl_SecondTable);
}
transaction.Commit();
IsSuccessSave = true;
}
connection.Close();
}
}
}
catch (Exception ex)
{
if (transaction != null)
transaction.Rollback();
Log4NetWrapper.WriteError(string.Format("{0} : {1} : Exception={2}",
this.GetType().FullName,
(new StackTrace(new StackFrame(0))).GetFrame(0).GetMethod().Name.ToString(),
ex.Message.ToString()));
if (ex.InnerException != null)
{
Log4NetWrapper.WriteError(string.Format("{0} : {1} : InnerException Exception={2}",
this.GetType().FullName,
(new StackTrace(new StackFrame(0))).GetFrame(0).GetMethod().Name.ToString(),
ex.InnerException.Message.ToString()));
}
}
return IsSuccessSave;
}
最后,它在不到 15 秒的时间内对超过 500,000 行执行插入过程。
我发布这个场景有两个原因。
因此,每一个更好的解决方案都将受到赞赏。
1)使用EF6.x,性能比EF5.x好很多
这里有更多建议(来自Bulk insert with EF )
2)通过为每个工作单元使用一个新的上下文来保持活动上下文图很小
3) 关闭 AutoDetechChangesEnabled - context.Configuration.AutoDetectChangesEnabled = false;
4)批处理,在你的循环中,定期调用 SaveChanges
我使用来自ZZZ Projects的付费实体框架扩展,由于流畅的 API(扩展方法、功能方法),它对开发人员很友好。 这不是广告,我在商业项目中使用了几年,它很棒。 如果您想免费使用某些东西并且您拥有 Oracle 数据库,则 Oracle 托管数据访问Oracle.ManagedDataAccess.Core具有批量操作的实现。
批量操作并不是 ORM 的真正用途。 对于批量插入操作,我将 xml 发送到存储过程,然后将其切碎并从那里进行批量插入/更新或合并。 因此,即使我使用 ORM,我也会创建一个不依赖于 EF(或 NHibernate)的域库。所以我有一个“安全阀”来在某些情况下绕过 ORM。
您应该考虑为此使用 System.Data.SqlClient.SqlBulkCopy。 这是文档 - http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.aspx ,当然还有很多在线教程。
如果我们希望 EF 批量插入记录,建议以下几点来提高性能
例子:
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;
}
为了提高性能,在“许多”记录(大约 100 或 1000 个左右的“许多”)之后调用 SaveChanges() 很重要。 它还提高了在 SaveChanges 之后处理上下文并创建新上下文的性能。
这会清除所有实体的上下文,SaveChanges 不会这样做,实体仍以 Unchanged 状态附加到上下文。 上下文中附加实体的不断增长的大小逐渐减慢了插入速度。 因此,在一段时间后清除它是有帮助的。
AutoDetectChangesEnabled = 假; 在 DbContext 上。
它还有一个很大的额外性能影响: 为什么在 EF 4.1 中插入实体比 ObjectContext 慢? .
在EF中,以下组合可以很好地提高速度。
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
您是否尝试在所有插入之前在 Context.Configuration 中设置 AutoDetectChangesEnabled = false ?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.