.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つの考えられる理由は次のとおりです。
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
を返します_""
_。空は多くの文字列のようなフィールドで有効ですが、有効な数値になることはありません。
フィールドに値を割り当てるための条件は、この特定の列の時間の正確にヒットしたパーセントです。したがって、何も設定されていません。
簡単に言えば、やりたいことはできません。 BulkCopyがデフォルト値でどのように機能するかについての最良のリファレンスは、Rutzkyによる This Answer です。
問題は、BulkCopyがターゲットデータベースにクエリを実行し、テーブルの構造を決定するステップを含むことです。ターゲット列がNOT NULL
ableであると判断し、nullまたはDBNullを渡した場合、データを渡そうとする前に例外がスローされます。
SQLプロファイラーを使用する場合、BCP呼び出しは表示されますが、データは表示されません(データが表示されることはありません)。表示されるのは、列リストとフラグを定義するための呼び出しだけです。
BulkCopyが最終的にデータを渡すことを決定したとき。列が存在し、フィールドがNULL
ableで、値がDBNull.Valueであり、列にデフォルト値がある場合。一括コピーは、基本的にその列にDEFAULT
フラグを渡します。ただし、フィールドがNOT NULL
ableである場合を除いて、デフォルト値を使用せず、代わりに例外をスローするように、いくつかの決定が行われました。
私の知る限り、これはマイクロソフトによるバグまたは見落としです。
他のいくつかの回答が述べているように、一般的な回避策は、コード内の値を計算して手動でこれらの値を処理することです。もちろん、デフォルト値を計算する場合、DBAがフィールドの実際のSQLデフォルト値を変更すると、システムが一致しなくなります。次のステップは、システムにサブシステムを追加することです。これは、ヒットしているSQL Serverから現在指定されているデフォルト値をクエリまたは追跡、またはキャッシュし、それらを割り当てます。これは、必要以上に多くの作業です。
TLDR:やりたいことはできません。しかし、他の人が指定した次善の回避策があります。
10万件のレコードのうち、おそらく20件にはデータがあります。したがって、私の500のバッチでは、DateAddedフィールドにデータがない場合もあれば、1つまたは2つのデータがある場合もあります。
DateAddedがnullを認識しないように構成されているのに対し、DateAddedに一部のレコードに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
}
次に、すべての列の処理が完了したら、新しい行をデータテーブルに追加し、完成したデータテーブルをサーバーにコピーします。
この組み合わせはSqlBulkCopyでは機能しません。
この組み合わせは機能します:
テーブルの日付フィールドを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