DbType.Time игнорируется для IDbDataParameter.DbType (SqlClient)

У меня есть привычка всегда использовать общие абстракции в 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.SqlClientIDbDataParameter или есть какой-то параметр, о котором я не знаю, который вызывает такое поведение (возможно, по умолчанию совместимость с pre-SQL 2008), который я могу настроить. избежать этого хакерского обходного пути?

Это связано с тем, что dbtype.time поддерживает только до 24 часов. для хранения временного интервала вы должны использовать datetime. Пример вывода временного интервала с 1 дня 1.00:00:00, это недопустимый формат dbtype.time

Claudio Corchez 10.06.2019 18:28

@DavidG похоже, что ваш комментарий - правильный ответ. Согласно этому справочному коду, такое поведение разработано для обратной совместимости.

Tim 10.06.2019 18:40
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
256
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Спасибо @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.

Другие вопросы по теме