У меня есть привычка всегда использовать общие абстракции в System.Data
, такие как IDbCommand
и IDbDataParameter
, для доступа к данным вместо конкретных реализаций System.Data.SqlClient.SqlDbCommand
и System.Data.SqlClient.SqlParameter
, когда это возможно, даже если мое приложение нацелено на SQL Server. Я делаю это для того, чтобы в будущем было проще перенести приложение на другую СУБД или, при необходимости, ввести уровни абстракции базы данных между моим приложением и SQL Server. Я успешно использую этот шаблон уже более десяти лет.
Однако сегодня я столкнулся с очень странным поведением, когда использование IDbDataParameter
вело себя неожиданно. Используя код, подобный этому:
System.Data.IDbCommand cmd;
TimeSpan someTimeSpanValue;
...
System.Data.IDbDataParameter dataParameter = cmd.CreateParameter();
dataParameter.DbType = DbType.Time;
dataParameter.Value = someTimeSpanValue;
cmd.Execute();
ADO.NET пожаловался, когда я выполнил свою инструкцию, что не может преобразовать мой TimeSpan
в DateTime
. Базовый тип базы данных, который задавался на основе этого параметра, имел тип TIME
. В замешательстве я проследил код и, конечно же, когда я установил dataParameter.DbType = DbType.Time
, а затем опросил dataParameter.DbType
, он вернул DbType.DateTime
вместо DbType.Time
, как я устанавливал.
У меня есть обходной путь для этого кода, который выглядит так:
dataParameter.DbType = v.DbType; //v.DbType is the desire DbType for the command being prepared
if (v.DbType == DbType.Time && dataParameter.DbType != DbType.Time && dataParameter is System.Data.SqlClient.SqlParameter sqlParam)
sqlParam.SqlDbType = SqlDbType.Time;
Кажется, когда я устанавливаю SqlDbType в SqlDbType.Time, затем опрашиваю IDbDataParameter.DbType
, он возвращает DbType.Time
и все работает, хотя этот код теперь не настолько агностичен к базе данных, как хотелось бы.
Еще более странным является то, что если установить IDbDataParameter.DbType
в DbType.Time
(это его текущее значение), после установит для SqlDbType значение SqlDbType.Time, оно все равно изменится на DbType.DateTime
.
В конце концов, мой вопрос заключается в том, является ли это дефектом реализации System.Data.SqlClient
IDbDataParameter
или есть какой-то параметр, о котором я не знаю, который вызывает такое поведение (возможно, по умолчанию совместимость с pre-SQL 2008), который я могу настроить. избежать этого хакерского обходного пути?
Это связано с тем, что dbtype.time поддерживает только до 24 часов. для хранения временного интервала вы должны использовать datetime. Пример вывода временного интервала с 1 дня 1.00:00:00, это недопустимый формат dbtype.time
@DavidG похоже, что ваш комментарий - правильный ответ. Согласно этому справочному коду, такое поведение разработано для обратной совместимости.
Спасибо @DavidG, опубликовавшему комментарий со ссылкой на исходный код .NET. На основе справочного кода:
override public DbType DbType {
get {
return GetMetaTypeOnly().DbType;
}
set {
MetaType metatype = _metaType;
if ((null == metatype) || (metatype.DbType != value) ||
// SQLBU 504029: Two special datetime cases for backward compat
// DbType.Date and DbType.Time should always be treated as setting DbType.DateTime instead
value == DbType.Date ||
value == DbType.Time) {
PropertyTypeChanging();
_metaType = MetaType.GetMetaTypeFromDbType(value);
}
}
}
Я могу сделать вывод, что такое поведение предусмотрено дизайном, основываясь на комментариях в справочном коде.
Вслед за GetMetaTypeFromDbType(value)
справочный источник может показаться, что это последовательно:
internal static MetaType GetMetaTypeFromDbType(DbType target) {
// if we can't map it, we need to throw
switch (target) {
case DbType.AnsiString: return MetaVarChar;
case DbType.AnsiStringFixedLength: return MetaChar;
case DbType.Binary: return MetaVarBinary;
case DbType.Byte: return MetaTinyInt;
case DbType.Boolean: return MetaBit;
case DbType.Currency: return MetaMoney;
case DbType.Date:
case DbType.DateTime: return MetaDateTime;
case DbType.Decimal: return MetaDecimal;
case DbType.Double: return MetaFloat;
case DbType.Guid: return MetaUniqueId;
case DbType.Int16: return MetaSmallInt;
case DbType.Int32: return MetaInt;
case DbType.Int64: return MetaBigInt;
case DbType.Object: return MetaVariant;
case DbType.Single: return MetaReal;
case DbType.String: return MetaNVarChar;
case DbType.StringFixedLength: return MetaNChar;
case DbType.Time: return MetaDateTime;
case DbType.Xml: return MetaXml;
case DbType.DateTime2: return MetaDateTime2;
case DbType.DateTimeOffset: return MetaDateTimeOffset;
case DbType.SByte: // unsupported
case DbType.UInt16:
case DbType.UInt32:
case DbType.UInt64:
case DbType.VarNumeric:
default: throw ADP.DbTypeNotSupported(target, typeof(SqlDbType)); // no direct mapping, error out
}
}
Исходя из этого, может показаться, что реализация IDbDataParameter.DbType
в SqlParameter
просто не поддерживает DbType.Time
или какой-либо способ создания параметра типа TIME
с использованием интерфейса IDbDataParameter
, поскольку ни один из этих случаев не возвращает MetaTime
. Я пришел к выводу, что единственный способ заставить параметр ссылаться на тип TIME
— это использовать вместо него SqlParameter.SqlDbType
.
Из исходного кода DbType.Date и DbType.Time всегда следует рассматривать как установку DbType.DateTime..