实体框架和经典 Ado.net 之间的单个事务或批量插入操作下的 SqlBulkCopy 多个表插入

Posted

技术标签:

【中文标题】实体框架和经典 Ado.net 之间的单个事务或批量插入操作下的 SqlBulkCopy 多个表插入【英文标题】:SqlBulkCopy Multiple Tables Insert under single Transaction OR Bulk Insert Operation between Entity Framework and Classic Ado.net 【发布时间】:2013-03-06 10:56:51 【问题描述】:

我有两个表需要在我的应用程序运行时插入。 假设我有如下表格

tbl_FirstTable 和 tbl_SecondTable

我的问题是数据量。 我需要向 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。 于是我搜索了解决方案,幸运的是,我找到了下面的文章。

    Problem with Bulk insert using Entity Framework Using Transactions with SqlBulkCopy Performing a Bulk Copy Operation in a Transaction

根据那篇文章,我将代码从实体框架更改为经典 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】:

1) 使用EF6.x,性能比EF5.x好很多

这里有更多建议(来自Bulk insert with EF)

2) 通过为每个工作单元使用新上下文来保持活动上下文图较小

3) 关闭 AutoDetechChangesEnabled - context.Configuration.AutoDetectChangesEnabled = false;

4) 批处理,在您的循环中,定期调用 SaveChanges

【讨论】:

以上是关于实体框架和经典 Ado.net 之间的单个事务或批量插入操作下的 SqlBulkCopy 多个表插入的主要内容,如果未能解决你的问题,请参考以下文章

ADO.NET 实体框架 - 带有实体框架 6 的 Oracle

如何使用 MS Access 作为 ADO.NET 实体框架的提供者?

带有 OLE DB SQLServer2000 数据源的 ADO.NET 实体框架

ADO.NET 实体框架教程 [关闭]

实体框架寻找 ADO.NET 提供程序 SqlServerCe.3.5 而不是 4.0

asp.net中的ADO.NET实体框架适用于不同的数据库及其性能