SqlBulkCopy给出FOREIGN KEY约束错误

时间:2016-09-25 07:11:15

标签: sqlbulkcopy sql-server-2016

我正在使用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]

我正在使用以下技术:

  • 企业库的数据访问数据访问块。
  • Fast member将List转换为DataReader

1 个答案:

答案 0 :(得分:1)

问题是由于SqlBulkCopy插入:

引起的
  • 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");