DbType.Time 已忽略 IDbDataParameter.DbType (SqlClient)

DbType.Time ignored for IDbDataParameter.DbType (SqlClient)

我有一个习惯,总是使用 System.Data 中的通用抽象,例如 IDbCommandIDbDataParameter 来访问数据,而不是使用具体实现 System.Data.SqlClient.SqlDbCommandSystem.Data.SqlClient.SqlParameter,只要有可能,即使我的应用程序以 SQL 服务器为目标。我这样做是为了将来更容易将应用程序移植到另一个 RDBMS,或者根据需要在我的应用程序和 SQL 服务器之间引入数据库抽象层。十多年来,我一直在成功地使用这种模式。

然而,今天我遇到了一些非常奇怪的行为,其中使用 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 时,它 returned 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,它确实 return DbType.Time 并且一切正常,尽管这段代码现在不是数据库不可知论者,如我所愿。

更奇怪的是,如果将 IDbDataParameter.DbType 设置为 DbType.Time(这是它的当前值)after 将 SqlDbType 设置为 SqlDbType.Time , 它仍然变为 DbType.DateTime.

毕竟,我的问题是,这是否是 System.Data.SqlClientIDbDataParameter 的实现中的一个缺陷,或者是否有一些我不知道的设置导致了这种行为(也许默认为 SQL 2008 之前的兼容性),我可以调整它以避免这种 hacky 解决方法?

感谢@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) reference source,这似乎是一致的:

  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
        }
    }

基于此,SqlParameterIDbDataParameter.DbType 的实现似乎根本不支持 DbType.Time 或任何创建类型 TIME 参数的方法IDbDataParameter 接口,因为 none 这些情况 return MetaTime。我的结论是,获取参数以引用 TIME 类型的唯一方法是改用 SqlParameter.SqlDbType