【问题标题】:SqlBulkCopy into table that Default column values fails when source DataTable row has DBNull.Value当源 DataTable 行具有 DBNull.Value 时,SqlBulkCopy 到默认列值失败的表中
【发布时间】:2014-11-19 21:08:24
【问题描述】:

更新:Here is my solution

我有一个表定义为:

CREATE TABLE [dbo].[csvrf_References]
(
    [Ident] [int] IDENTITY(1,1) NOT NULL,
    [ReferenceID] [uniqueidentifier] NOT NULL DEFAULT (newsequentialid()),
    [Type] [nvarchar](255) NOT NULL,
    [Location] [nvarchar](1000) NULL,
    [Description] [nvarchar](2000) NULL,
    [CreatedOn] [datetime] NOT NULL DEFAULT (getdate()),
    [LastUpdatedOn] [datetime] NOT NULL DEFAULT (getdate()),
    [LastUpdatedUser] [nvarchar](100) NOT NULL DEFAULT (suser_sname()),

    CONSTRAINT [PK_References] PRIMARY KEY NONCLUSTERED ([ReferenceID] ASC)
) ON [PRIMARY]

我有一个DataTable,其中的列与表列名称和数据类型相匹配。 DataTableCreatedOnLastUpdatedOnLastUpdatedUser 中填写DBNull.ValueReferenceID 已经生成。当我调用以下代码时,出现以下错误。

代码:

SqlBulkCopy bulkCopy = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, bulkCopyTran);
bulkCopy.DestinationTableName = table.TableName;
bulkCopy.ColumnMappings.Clear();
foreach (DataColumn col in table.Columns) bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
bulkCopy.WriteToServer(table);

错误:

尝试批量复制表 csvrf_References 时出错
System.InvalidOperationException:列“CreatedOn”不允许 DBNull.Value。
在 System.Data.SqlClient.SqlBulkCopy.ConvertValue(对象值, _SqlMetaData 元数据, Boolean isNull, Boolean& isSqlType, Boolean& coercedToDataFeed)

我已经看遍了,我似乎无法找到答案。 SqlBulkCopy 类似乎不尊重默认值,即使它说它确实如此。我在这里做错了什么?

【问题讨论】:

    标签: c# sql-server null sqlbulkcopy default-constraint


    【解决方案1】:

    对于第 1 部分,“具有默认值的非 NULL 字段”,您不应该首先发送该字段。它不应该被映射。无需为此更改该字段以接受 NULL。

    对于第 2 部分,“默认值为 NULL 的字段”,只要您没有设置 SqlBulkCopyOptions,在传入 DbNull.Value 时获取默认值到KeepNulls,否则它将插入一个实际的数据库NULL

    由于对KeepNulls的SqlBulkCopyOption有些混淆,我们来看看它的定义:

    无论默认值的设置如何,都在目标表中保留空值。如果未指定,则 null 值将在适用的情况下替换为默认值。

    这意味着设置为DbNull.Value 的DataColumn 将作为数据库NULL 插入,即使该列具有默认约束,如果指定了KeepNulls 选项。它没有在您的代码中指定。这导致第二部分说DbNull.Value 值在适用的情况下被“默认值”替换。这里的“适用”意味着该列上定义了一个默认约束。因此,当存在默认约束时,非DbNull.Value 值将按原样发送,而DbNull.Value 应该 转换为SQL 关键字DEFAULT。该关键字在 INSERT 语句中被解释为采用 DEFAULT 约束的值。当然,SqlBulkCopy 也有可能,如果发出单独的 INSERT 语句,如果将该行设置为 NULL,则可以简单地将该字段排除在列列表之外,这将获取默认值。无论哪种情况,最终结果都是它按您的预期工作。而且我的测试表明它确实以这种方式工作。

    要明确区别:

    • 如果数据库中的某个字段设置为 NOT NULL 并在其上定义了 DEFAULT CONSTRAINT,则您的选项是:

      • 在字段中传递(即它不会获取默认值),在这种情况下它永远不能设置为DbNull.Value

      • 根本不传入该字段(即它获取默认值),这可以通过以下任一方式完成:

        • 不要将它放在 DataTable 或查询或 DataReader 或任何发送的源中,在这种情况下,您可能根本不需要指定 ColumnMappings 集合

        • 如果该字段在源中,那么您必须指定 ColumnMappings 集合,以便您可以将该字段排除在映射之外。

      • 设置或不设置KeepNulls 不会改变上述行为。

    • 如果数据库中的某个字段设置为 NULL 并在其上定义了 DEFAULT CONSTRAINT,则您的选项是:

      • 根本不传入该字段(即它获取默认值),这可以通过以下任一方式完成:

        • 不要将它放在 DataTable 或查询或 DataReader 或任何发送的源中,在这种情况下,您可能根本不需要指定 ColumnMappings 集合

        • 如果该字段在源中,那么您必须指定 ColumnMappings 集合,以便您可以将该字段排除在映射之外。

      • 传入设置为非DbNull.Value的值的字段,在这种情况下,它将被设置为该值并且不获取DEFAULT值

      • DbNull.Value的形式传入该字段,这种情况下效果取决于SqlBulkCopyOptions是否被传入并已设置为KeepNulls

        • KeepNulls 设置将获取默认值

        • KeepNulls is set 会将字段设置为NULL


    下面是一个简单的测试,看看DEFAULT 关键字是如何工作的:

    --DROP TABLE ##DefaultTest;
    CREATE TABLE ##DefaultTest
    (
      Col1 INT,
      [CreatedOn] [datetime] NOT NULL DEFAULT (GETDATE()),
      [LastUpdatedOn] [datetime] NULL DEFAULT (GETDATE())
    );
    INSERT INTO ##DefaultTest (Col1, CreatedOn) VALUES (1, DEFAULT);
    INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (2, DEFAULT);
    INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (3, NULL);
    INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (4, '3333-11-22');
    
    SELECT * FROM ##DefaultTest ORDER BY Col1 ASC;
    

    结果:

    Col1   CreatedOn                  LastUpdatedOn
    1      2014-11-20 12:34:31.610    2014-11-20 12:34:31.610
    2      2014-11-20 12:34:31.610    2014-11-20 12:34:31.610
    3      2014-11-20 12:34:31.610    NULL
    4      2014-11-20 12:34:31.613    3333-11-22 00:00:00.000
    

    【讨论】:

    • 您是说您能够使用 SqlBulkCopy 使其工作,或者它应该工作,因为当相关字段存在默认约束时,DBNull.Value 应该转换为 Default?对我来说,使用 .NET 4.5 和 SQL Server 2012 我无法让 SqlBulkCopy 将 DBNull.Value 传递给具有默认约束的不可空字段。
    • @TimLentine:嘿。澄清一下,我是说 a) 我确实通过 SqlBulkCopy 让这个工作,并且 b) 如果你想选择,你不能指定一个标记为 NOT NULL 的字段默认值(我在第 1 部分中说过)。我提到转换为DEFAULT 的第 2 部分是关于 NULLable 字段,其中涵盖了 James 在评论中对您的回答关于“我将包含包含数据和空值的列的其他表”的评论。但是,是的,令人困惑的是,SqlBulkCopy 根据声明的字段 NULLNOT NULL 处理 DbNull.value 的方式不同。
    • @TimLentine :我刚刚用一个项目符号列表更新了我的答案,希望更清楚地映射各种场景。
    • 感谢您的列表,这有助于澄清您的观点。(我在想我错过了一些使它像他的问题中描述的 OP 那样工作的方法,但事实并非如此。)与 DBNull.Value 相关的行为确实很古怪。
    • 如果只有 MS 文档会直接说明这一点!谢谢你的解释。
    【解决方案2】:

    “SQLBulkCopy column does not allow DbNull.value”错误是由于源表和目标表的列顺序不同。

    【讨论】:

    • 在将数据从一个数据库表传输到另一个数据库表时出现此错误。正如@James Nix 总结的那样,根本原因是我们如何设置列的 default value 属性。谢谢。
    • 这是正确答案!我在 mysql 整数列转换中搜索问题,然后是我的代码和可能的空值等。但这是愚蠢的事情!谢谢
    【解决方案3】:

    阅读有关SqlBulkCopy,尤其是SqlBulkCopyOptions 的文档,我会得出与您所做的相同的结论:SQL Server 应该足够“聪明”,可以在适用的情况下使用默认约束,尤其是因为您没有使用@ 987654324@属性。

    但是,在这种情况下,我怀疑文档有细微的错误;如果不是错误的,那肯定是误导。

    正如您所观察到的,对于具有默认约束的不可空字段(在本例中为 GetDate()),SqlBulkCopy 会失败并出现上述错误。

    作为测试,尝试创建第二个表来模仿第一个表,但这次将CreatedOnLastUpdatedOn 字段设置为可为空。在我的测试中,使用默认选项 (SqlBulkCopyOptions.Default) 过程可以正常工作 并且 CreatedOnLastUpdatedOn 都在表中填充了正确的 DateTime 值,尽管 DataTable 的值这些字段是DBNull.Value

    作为另一个测试,使用相同的(可为空字段)表,执行 SqlBulkCopy 仅这次使用SqlBulkCopyOptions.KeepNulls 属性。我怀疑你会看到和我一样的结果,即CreatedOnLastUpdatedOn 在表格中都是空的。

    此行为类似于执行“普通”T-SQL 语句将数据插入表中。

    以原表(不可为空的字段)为例,如果执行

    INSERT INTO csvrf_References ([Type], [Location], [Description], [CreatedOn], [LastUpdatedOn], [LastUpdatedUser]) 
    VALUES ('test', 'test', 'test', null, null, null)
    

    您将收到关于表中不允许空值的类似错误。

    但是,如果您从语句中省略不可为空的字段,SQL Server 会对这些字段使用默认约束:

    INSERT INTO csvrf_References ([Type], [Location], [Description]
    VALUES ('test', 'test', 'still testing')
    

    基于此,我建议要么使表中的字段可为空(我认为这不是一个很好的选择),要么为 SqlBulkCopy 进程使用“暂存”表(其中字段可为空并且具有类似的默认值)约束到位)。一旦数据在临时表中,执行第二条语句将数据移动到实际的最终目标表中。

    【讨论】:

    • 是的,我希望 SQL Server 在提供空值​​时使用默认值填充列。请参阅:stackoverflow.com/a/4244132/3874334 此示例演示了我很可能始终保留为空的字段,但我还有其他表,其中我将包含包含数据和空值的列。
    • @JamesNix 这不是 SQL Server DEFAULTS 的工作方式,不管有没有 SqlBulkCopy。仅当列未指定时才应用默认值。如果为列指定 NULL,它将尝试将其设为 NULL(如果将其声明为 NOT NULL,则会导致错误),并且不会使用 DEFAULT 值。当文档说“... 将遵守任何默认值..”时,这意味着 SqlBulkCopy 将以与任何其他 SQL 命令相同的方式观察 DEFAULT。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-14
    • 2017-08-31
    • 1970-01-01
    • 2013-07-02
    • 2021-12-11
    • 2020-03-03
    • 1970-01-01
    相关资源
    最近更新 更多