2016-09-25 2 views
0

Я использую SqlBulkCopy для массового ввода записей в базу данных.SqlBulkCopy, дающий ошибку ограничения FOREIGN KEY

Ниже приведен код для этого. Меня беспокоит то, что когда я использую SqlBulkCopy, я получаю ошибку ограничения FOREIGN KEY и для точных записей, если я использую метод UNION ALL, он работает нормально. Что я здесь делаю неправильно?

public partial class Repository 
{ 
    public bool InsertResult(List<string> kitIds) 
    { 
     if (kitIds == null || kitIds.Count == 0) 
      return false; 

     using (var connection = (SqlConnection)_database.CreateConnection()) 
     { 
      connection.Open(); 

      using (var transaction = connection.BeginTransaction()) 
      { 
       try 
       { 
        using (SqlBulkCopy copy = new SqlBulkCopy(connection, SqlBulkCopyOptions.CheckConstraints, transaction)) 
        { 
         /*This works*/ 
         var sb = new StringBuilder(2048); 
         sb.AppendLine("INSERT INTO KITSTATUSES (KitId, StatusId, IsActiveStatus) "); 

         for (int i = 0; i < kitIds.Count; i++) 
         { 
          sb.AppendLine($"SELECT '{kitIds[i]}', {(int)StatusOfKit.ResultUploaded}, 1"); 
          sb.AppendLine("UNION ALL "); 
         } 

         sb.Remove(sb.Length - 12, 12); 
         connection.Execute(sb.ToString(), null, transaction); 

         /*DOES NOT WORK and throws error: 
         * The INSERT statement conflicted with the FOREIGN KEY constraint "FK_KitStatuses_Kits". The conflict occurred in database "GeneBlueprint", table "dbo.Kits", column 'KitId'. 
         * The statement has been terminated. 
         var kitStatuses = kitIds.Select(k => new KitStatus { KitId = k, IsActiveStatus = true, StatusId = (int)StatusOfKit.ResultUploaded }).ToList(); 
         using (var reader = ObjectReader.Create(kitStatuses, "KitId", "StatusId", "IsActiveStatus")) 
         { 
          //Verify that reader has right values 
          //while (reader.Read()) 
          //{ 
          // Debug.WriteLine($"KitId: {reader.GetFieldValue<string>(0)}, StatusId: {reader.GetFieldValue<int>(1)}, IActiveStatus: {reader.GetFieldValue<bool>(2)}, StatusDate: {reader.GetFieldValue<DateTime>(3)}"); 
          //} 
          copy.DestinationTableName = "KitStatuses"; 
          copy.WriteToServer(reader); 
         } 
         */ 

         /*DOES NOT WORK and throws error 
         * The INSERT statement conflicted with the FOREIGN KEY constraint "FK_KitStatuses_Kits". The conflict occurred in database "GeneBlueprint", table "dbo.Kits", column 'KitId'. 
         * The statement has been terminated. 
         DataTable dt = new DataTable(); 
         dt.Columns.Add(new DataColumn() { ColumnName = "KitId", DataType = typeof(string), MaxLength = 15, AllowDBNull = false, AutoIncrement = false }); 
         dt.Columns.Add(new DataColumn() { ColumnName = "StatusId", DataType = typeof(int), AllowDBNull = false, AutoIncrement = false, DefaultValue = 8 }); 
         dt.Columns.Add(new DataColumn() { ColumnName = "IsActiveStatus", DataType = typeof(bool), AllowDBNull = false, AutoIncrement = false, DefaultValue = true }); 
         for (int i = 0; i < kitIds.Count; i++) 
         { 
          var row = dt.NewRow(); 
          row[0] = kitIds[i]; 
          row[1] = 8; 
          row[2] = true; 
          dt.Rows.Add(row); 
         } 

         using (var reader = ObjectReader.Create(kitStatuses, "KitId", "StatusId", "IsActiveStatus", "StatusDate")) 
         { 
          copy.DestinationTableName = "KitStatuses"; 
          copy.WriteToServer(dt); 
         } 
         */ 
        } 

        transaction.Commit(); 
       } 
       catch (Exception) 
       { 
        transaction.Rollback(); 
        throw; 
       } 
      } 
     } 

     return true; 
    } 
} 

Ниже скрипт для таблиц, имеющих отношение к данному вопросу:

CREATE TABLE [dbo].[Kits] 
(
    [KitId] [nvarchar](15) NOT NULL, 
    CONSTRAINT [PK_Kits] PRIMARY KEY CLUSTERED ([KitId] ASC) 
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
        IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
        ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
GO 

CREATE TABLE [dbo].[KitStatuses] 
(
    [KitStatusId] [int] IDENTITY(1,1) NOT NULL, 
    [KitId] [nvarchar](15) NOT NULL, 
    [StatusId] [int] NOT NULL, 
    [StatusDate] [datetime] NOT NULL, 
    [IsActiveStatus] [bit] NOT NULL, 

    CONSTRAINT [PK_KitStatuses] PRIMARY KEY CLUSTERED([KitStatusId] ASC) 
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
        IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
        ALLOW_PAGE_LOCKS = ON) ON [PRIMARY], 
    CONSTRAINT [IX_KitStatuses_KitId_KitStatus] 
     UNIQUE NONCLUSTERED ([KitId] ASC, [KitStatusId] ASC) 
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
        IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
        ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
GO 

CREATE TABLE [dbo].[Statuses] 
(
    [StatusId] [int] IDENTITY(1,1) NOT NULL, 
    [StatusName] [nvarchar](50) NOT NULL, 

    CONSTRAINT [PK_Statuses] PRIMARY KEY CLUSTERED ([StatusId] ASC) 
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
         IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, 
         ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
GO 

ALTER TABLE [dbo].[KitStatuses] 
    ADD CONSTRAINT [DF_KitStatuses_StatusDate] 
     DEFAULT (getdate()) FOR [StatusDate] 

ALTER TABLE [dbo].[KitStatuses] 
    ADD CONSTRAINT [DF_KitStatuses_IsActiveStatus] 
     DEFAULT ((1)) FOR [IsActiveStatus] 

ALTER TABLE [dbo].[KitStatuses] WITH CHECK 
    ADD CONSTRAINT [FK_KitStatuses_Kits] 
     FOREIGN KEY([KitId]) REFERENCES [dbo].[Kits] ([KitId]) 

ALTER TABLE [dbo].[KitStatuses] CHECK CONSTRAINT [FK_KitStatuses_Kits] 

ALTER TABLE [dbo].[KitStatuses] WITH CHECK 
    ADD CONSTRAINT [FK_KitStatuses_Statuses] 
     FOREIGN KEY([StatusId]) REFERENCES [dbo].[Statuses] ([StatusId]) 

ALTER TABLE [dbo].[KitStatuses] CHECK CONSTRAINT [FK_KitStatuses_Statuses] 

Я использую ниже технологий:

  • блок доступа к данным Enterprise Library для подключения к БД.
  • Fast member преобразовать список в DataReader

ответ

1

Проблема вызвана тем, что SqlBulkCopy вставка:

  • KitId в колонке StatusId
  • StatusId в колонке KitId

AutoMapping не очень умный ...

Во-первых, все столбцы сопоставляются по порядковому:

  • DataColumn 0 (KitId) отображается на TableColumn 0 (KitStatusId)
  • DataColumn 1 (StatusId) отображается на TableColumn1 (KitIt)
  • DataColumn 2 (IsActiveStatus) сопоставляется с TableColumn2 (StatusId)

SqlBulkCopy Код

internal void CreateDefaultMapping(int columnCount) 
{ 
    for (int index = 0; index < columnCount; ++index) 
    this.InnerList.Add((object) new SqlBulkCopyColumnMapping(index, index)); 
} 

Во-вторых, поскольку KitStatusId является идентификатором, AutoMapping попытается сопоставить следующий доступный столбец, и поскольку IsActiveStatus не соответствует типу StatusId, AutoMapping сопоставляет KitId с столбцом StatusId.

Если вы посмотрите на SQL сгенерированного, вы увидите IsActiveStatus не отображается

insert bulk KitStatuses ([KitId] NVarChar(15) COLLATE SQL_Latin1_General_CP1_CI_AS, [StatusId] Int) with (CHECK_CONSTRAINTS) 

Короче говоря, никогда не доверяйте Автоотображению это приведет лишь к какой-то ошибке.

Карта вашей колонки явно вместо

copy.ColumnMappings.Add("KitId", "KitId"); 
copy.ColumnMappings.Add("StatusId", "StatusId"); 
copy.ColumnMappings.Add("IsActiveStatus", "IsActiveStatus"); 
+0

Я никогда не новый, что может быть проблемой. После добавления сопоставлений он просто сработал. – ndd

+0

Я также был очень удивлен, когда вы пытаетесь использовать свой сценарий. Я всегда, хотя столбец идентичности был проигнорирован, и все сопоставления были перенесены в следующий столбец, который должен был бы иметь большой смысл. Но когда я попробовал свой код, только первый столбец был перенесен в последний столбец. –

Смежные вопросы