Столбец не позволяет DBNull.Value - Нет KeepNulls - Правильные сопоставления столбцов - PullRequest
0 голосов
/ 09 января 2019

Я использую c # с .NET 4.5.2, отправляю на 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();
}

Я знаю две возможные причины ошибки does not allow DBNull:

  1. Столбцы расположены в неправильном порядке, что можно решить, разместив их в том же порядке, что и их порядковый номер в базе данных, или выполнив сопоставление столбцов.
  2. KeepNulls включен, и DBNull.Value (или null?) Установлены в DataTable.

Но я правильно сопоставляю карты и НЕ настраиваю KeepNulls.

Пока я получаю сообщение об ошибке:

Столбец DateAdded не допускает DBNull.Value

РЕДАКТИРОВАТЬ Я также пытался просто НЕ УСТАНОВИТЬ что-либо, включая null, DBNull.Value и DefaultValue ... просто буквально вообще не устанавливая этот столбец.

Кроме того, если я удаляю столбец DateAdded из DataTable, он работает. Но я этого не хочу. Возможно, из 100 000 записей 20 имеют данные. Поэтому в моих пакетах по 500 иногда ни один не имеет данных в поле 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);
}

IsNull() возвращает TRUE, если значение null или строка "null", как требуется для моих бизнес-требований.

IsEmptyDateOrNumber() вернет TRUE, если поле числового типа или типа даты, а значение равно нулю или пусто "". Поскольку пустое значение допустимо для многих строковых полей, оно никогда не является допустимым числовым значением.

Условие для присвоения полю значения задается ровно 0 процентов времени для этого конкретного столбца. Таким образом, ничего не установлено.

Ответы [ 3 ]

0 голосов
/ 13 января 2019

Из 100 000 записей, возможно, у 20 из них есть данные. Так в моих партиях из 500, иногда нет данных в поле DateAdded, иногда один или два имеют данные.

Я предполагаю, что некоторые из ваших записей имеют нулевые значения в DateAdded, в то время как DateAdded настроен так, чтобы не принимать нулевые значения.

Чтобы преодолеть это, у вас есть несколько способов сделать это:

  1. (проще) просто изменить столбец DateAdded для принятия нуля.
  2. Назначить значение по умолчанию для столбца DateAdded (из MSSQL).
  3. Управляйте нулями записей из вашего кода.

Если у вас нет разрешений или в вашем рабочем задании указано, что DateAdded не может принимать значения NULL или имеет значение по умолчанию из 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 

}   

затем, когда вы закончите обработку всех столбцов, вы просто добавляете новую строку в таблицу данных, а затем копируете готовую таблицу данных на сервер.

0 голосов
/ 16 января 2019

Проще говоря, вы не можете делать то, что вы хотите. Лучшая справка о том, как BulkCopy работает со значениями по умолчанию, - Этот ответ от Rutzky.

Проблема в том, что BulkCopy включает в себя этап, на котором он запрашивает целевую базу данных и определяет структуру таблицы. Если он определяет, что целевой столбец может NOT NULL, и вы передаете значение NULL или DBNull, он выдает исключение даже перед попыткой передачи данных.

Если вы используете SQL Profiler, вы увидите вызовы BCP, но не данные (в любом случае данные никогда не будут отображаться). Все, что вы увидите, это вызов для определения списка столбцов и флагов.

Когда BulkCopy наконец решает передать данные. Если столбец существует, и поле NULL способны, и значение равно DBNull.Value, и столбец имеет значение по умолчанию; Bulk Copy по существу передает флаг DEFAULT для этого столбца. Но было принято какое-то решение, что эти условия, за исключением случаев, когда поле NOT NULL способно, не должны использоваться значение по умолчанию и вместо этого должно быть выброшено исключение.

Насколько я могу судить, это ошибка или недосмотр со стороны Microsoft.

Обычный обходной путь, как утверждают некоторые другие ответы, состоит в том, чтобы просто обрабатывать эти значения вручную, вычисляя, какое значение должно быть в коде. Конечно, если вы вычислите значения по умолчанию, тогда администратор базы данных изменит фактическое значение SQL по умолчанию для поля, ваши системы не будут соответствовать. Следующим шагом является добавление в вашу систему подсистемы, которая запрашивает и / или отслеживает / кэширует заданные в настоящее время значения по умолчанию для используемого вами SQL Server, и назначает их. Это намного больше работы, чем нужно.

TLDR: Ты не можешь делать то, что хочешь. Но есть неоптимальные обходные пути, которые указали другие.

0 голосов
/ 09 января 2019

Эта комбинация не работает для SqlBulkCopy:

  1. Поле даты в таблице SQL настроено как ненулевое.
  2. Ваш C # DataTable содержит записи с неустановленными значениями даты (или имеет заданные значения, но они не являются фактическими значениями даты / времени).

Эта комбинация работает:

  1. Поле даты в таблице SQL сконфигурировано для разрешения пустых значений, но использует значение по умолчанию.
  2. В вашем C # DataTable есть записи с неустановленными значениями даты (SQL будет использовать значение по умолчанию, если поле даты не указано в C # DataTable).

Если поле даты в вашей таблице ДОЛЖНО быть настроено так, чтобы оно не принимало значение 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 #:

        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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...