簡體   English   中英

從C#列表批量插入SQL Server到具有外鍵約束的多個表中

[英]Bulk insert from C# list into SQL Server into multiple tables with foreign key constraints

我對這個問題一無所知,我們將不勝感激:

我有兩個表,一個是主數據表( Table A ),另一個表( Table B )與Table A一個條目具有多個條目(具體為18)具有外鍵關系。

我正在列表中獲取數據,並希望將其插入SQL Server數據庫中。

我目前正在使用以下模式,但要花費14分鍾的時間Table A插入100行,並在Table B插入相應的18 * 100行。

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();

請提出我是否可以通過任何方式提高此代碼的性能。 還是他們有更好/更快的方法?

一些想法:

  1. 在整個批次插入過程中,保持同一連接打開。 首先將其打開,然后在完成后將其關閉。

  2. 不要在每次循環迭代期間重新創建SqlCommand 首先創建一次,然后僅更新參數的值: cmd.Parameters["@x"].Value = …;

  3. 您正在通過插入單個記錄的foreach循環插入第二個表(B)。 您可以考慮將其替換為單個INSERT INTO TableB (x, y, z) SELECT x, y, z FROM @tvp ,其中@tvp是一個表值參數 從本質上講,這意味着您可以用要插入到第二個表中的行來填充例如DataTable ,然后將該DataTable表作為@tvp 從SQL Server 2008開始,IIRC支持TVP。 設置其中一個需要第一次學習。

    (我不確定上面的INSERT語句是否會真正起作用,或者TVP是否僅作為存儲過程的參數起作用( 例如,參見此示例 )。)

  4. 比#3更進一步,將插入到表A和B中的內容移動到DB存儲過程中。 此SP將具有進入表A的值作為參數,以及具有進入表B的記錄的表值參數。

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 ,但是隨后您必須注意,如果導入失敗,則有責任刪除已經提交的行。

您應該使用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的完整示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM