C# 列不允许DBNull.Value-无KeepNulls-正确的列映射

C# 列不允许DBNull.Value-无KeepNulls-正确的列映射,c#,sql-server,datatable,C#,Sql Server,Datatable,我正在使用c#with.NET 4.5.2,并将其推到SQL Server 2017 14.0.1000.169 在我的数据库中,我有一个带有DateAdded字段的表,类型为DateTimeOffset 我正在尝试使用以下代码进行批量复制: private Maybe BulkCopy(SqlSchemaTable table, System.Data.DataTable dt, bool identityInsertOn) { try { var option

我正在使用c#with.NET 4.5.2,并将其推到SQL Server 2017 14.0.1000.169

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

我正在尝试使用以下代码进行批量复制:

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();
}
IsNull()
如果值为
null
或字符串为
“null”
,则返回
TRUE
,这是我的业务需求所必需的

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


指定字段值的条件正好命中此特定列时间的0%。因此未设置任何内容。

此组合不适用于SqlBulkCopy:

  • SQL表中的日期字段配置为非空
  • 您的C#DataTable中有未设置日期值的记录(或有设置值,但它们不是实际的日期/时间值)
  • 这种组合确实有效:

  • SQL表中的日期字段配置为允许空值,但使用默认值
  • 您的C#DataTable具有未设置日期值的记录(当C#DataTable中未指定日期字段时,SQL将使用默认值)
  • 如果必须将表中的日期字段配置为不接受null,那么您还有更多的工作要做(创建一个接受null的暂存表,向暂存表中大容量插入,调用您编写的存储过程,从暂存表插入到常规表中)

    我认为你要寻找的最简单的途径是:

    您的表应允许日期字段为NULL,但默认值为:

    CREATE TABLE [dbo].[MyTable](
        [Field1] [varchar](50) NULL,
        [MyDateField] [datetime] NULL,
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[MyTable] ADD  CONSTRAINT [DF_Table_1_MyDateField] DEFAULT (getdate()) FOR [MyDateField]
    GO
    
    然后在C#中:

    在100000条记录中,可能有20条有数据。所以在我的批次中 在500个字段中,有时在DateAdded字段中没有数据,有时是一个 一两个有数据

    我猜您的一些记录在DateAdded中有空值,而DateAdded被配置为不接受空值

    要克服此问题,您有多种方法:

  • (更简单的一个)只是将列DateAdded更改为接受null
  • 将默认值指定给列DateAdded(来自MSSQL)
  • 管理代码中记录的空值
  • 如果您没有权限或您的工作要求,DateAdded不能接受空值,或者具有MSSQL或1&2中的默认值,则无法解决您的问题。 然后,在将每个批复制到服务器之前,可以管理每个批上DateAdded列上的空值

    在这种情况下,在您的代码中,当您填充数据时,您应该向该列添加一个条件,如下所示:

    if(field.ColumnName == "DateAdded")
    {
        // Do something to handle this column if it's null 
    
        // Set a default value 
        DateTimeOffset dtoffset = DateTimeOffset.Parse("2019-01-01 00:00:00.0000000 -00:00", CultureInfo.InvariantCulture); // change it to the required offset 
        string defaultDateAdded = dtoffset.ToString("yyyy-MM-dd HH:mm:ss.fffffff zzz", CultureInfo.InvariantCulture);               
    
        // Add the default value 
        newRow[field.ColumnName] = defaultDateAdded;
    }
    else
    {
        // Do something to handle the rest of columns if they're null 
    
    }   
    

    然后,当处理完所有列后,只需将新行添加到数据表中,然后将最终确定的数据表复制到服务器。

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

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

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

    当BulkCopy最终决定传递数据时。如果该列存在,字段为
    NULL
    able,值为DBNull.value,该列有默认值;大容量复制基本上会为该列传递
    DEFAULT
    标志。但是做出了一些决定,除了字段为
    notnull
    able之外,这些条件不应该使用默认值,而是应该抛出一个异常

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

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


    TLDR:你不能做你想做的事。但也有其他人指定的次优解决方法。

    很可能DB中添加的DateAdd列被设置为不允许为null,并且几乎总是默认为getdate()。如果默认值设置正确,则不要尝试插入该列。@KeithL谢谢。这就是托尼的答案。我们试过了,但仍然有错误。我更新了我的问题以表明我们已经尝试过了。我仍然认为该表不允许在该列中插入null。换句话说,它需要一个值。是否尝试设置CheckConstraints SQLBulkCopy选项?如果排除该列,SQLBulkCopy似乎只能正确处理此问题。i、 e.此列的所有行都有一个默认值。这是SQLBulkCopy的源代码,我以前将其标记为答案,但现在已取消标记。我所做的是完全跳过该列的赋值,尽管该列仍然存在于DataTable定义中。这似乎是可行的,但事实证明,我并没有删除之前的测试代码,而这些代码实际上是在从DataTable定义中删除列。因此,在解决问题时,这是一个假阳性。我会花一些时间来研究FireTriggers,如果这有助于解决问题,我会重新应用它作为答案
            DataTable dt = new DataTable("dbo.MyTable");
    
            dt.Columns.Add("Field1");
            dt.Columns.Add("MyDateField");
    
            DataRow row1 = dt.NewRow();
            row1["Field1"] = "test row 1";
            row1["MyDateField"] = DateTime.Now; //specify a value for the date field in C#
            dt.Rows.Add(row1);
    
            DataRow row2 = dt.NewRow();
            row2["Field1"] = "test row 2";
            //do not specify a value for the date field - SQL will use the default value
            dt.Rows.Add(row2);
    
            do the bulk copy
    
    if(field.ColumnName == "DateAdded")
    {
        // Do something to handle this column if it's null 
    
        // Set a default value 
        DateTimeOffset dtoffset = DateTimeOffset.Parse("2019-01-01 00:00:00.0000000 -00:00", CultureInfo.InvariantCulture); // change it to the required offset 
        string defaultDateAdded = dtoffset.ToString("yyyy-MM-dd HH:mm:ss.fffffff zzz", CultureInfo.InvariantCulture);               
    
        // Add the default value 
        newRow[field.ColumnName] = defaultDateAdded;
    }
    else
    {
        // Do something to handle the rest of columns if they're null 
    
    }