我将C#与.NET 4.5.2一起使用,并推送到SQL Server 2017 14.0.1000.169
在我的数据库中,我有一个带有DateAdded字段的表,类型为DateTimeOffset
。
我正在尝试使用以下代码批量复制:
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
错误的两个可能原因是:
DBNull.Value
(或null
?)。但是我映射正确,从来没有设置KeepNulls。
但是我收到错误:
列DateAdded不允许使用DBNull.Value
编辑,我也只是尝试不设置任何内容,包括null
,DBNull.Value
和DefaultValue
...根本就没有设置该列。
此外,如果我从DataTable中删除DateAdded列,则它可以正常工作。但是我不想要那个。在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
将返回""
。因为虽然empty对许多类似字符串的字段都有效,但它永远不是有效的数值。
为该字段分配值的条件恰好在该特定列的时间的 0%内达到。因此什么也没设置。
答案 0 :(得分:2)
简单地说,您不能做自己想做的。关于BulkCopy如何使用默认值的最佳参考是Rutzky的This Answer。
问题在于,BulkCopy包含一个查询目标数据库并确定表结构的步骤。如果它确定目标列可以NOT NULL
,并且您要传递null或DBNull,它甚至在尝试传递数据之前都会引发异常。
如果使用SQL事件探查器,则将看到BCP调用,但看不到数据(无论如何都不会显示数据)。您将看到的是定义列列表和标志的调用。
当BulkCopy最终决定传递数据时。如果该列存在,并且该字段是NULL
字段,并且值是DBNull.Value,并且该列具有默认值;批量复制本质上传递了该列的DEFAULT
标志。但是做出了一些决定,使得那些条件(除非该字段是NOT NULL
可以使用)不应该使用默认值,而应该引发异常。
据我所知这是Microsoft的错误或疏忽。
与其他一些答案一样,常见的解决方法是通过计算值应在代码中手动处理这些值。当然,如果您计算默认值,则DBA会更改字段的实际SQL默认值,您的系统将不匹配。下一步是向您的系统中添加一个子系统,该子系统可以查询和/或跟踪/缓存您要查找的SQL Server中当前指定的默认值,并为其分配这些默认值。这比需要的工作还要多。
TLDR:您无法做您想做的事。但是还有其他人指定的次优解决方法。
答案 1 :(得分:1)
此组合不适用于SqlBulkCopy:
此组合确实有效:
如果必须将表中的日期字段配置为不接受空值,那么您还有更多工作要做(创建一个接受空值的临时表,将其批量插入该临时表,调用您编写的存储过程,该插入过程从临时表到常规表)。
我认为您要寻找的最简单的路径是:
您的表中的日期字段应允许为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
答案 2 :(得分:1)
在100,000条记录中,可能有20条具有数据。所以在我的批次中 的500,有时在DateAdded字段中没有数据,有时一个 一两个有数据。
我猜您的某些记录在DateAdded中具有空值,而DateAdded配置为不限制空值。
要解决此问题,您可以采用多种方法:
如果您没有权限,或者您的工作要求指定DateAdded不能接受空值或具有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
}
然后,当您完成所有列的处理后,只需将新行添加到数据表中,然后将最终的数据表复制到服务器即可。