首页 > 解决方案 > 忽略 IDbDataParameter.DbType (SqlClient) 的 DbType.Time

问题描述

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

然而,今天,我遇到了一些非常奇怪的行为,其中 usingIDbDataParameter行为异常。使用与此类似的代码:

System.Data.IDbCommand cmd;
TimeSpan someTimeSpanValue;
...
System.Data.IDbDataParameter dataParameter = cmd.CreateParameter();
dataParameter.DbType = DbType.Time;
dataParameter.Value = someTimeSpanValue;
cmd.Execute();

当我执行我的声明时,ADO.NET 抱怨它无法将我转换TimeSpanDateTime. 基于此参数设置的基础数据库类型是 type 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,然后 interrogateIDbDataParameter.DbType时,它​​确实返回DbType.Time并且一切正常,尽管此代码现在不像我想要的那样与数据库无关。

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

毕竟,我的问题是,这是否是System.Data.SqlClient's 的实现中的缺陷,IDbDataParameter或者是否有一些我不知道的设置会导致这种行为(可能默认为 SQL 2008 之前的兼容性),我可以调整以避免这种情况骇客的解决方法?

标签: c#sql-serverado.net

解决方案


感谢@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
        }
    }

Based on this, it would appear that implementation of IDbDataParameter.DbType in SqlParameter simply does not support DbType.Time or any way to create a parameter of type TIME using the IDbDataParameter interface, since none of those cases return MetaTime. I conclude that the only way to get a parameter to reference a TIME type is to use the SqlParameter.SqlDbType instead.


推荐阅读