Обновление: вот мое решение
У меня есть таблица, определяемая как:
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
с столбцами, которые соответствуют именам столбцов таблицы и типам данных. DataTable
заполняется DBNull.Value
в CreatedOn
, LastUpdatedOn
и LastUpdatedUser
. ReferenceID
уже создан. Когда я вызываю следующий код, я получаю ошибку ниже.
Код:
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);
Ошибка:
Ошибка при попытке таблицы BulkCopy csvrf_References
System.InvalidOperationException: Column 'CreatedOn' не позволяет DBNull.Value.
в System.Data.SqlClient.SqlBulkCopy.ConvertValue (значение объекта, метаданные _SqlMetaData, Boolean isNull, Boolean & isSqlType, Boolean & coercedToDataFeed)
Я посмотрел на все, и я не могу найти ответа на это. Класс SqlBulkCopy
похоже, не соблюдает значения по умолчанию, даже если он говорит, что это так. Что я здесь делаю неправильно?
Для части 1 «поле, которое не является NULL с DEFAULT», вы не должны отправлять поле в первую очередь. Он не должен отображаться. Нет необходимости изменять это поле, чтобы принимать NULL только для этого.
Для части 2 «поле с NULL с DEFAULT», которое будет работать, чтобы получить значение по умолчанию при передаче в DbNull.Value, если у вас нет SqlBulkCopyOptions, установленного в KeepNulls
, иначе он будет вставлять фактическую базу данных NULL
.
Поскольку существует некоторая путаница в SqlBulkCopyOption KeepNulls
, давайте рассмотрим его определение:
Сохранять нулевые значения в таблице адресатов независимо от настроек для значений по умолчанию. Если не указано, нулевые значения заменяются значениями по умолчанию, где это применимо.
Это означает, что DataColumn, установленный в DbNull.Value
будет вставлен как NULL
базы данных, даже если столбец имеет DEFAULT CONSTRAINT, если KeepNulls
параметр KeepNulls
. Он не указан в вашем коде. Это приводит ко второй части, в DbNull.Value
значения DbNull.Value
заменяются «значениями по умолчанию», где это применимо. Здесь «применимо» означает, что столбец имеет DEFAULT CONSTRAINT, определенный на нем. Следовательно, когда существует DEFAULT CONSTRAINT, значение DbNull.Value
будет отправлено как есть, пока DbNull.Value
должен перевести на ключевое слово SQL DEFAULT
. Это ключевое слово интерпретируется в инструкции INSERT как принимающее значение ограничения DEFAULT. Конечно, также возможно, что SqlBulkCopy
, если выдавать отдельные инструкции INSERT, может просто оставить это поле вне списка столбцов, если для этой строки установлено значение NULL, которое будет получать значение по умолчанию. В любом случае конечный результат заключается в том, что он работает так, как вы ожидали. И мое тестирование показывает, что оно действительно работает таким образом.
Чтобы было ясно о различии:
Если для поля в базе данных установлено значение NOT NULL
и на нем задан КОНТРАКТ ПО УМОЛЧАНИЮ, то ваши параметры:
Перейдите в поле (то есть он не будет подобрать значение по умолчанию), в этом случае он не может быть установлен в DbNull.Value
Не проходите в поле вообще (то есть оно будет получать значение DEFAULT), которое может быть выполнено либо:
Не иметь его в DataTable или запросе или DataReader или что-то, что отправляется в качестве источника, и в этом случае вам может не потребоваться вообще указать коллекцию ColumnMappings
Если поле находится в источнике, то вы должны указать коллекцию ColumnMappings
чтобы вы могли оставить это поле вне сопоставлений.
Устанавливая или не устанавливая, KeepNulls
не изменяет вышеуказанное поведение.
Если для поля в базе данных установлено NULL
и на нем задано КОНСТРУКЦИЯ ПО УМОЛЧАНИЮ, то ваши параметры:
Не проходите в поле вообще (то есть оно будет получать значение DEFAULT), которое может быть выполнено либо:
Не иметь его в DataTable или запросе или DataReader или что-то, что отправляется в качестве источника, и в этом случае вам может не потребоваться вообще указать коллекцию ColumnMappings
Если поле находится в источнике, то вы должны указать коллекцию ColumnMappings
чтобы вы могли оставить это поле вне сопоставлений.
Передайте в поле значение не DbNull.Value
, и в этом случае оно будет установлено на это значение и не будет получать значение DEFAULT
DbNull.Value
в поле DbNull.Value
, и в этом случае эффект определяется тем, SqlBulkCopyOptions
ли SqlBulkCopyOptions
или установлено значение KeepNulls
:
KeepNulls
не установлен, KeepNulls
значение DEFAULT
KeepNulls
будет установлен в поле 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» из-за того, что исходная и целевая таблицы имеют разный порядок столбцов.