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
- Splitting up the data to pass in different columnmappings (complex)
- Check DB for defaults and filling them in the datareader (slow)
- Create seperate dump table without constraints somehow (complex, maybe slow)
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:
- Non-nullable default values workaround
- Clear validation messages (like max length)
- Handle column name case matching
Disadvantages
- parsing default value string
- can't resolve sql side default values (like sprocs)