列不允许DBNull.Value - No KeepNulls - 正确的列映射

Sua*_*ere 10 c# sql-server datatable

我在.NET 4.5.2中使用c#,推送到SQL Server 2017 14.0.1000.169

在我的数据库中,我有一个带有DateAdded字段的表,类型DateTimeOffset.

我正在尝试使用以下代码进行BulkCopy:

private Maybe BulkCopy(SqlSchemaTable table, System.Data.DataTable dt, bool identityInsertOn)
{
    try
    {
        var options = SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.FireTriggers | SqlBulkCopyOptions.UseInternalTransaction; //  | SqlBulkCopyOptions.CheckConstraints; // Tried CheckConstraints, but it didn't change anything.
        if (identityInsertOn) options |= SqlBulkCopyOptions.KeepIdentity;
        using (var conn = new SqlConnection(_connString))
        using (var bulkCopy = new SqlBulkCopy(conn, options, null))
        {
            bulkCopy.DestinationTableName = table.TableName;
            dt.Columns.Cast<System.Data.DataColumn>().ToList()
                .ForEach(x => bulkCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping(x.ColumnName, x.ColumnName)));

            try
            {
                conn.Open();
                bulkCopy.WriteToServer(dt);
            }
            catch (Exception ex)
            {
                return Maybe.Failure(ex);
            }
        }
    }
    catch (Exception ex)
    {
        return Maybe.Failure(ex);
    }

    return Maybe.Success();
}
Run Code Online (Sandbox Code Playgroud)

我知道does not allow DBNull错误的两个可能原因是:

  1. 列的顺序错误,可以通过将它们按照与数据库序数相同的顺序或通过执行列映射来解决.
  2. KeepNulls已启用,并且DBNull.Value(或null?)在DataTable中设置.

但我正确映射,并没有设置KeepNulls.

然而,我收到错误:

列DateAdded不允许DBNull.Value

编辑我也尝试了不设任何东西,包括null,DBNull.ValueDefaultValue...只是字面上没有设置该列在所有.

此外,如果我从DataTable中删除DateAdded列,它将工作.但我不希望这样.在100,000条记录中,可能有20条记录有数据.因此,在我的500个批次中,有时None在DateAdded字段中有数据,有时一个或两个有数据.

所以我想将列保留在我的DataTable中,但让它使用DefaultValue.

最后一点:我在将DataColumn的值设置为DBNull.Value与之间进行了交替dt.Columns[x.ColumnName].DefaultValue.两种方式都会产生相同的错误.

编辑2

这是我用来填充数据表中的数据的代码:

foreach (var column in table)
{
    System.Data.DataRow newRow = dt.NewRow();
    foreach (var field in column)
    {
        if (!IsNull(field.Value) && !IsEmptyDateOrNumber(field.ColumnType, field.Value))
        {
            // For DateAdded, this is not hit on the first batch, though there are columns Before and After DateAdded on the same row which do have value.
            // But it WILL be hit once or twice a few batches later.  So I don't want to completely remove the definition from the DataTable.
            newRow[field.ColumnName] = field.Value;
        }
        else
        {
            // newRow[field.ColumnName] = dt.Columns[field.ColumnName].DefaultValue;
            // newRow[field.ColumnName] = DBNull.Value;
            // dt.Columns[field.ColumnName].AllowDBNull = true;
        }
    }
    dt.Rows.Add(newRow);
}
Run Code Online (Sandbox Code Playgroud)

IsNull()TRUE如果值为null或字符串"null",则返回,这是我的业务要求所必需的.

IsEmptyDateOrNumber()TRUE如果字段是数字或日期类型,则返回,并且值为null或空"".因为虽然empty对于许多类似字符串的字段有效,但它永远不是有效的数值.

为该字段分配值的条件恰好是该特定列的0%.因此没有设置.

小智 5

简单地说,你不能做你想做的。关于 BulkCopy 如何使用默认值的最佳参考是 Rutzky 的这个答案

问题是,BulkCopy 包括一个步骤,它查询目标数据库并确定表的结构。如果它确定目标列NOT NULL能够,并且您正在传递 null 或 DBNull,它甚至会在尝试传递数据之前抛出异常。

如果您使用 SQL Profiler,您将看到 BCP 调用,但看不到数据(数据永远不会显示)。您将看到的只是定义列列表和标志的调用。

当 BulkCopy 最终决定传递数据时。如果该列存在,并且该字段是NULL能够,并且值为DBNull.Value,并且该列有一个默认值;批量复制实质上是传递该DEFAULT列的标志。但是做出了一些决定,使得这些条件,除非该字段NOT NULL能够,否则不应使用默认值,而应抛出异常。

据我所知,这是微软的一个错误或疏忽。

正如其他一些答案所述,常见的解决方法是通过计算代码中应该包含的值来手动处理这些值。当然,如果您计算默认值,那么 DBA 会更改字段的实际 SQL 默认值,您的系统将不匹配。下一步是向您的系统添加一个子系统,该子系统从您正在访问的 SQL Server 查询和/或跟踪/缓存当前指定的默认值,并分配这些值。这比应该需要的工作要多得多。

TLDR:你不能做你想做的。但是其他人已经指定了一些次优的解决方法。