使用 SqlBulkCopy 时未(始终)强制执行约束

Posted

技术标签:

【中文标题】使用 SqlBulkCopy 时未(始终)强制执行约束【英文标题】:Constraints not being enforced (consistently) while using SqlBulkCopy 【发布时间】:2014-03-04 00:08:02 【问题描述】:

在我们的数据库表中,我们使用两个唯一的非聚集索引来创建跨四个字段的唯一约束。我们使用两个,因为其中一个字段,ZipCode 是一个可为空的字段。如果表中存在一条记录,该记录具有 ZipCodenull 条目,我们不希望出现新记录与其他三个字段匹配但已定义 ZipCode 并被添加(反之亦然)的情况。

问题是似乎使用SqlBulkCopy 没有强制执行任何约束,因为您可以添加任何您喜欢的记录,而不管表上已有什么。

在另一个程序中,我们使用Entity Framework,因为我们加载的数据量要少得多。使用 EF,这些约束可以完美地工作(它们目前正在生产中)。但是,使用 SqlBulkCopy 似乎它们被完全忽略了。

T-SQL

CREATE UNIQUE NONCLUSTERED INDEX [UQ_ChannelStateEndDateZipCodeNOTNULL] ON [dbo].[ZipCodeTerritory]
(
    [ChannelCode] ASC,
    [StateCode] ASC,
    [ZipCode] ASC,
    [EndDate] ASC
)
WHERE ([ZipCode] IS NOT NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [UQ_ChannelStateEndDateZipCodeISNULL] ON [dbo].[ZipCodeTerritory]
(
    [ChannelCode] ASC,
    [StateCode] ASC,
    [ZipCode] ASC,
    [EndDate] ASC
)
WHERE ([ZipCode] IS NULL)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
GO

C#

Dictionary<DataRow, string> faultyRows = new Dictionary<DataRow, string>();
using (SqlConnection connection = new SqlConnection(connString))

    //Open Database connection
    connection.Open();

    //Create transaction objects
    SqlTransaction transaction = connection.BeginTransaction();
    SqlBulkCopy bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.CheckConstraints, transaction);
    bulkCopy.DestinationTableName = "ZipCodeTerritory";

    //Load data and initialize datatable
    DataTable dataTable = LoadData(inserts);
    IDataReader reader = dataTable.CreateDataReader();
    DataTable dataSchema = reader.GetSchemaTable();
    DataTable tmpDataTable = InitializeStructure();

    //Create array to hold data being transfered into tmpDataTable
    object[] values = new object[reader.FieldCount];

    while (reader.Read())
    
        //Clear temp table for single-record use
        tmpDataTable.Rows.Clear();

        //Get data for current row
        reader.GetValues(values);

        //Load values into the temp table
        tmpDataTable.LoadDataRow(values, true);

        //Load one record at a time
        try
        
            bulkCopy.WriteToServer(tmpDataTable);
            transaction.Commit();
        
        catch (Exception ex)
        
            faultyRows.Add(tmpDataTable.Rows[0], ex.Message);
        
    

编辑

我发现如果定义了ZipCode 字段,则实际上会强制执行此约束。 ChannelCodeStateCodeZipCodeEndDate 字段的任何匹配都将导致 SqlException 具有以下 Message 属性(从我刚刚运行的特定文本中提取):

Cannot insert duplicate key row in object 'dbo.ZipCodeTerritory' with unique index 'UQ_ChannelStateEndDateZipCodeNOTNULL'. The duplicate key value is (9, WA , 98102 , 9999-12-31)

但是,这是我唯一一次可以触发我们的两个约束之一。

【问题讨论】:

切换到bcp命令可行吗?如果您可以使用它,您可以将CHECK_CONSTRAINTS 选项放在它上面,否则它将忽略约束,就像SqlBulkCopy SqlBulkCopy 有疯狂的(!)默认值。见副本。例如,它将禁用现有的外键。这是一个 DDL 操作,在没有警告的情况下隐式执行。 @SaUce: 怎么重复??我在构造函数中设置了SqlBulkCopyOptions,与该问题的答案相同。我们也没有任何触发器。 @SaUce 我也不确定“切换到bcp 命令”是什么意思 我删除了重复的标志,起初我没有注意到 SqlBulkCopyOptions 必须向右滚动。通过切换,我的意思是编写将执行 BCP 命令的 SP,您需要做的就是传递所需的属性。 【参考方案1】:

这是由于我使用将数据从模型对象(我仍在某些地方使用实体框架)传输到DataRow 的方法而发生的。下面是我现在使用的方法。在我修复之前,ZipCode 字段在 null 时被作为空字符串放入 DataRow 列。由于这在技术上不是空字段,因此没有触发约束。

    private static DataTable LoadData(List<ZipCodeTerritory>zipCodeList, bool update = false)
    
        DataTable dataTable = InitializeStructure();

        foreach (var zipcode in zipCodeList)
        
            DataRow row = dataTable.NewRow();

            try
            
                row[0] = zipcode.ChannelCode.Trim();
                row[1] = zipcode.DrmTerrDesc.Trim();
                row[2] = zipcode.IndDistrnId.Trim();
                row[3] = zipcode.StateCode.Trim();
                row[4] = (string.IsNullOrWhiteSpace(zipcode.ZipCode) ? null : zipcode.ZipCode.Trim());
                row[5] = zipcode.EndDate.Date;
                row[6] = zipcode.EffectiveDate.Date;
                row[7] = zipcode.LastUpdateId;
                row[8] = DateTime.Now.Date;
                row[10] = zipcode.ErrorCodes;
                row[11] = zipcode.Status;

                //Add the Id column if we're doing an update
                if(update) row[9] = zipcode.Id;
            
            catch (Exception ex)
            

            

            dataTable.Rows.Add(row);
        

        return dataTable;
    

【讨论】:

以上是关于使用 SqlBulkCopy 时未(始终)强制执行约束的主要内容,如果未能解决你的问题,请参考以下文章

如何强制实体框架始终从数据库中获取更新数据?

排除 SqlBulkCopy 未执行最少日志记录的问题

有啥方法可以强制在 iOS13 的入职过程中出现“始终允许”提示?

SqlBulkCopy批量插入数据时,不执行触发器和约束的解决方法

C#使用SQLBulkCopy或等效库高效批量删除50000条记录

使用 IDatareader 和 SqlBulkCopy 将 Dictionary 元素作为行插入 SQL 表