SqlBulkCopy 给出 FOREIGN KEY 约束错误

SqlBulkCopy giving FOREIGN KEY constraint error

我正在使用 SqlBulkCopy 将记录批量插入数据库。

下面是它的代码。困扰我的是,当我使用 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]

我正在使用以下技术:

问题是因为 SqlBulkCopy insert:

  • StatusId 列中的 KitId
  • KitId 列中的 StatusId

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)

简而言之,永远不要相信 AutoMapping,这只会导致一些错误。

改为显式映射您的列

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