Reputation: 93317
Is there a way to pass the DEFAULT keyword to SqlBulkCopy from an IDataReader?
Basically the equivalent of
INSERT INTO MyTable(NonNullableWithDefault)
VALUES (DEFAULT)
In my application the target tables have not-nullable columns with a default constraint on them.
Some of the data to upload has values for these columns, some does not.
For the rows where there is no value (DBNull) it should use the default constraint.
As noted in this answer the SqlBulkCopy either expects you to not pass in a ColumnMapping at all or send a value.
Alternatives I'm considering
I can't be the first person with this problem.
What do people use in such a scenario?
UPDATE
After much fiddling around I now fetch my entire database structure with one query and then pass that dataset to my datareaders.
SELECT
columns.TABLE_SCHEMA schemaName
, columns.TABLE_NAME tableName
, columns.TABLE_SCHEMA + '.' + columns.TABLE_NAME fullTableName
, columns.COLUMN_NAME columnName
, columns.DATA_TYPE dataType
, ISNULL(columns.CHARACTER_MAXIMUM_LENGTH, -1) charlength
, columns.COLUMN_DEFAULT defaultValue
, columns.ORDINAL_POSITION ordinalPosition
FROM
information_schema.columns columns
The parsing of the default value string isn't pretty and most likely not fitted for advanced cases. In our case however, it covers all bases.
private object ParseDefault(DataRow row)
{
if(row.IsNull("defaultValue")) return null;
var value = row.Field<string>("defaultValue");
if (value == "(getdate())")
{
return DateTime.UtcNow;
}
var type = GetTypeForName(row.Field<string>("dataType"));
return ParseCell(value.Trim('(', ')'), type);
}
private static object ParseCell(string value, Type info)
{
if (value.Equals("NULL", StringComparison.OrdinalIgnoreCase))
{
return DBNull.Value;
}
int intValue;
if (info == typeof(bool) && int.TryParse(value, out intValue))
{
return intValue == 1;
}
var foo = TypeDescriptor.GetConverter(info);
return foo.ConvertFromInvariantString(value);
}
Advantages:
Disadvantages
Upvotes: 2
Views: 723
Reputation: 5147
Is there a way to pass the DEFAULT keyword to SqlBulkCopy from an IDataReader?
Unfortunately, no. :-( If a column is NOT NULL
and your bulk insert maps to it, each row must have a value for that column.
Background: At the TDS level, a bulk insert is done by sending a SQL INSERT
statement written using the external tools only syntax followed by TDS structures containing column metadata then row data. None of these provide a way to say "if a NULL
value is in a non-NULL
able column, treat the NULL
as a DEFAULT
."
What do people use in such a scenario?
Hum...it's frustrating when it feels like there should be built-in, simple solution and instead all the options are painful. :-/ Without knowing more about your situation, it's hard to know which option to recommend.
One additional option: the placeholder value approach. Prior to the insert, replace the "NULL
s that should be defaulted" with a placeholder value. Then, post-insert, run an update(s) to replace the placeholder(s) with database-generated default values (e.g. UPDATE ... SET Col1 = DEFAULT WHERE Col1 = *placeholder*
). Your application would have to know which non-nullable columns have defaults but wouldn't have to know how to compute the defaults. I don't care for it because it uses so-called magic numbers--but it is an option.
Upvotes: 2