web-dev-qa-db-ja.com

列はDBNull.Valueを許可しません-KeepNullsなし-適切な列マッピング

.NET 4.5.2でc#を使用して、SQL Server 2017 14.0.1000.169にプッシュしています

私のデータベースには、DateTimeOffsetタイプの、DateAddedフィールドを持つテーブルがあります。

次のコードで一括コピーを試みています:

_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_エラーについて私が知っている2つの考えられる理由は次のとおりです。

  1. 列の順序が間違っています。列の順序をデータベースの順序と同じにするか、列マッピングを実行することで解決します。
  2. KeepNullsが有効で、_DBNull.Value_(またはnull?)がDataTableに設定されています。

しかし、私は正しくマッピングしており、KeepNullを設定していません。

それでも私はエラーを受け取っています:

列DateAddedはDBNull.Valueを許可しません

[〜#〜] edit [〜#〜]null、_DBNull.Value_、DefaultValue...など、何も設定しないでみました。文字通りその列をまったく設定しないだけです。

また、DataTableからDateAdded列を削除すると、機能します。しかし、私はそれを望んでいません。 10万件のレコードのうち、おそらく20件にはデータがあります。したがって、私の500のバッチでは、DateAddedフィールドにデータがない場合もあれば、1つまたは2つのデータがある場合もあります。

したがって、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()は、フィールドが数値型または日付型で、値がnullまたは空の場合、TRUEを返します_""_。空は多くの文字列のようなフィールドで有効ですが、有効な数値になることはありません。

フィールドに値を割り当てるための条件は、この特定の列の時間の正確にヒットしたパーセントです。したがって、何も設定されていません。

10
Suamere

簡単に言えば、やりたいことはできません。 BulkCopyがデフォルト値でどのように機能するかについての最良のリファレンスは、Rutzkyによる This Answer です。

問題は、BulkCopyがターゲットデータベースにクエリを実行し、テーブルの構造を決定するステップを含むことです。ターゲット列がNOT NULLableであると判断し、nullまたはDBNullを渡した場合、データを渡そうとする前に例外がスローされます。

SQLプロファイラーを使用する場合、BCP呼び出しは表示されますが、データは表示されません(データが表示されることはありません)。表示されるのは、列リストとフラグを定義するための呼び出しだけです。

BulkCopyが最終的にデータを渡すことを決定したとき。列が存在し、フィールドがNULLableで、値がDBNull.Valueであり、列にデフォルト値がある場合。一括コピーは、基本的にその列にDEFAULTフラグを渡します。ただし、フィールドがNOT NULLableである場合を除いて、デフォルト値を使用せず、代わりに例外をスローするように、いくつかの決定が行われました。

私の知る限り、これはマイクロソフトによるバグまたは見落としです。

他のいくつかの回答が述べているように、一般的な回避策は、コード内の値を計算して手動でこれらの値を処理することです。もちろん、デフォルト値を計算する場合、DBAがフィールドの実際のSQLデフォルト値を変更すると、システムが一致しなくなります。次のステップは、システムにサブシステムを追加することです。これは、ヒットしているSQL Serverから現在指定されているデフォルト値をクエリまたは追跡、またはキャッシュし、それらを割り当てます。これは、必要以上に多くの作業です。

TLDR:やりたいことはできません。しかし、他の人が指定した次善の回避策があります。

4
MasterHman

10万件のレコードのうち、おそらく20件にはデータがあります。したがって、私の500のバッチでは、DateAddedフィールドにデータがない場合もあれば、1つまたは2つのデータがある場合もあります。

DateAddedがnullを認識しないように構成されているのに対し、DateAddedに一部のレコードにnullがあると思います。

これを克服するには、複数の方法があります。

  1. (簡単な方法)列のAddDateを変更してnullを受け入れるだけです。
  2. (MSSQLからの)列DateAddedにデフォルト値を割り当てます。
  3. コードからレコードのnullを管理します。

権限がない場合、または仕事の要求にDateAddedがnullを受け入れることができないか、MSSQLのデフォルト値がないことが指定されている場合OR 1&2は問題を解決しませんでした。次に、サーバーにコピーする前に、各バッチのDateAdded列でnull。

この場合、コードで、データを入力している間に、次のような条件をこの列に追加する必要があります。

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 

}   

次に、すべての列の処理が完了したら、新しい行をデータテーブルに追加し、完成したデータテーブルをサーバーにコピーします。

1
iSR5

この組み合わせはSqlBulkCopyでは機能しません。

  1. SQLテーブルの日付フィールドがnull以外として構成されています。
  2. C#DataTableに未設定の日付値を持つレコードがあります(または設定値がありますが、実際の日付/時刻値ではありません)。

この組み合わせは機能します:

  1. SQLテーブルの日付フィールドは、NULLを許可するように構成されていますが、デフォルト値を使用しています。
  2. 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#で:

        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
1
toni