【问题标题】:Bulk insert from C# list into SQL Server into multiple tables with foreign key constraints将 C# 列表中的批量插入 SQL Server 到具有外键约束的多个表中
【发布时间】:2014-10-09 04:15:50
【问题描述】:

我完全不知道这个问题,任何帮助将不胜感激:

我有两张表,一张是主数据表(Table A),另一张表(Table B)与Table A中的一个条目有多个条目(具体为18个)的外键关系。

我正在获取列表中的数据并希望将其插入 SQL Server 数据库中。

我目前正在使用以下模式,但在 Table A 中插入 100 行并在 Table B 中插入相应的 18*100 行需要 14 分钟

using (SqlConnection conn = new SqlConnection(conStr))
{
    foreach (var ticket in Tickets)
    {
        sql = string.Format(@"INSERT INTO dbo.Tickets([ColumnA], [ColumnB] ,..." + @")
                              VALUES(@ColumnA, @ColumnB,@ColumnC, @ColumnD, .... +
                            @"SELECT SCOPE_IDENTITY();");

        using (cmd = new SqlCommand(sql, conn))
        {
            cmd.Parameters.AddWithValue("@ColumnA", (object)ticket.Id ?? DBNull.Value);
            cmd.Parameters.AddWithValue("@ColumnB", (object)ticket.Address ?? DBNull.Value);
            cmd.Parameters.AddWithValue("@ColumnC", (object)ticket.Status?? DBNull.Value);
            ....

            conn.Open();
            TableA_TicketId = Convert.ToInt32(cmd.ExecuteScalar());
        }
    }
} 

我使用SCOPE_IDENTITY() 从表 A 中获取插入的每条记录的最新标识,并将其用于插入第二个表中

sql = string.Format(@"INSERT INTO Tickets_Fields ([TableA_TicketId], [FieldName], [Key],[Value]) 
                      VALUES (@TableA_TicketId, @FieldName, @Key, @Value);");

using (cmd = new SqlCommand(sql, conn))
{
    foreach (var customField in ticket.CustomFields)
    {
        cmd.Parameters.Clear();
        cmd.Parameters.AddWithValue("@TableA_TicketId", (object)TicketId ?? DBNull.Value);
        cmd.Parameters.AddWithValue("@FieldName", (object)"CustomField" ?? DBNull.Value);
        ...
        cmd.ExecuteNonQuery();
    }
}

conn.Close();

请建议我是否可以通过任何方式提高此代码的性能。还是他们有更好/更快的方法?

【问题讨论】:

    标签: c# sql sql-server performance bulkinsert


    【解决方案1】:

    SqlBulkCopy是你的朋友

    using System;
    using System.Data;
    using System.Data.SqlClient;
    
    namespace SqlBulkInsertExample
    {
    class Program
    {
      static void Main(string[] args)
      {
            DataTable prodSalesData = new DataTable("ProductSalesData");
    
            // Create Column 1: SaleDate
            DataColumn dateColumn = new DataColumn();
            dateColumn.DataType = Type.GetType("System.DateTime");
            dateColumn.ColumnName = "SaleDate";
    
            // Create Column 2: ProductName
            DataColumn productNameColumn = new DataColumn();
            productNameColumn.ColumnName = "ProductName";
    
            // Create Column 3: TotalSales
            DataColumn totalSalesColumn = new DataColumn();
            totalSalesColumn.DataType = Type.GetType("System.Int32");
            totalSalesColumn.ColumnName = "TotalSales";
    
            // Add the columns to the ProductSalesData DataTable
            prodSalesData.Columns.Add(dateColumn);
            prodSalesData.Columns.Add(productNameColumn);
            prodSalesData.Columns.Add(totalSalesColumn);
    
            // Let's populate the datatable with our stats.
            // You can add as many rows as you want here!
    
            // Create a new row
            DataRow dailyProductSalesRow = prodSalesData.NewRow();
            dailyProductSalesRow["SaleDate"] = DateTime.Now.Date;
            dailyProductSalesRow["ProductName"] = "Nike";
            dailyProductSalesRow["TotalSales"] = 10;
    
            // Add the row to the ProductSalesData DataTable
            prodSalesData.Rows.Add(dailyProductSalesRow);
    
            // Copy the DataTable to SQL Server using SqlBulkCopy
            using (SqlConnection dbConnection = new SqlConnection("Data Source=ProductHost;Initial Catalog=dbProduct;Integrated Security=SSPI;Connection Timeout=60;Min Pool Size=2;Max Pool Size=20;"))
            {
                dbConnection.Open();
                using (SqlBulkCopy s = new SqlBulkCopy(dbConnection))
                {
                    s.DestinationTableName = prodSalesData.TableName;
    
                    foreach (var column in prodSalesData.Columns)
                        s.ColumnMappings.Add(column.ToString(), column.ToString());
    
                    s.WriteToServer(prodSalesData);
                }
            }
        }
    }
    }
    

    请注意,默认情况下,它会锁定表直到完成,这意味着在该站点上工作的任何其他人都将无法写入同一个表。

    要解决这个问题,您可以设置SqlBulkCopy.BatchSize,但是您必须注意,如果您的导入失败,您有责任删除已经提交的行。

    【讨论】:

    • SqlBulkCopy +1,但您的示例仅涉及一个表,因此避免了外键约束问题和必须以正确的顺序插入数据。您能否扩展您的示例以展示如何处理 2 桌情况?
    • 对于 2 个表,只需先做 1,然后再做另一个。特别是如果您需要第一次导入的 ID。
    • 我建议您使用“set identity insert on”预分配您的 ID -“SET ATseed = IDENT_CURRENT(table)” - 插入 id = ATseed + 行数 - 删除插入的行 - 'set identity插入”。这样您就不需要读回所有值来获取 ids(可能是非连续的)。
    【解决方案2】:

    一些想法:

    1. 在整个批量插入过程中保持相同的连接打开。一开始就打开,完成后才关闭。

    2. 不要在每次循环迭代期间重新创建 SqlCommands。一开始就创建一次,然后只更新参数的值:cmd.Parameters["@x"].Value = …;

    3. 您正在通过插入单个记录的foreach 循环插入第二个表 (B)。您可以考虑用单个INSERT INTO TableB (x, y, z) SELECT x, y, z FROM @tvp 替换它,其中@tvptable-valued parameter。本质上,这意味着您可以填充例如一个DataTable,其中包含要插入第二个表的行,然后将DataTable 传递为@tvp。从 SQL Server 2008 开始,IIRC 支持 TVP。第一次设置其中一个需要一点研究。

      (我不太确定上述INSERT 语句是否真的有效,或者TVP 是否仅作为存储过程的参数(see e.g. this example)。)

    4. 比 #3 更进一步,将表 A 和 B 中的插入移动到 DB 存储过程中。该 SP 将具有进入表 A 的值作为参数,以及具有进入表 B 的记录的表值参数。

    【讨论】:

    • Stakx,它很有帮助。 1)我已经在做你提到的第一点了..让我知道是否需要从这个角度修改代码 2)相应地更改了代码 3)这是游戏规则改变者..它回到了 2 左右现在分钟。 4)你的意思是为表A中的每个插入执行一个存储过程??这会提高一些性能吗??
    【解决方案3】:

    您应该使用 SqlTransaction 或 TransactionScope 来确保在两个表中插入都成功。

    从表 A 中获取 Max(id)。 使用类似的方法在表 A 中插入记录:

    using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["SomeConnectionString"].ConnectionString))
        {
             connection.Open();
             SqlTransaction transaction = connection.BeginTransaction();
    
             using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
             {
                bulkCopy.BatchSize = 100;
                bulkCopy.DestinationTableName = "dbo.Person";
                try
                {
                    bulkCopy.WriteToServer(listPerson.AsDataTable());
                }
                catch (Exception)
                {
                    transaction.Rollback();
                    connection.Close();
                }
              }
    
              transaction.Commit();
        }
    

    然后将记录插入到表 B 中。 您将知道需要从哪个 ID 计算 ID,因为您在插入之前选择了 Max(id)。

    有关BulkInsert with minimum lines of code 的完整示例,请参阅本文。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-02-02
      • 1970-01-01
      • 1970-01-01
      • 2011-08-03
      • 1970-01-01
      • 2012-12-01
      • 1970-01-01
      • 2016-10-28
      相关资源
      最近更新 更多