在将对象设置为参数值之前将对象变量转换为 DBType?

Posted

技术标签:

【中文标题】在将对象设置为参数值之前将对象变量转换为 DBType?【英文标题】:Convert a object variable to a DBType before setting object as value of a parameter? 【发布时间】:2015-12-17 20:54:51 【问题描述】:

我正在使用 SqlCommand 来执行存储过程。参数是使用类SqlCommandBuilderDeriveParameters 方法根据存储过程的定义自动创建的。这会自动为我设置 DbType。接下来,我使用 <string, object> 键值对遍历字典,其中字符串是参数的名称,对象包含要设置的值。

简单示例来源:

public DataTable FetchProducts(SqlConnection sqlConn, IDictionary<string, object> paramvalues)

    using (SqlCommand cmd = new SqlCommand("ProcFetchProducts", sqlConn))
    
        cmd.CommandType = CommandType.StoredProcedure;
        SqlCommandBuilder.DeriveParameters(cmd);

        foreach (KeyValuePair<string, object> pair in paramvalues)
        
            var index = cmd.Parameters.IndexOf(pair.Key);
            cmd.Parameters[index].Value = pair.Value;
        

        using (var dr = cmd.ExecuteReader())
        
            var dt = new DataTable("Result");
            dt.Load(dr);
            return dt;
        
    

有时对象包含的值与参数的 DBType 不匹配。例如,一个参数是 smallint 类型,而对象包含一个字符串。现在,当我执行数据读取器时,我得到一个“输入字符串的格式不正确”FormatException,它没有告诉我是哪个参数导致了这个问题。

所以我的主要问题是:有没有办法将对象从字典转换为参数中定义的 DBType,这样我就可以在执行数据读取器之前检查它是否是正确的类型?

【问题讨论】:

【参考方案1】:

更新

======在 OP 澄清后更新了答案(见 cmets)=======

您需要维护 CLR 类型映射 w.r.t 的列表。 SqlDbType 然后检查它是否是字符串类型并将字符串类型解析/转换为相应的 clr 类型并将其作为对象返回。这会将底层类型从字符串更改为 SqlDbType 的映射 clr 类型。

SqlDbType 到 CLR 类型:(referred from this source)

public static Type GetClrType(SqlDbType sqlType)

    switch (sqlType)
    
        case SqlDbType.BigInt:
            return typeof(long?);

        case SqlDbType.Binary:
        case SqlDbType.Image:
        case SqlDbType.Timestamp:
        case SqlDbType.VarBinary:
            return typeof(byte[]);

        case SqlDbType.Bit:
            return typeof(bool?);

        case SqlDbType.Char:
        case SqlDbType.NChar:
        case SqlDbType.NText:
        case SqlDbType.NVarChar:
        case SqlDbType.Text:
        case SqlDbType.VarChar:
        case SqlDbType.Xml:
            return typeof(string);

        case SqlDbType.DateTime:
        case SqlDbType.SmallDateTime:
        case SqlDbType.Date:
        case SqlDbType.Time:
        case SqlDbType.DateTime2:
            return typeof(DateTime?);

        case SqlDbType.Decimal:
        case SqlDbType.Money:
        case SqlDbType.SmallMoney:
            return typeof(decimal?);

        case SqlDbType.Float:
            return typeof(double?);

        case SqlDbType.Int:
            return typeof(int?);

        case SqlDbType.Real:
            return typeof(float?);

        case SqlDbType.UniqueIdentifier:
            return typeof(Guid?);

        case SqlDbType.SmallInt:
            return typeof(short?);

        case SqlDbType.TinyInt:
            return typeof(byte?);

        case SqlDbType.Variant:
        case SqlDbType.Udt:
            return typeof(object);

        case SqlDbType.Structured:
            return typeof(DataTable);

        case SqlDbType.DateTimeOffset:
            return typeof(DateTimeOffset?);

        default:
            throw new ArgumentOutOfRangeException("sqlType");
    

字符串到类型转换器:

private static object Convert(string value, Type type)
    
        object result;

        if (string.IsNullOrWhiteSpace(value))
        
            return null;
        

        try
        
            var converter = TypeDescriptor.GetConverter(type);
            result = converter.ConvertFromString(value);
            return result;
        
        catch (Exception exception)
        
            // Log this exception if required.
            throw new InvalidCastException(string.Format("Unable to cast the 0 to type 1", value, newType, exception));
        
    

用法:

foreach (KeyValuePair<string, object> pair in paramvalues)

    var index = cmd.Parameters.IndexOf(pair.Key);

    var value = pair.Value;

    if (pair.Value == typeof(string))
    
        value = Convert((string)pair.Value, GetClrType(cmd.Parameters[index].SqlDbType));
    

    cmd.Parameters[index].Value = value;

【讨论】:

如果我错了,请纠正我,但在我给出的示例中,我的参数是从存储过程定义中派生的。 proc 中的参数是 smallint 类型,但我的对象(值)是字符串类型。在您的情况下,dbtype 将是 DbType.String,由于类型不匹配,这将导致数据读取器出错。 我明白你的意思了。你需要一张 SqlDbType 和 .Net 类型的映射。然后你需要一个字符串解析器,它将根据 .NetType w.r.t 转换提供的值。 SqlDbType 在您的参数中。我会更新我的答案。 我找到了解决办法! ...我也更新了答案。希望这会有所帮助。 nice.. 和我的回答几乎一样 :) 我刚刚使用了 Convert.ChangeType 并查看是否可以进行转换。如果不是,则类型不匹配。我会接受你对你所有工作的回答。谢谢。【参考方案2】:

没有内置函数,但您可以创建自己的简单列表并检查它们是否匹配:

typeMap = new Dictionary<Type, DbType>();
typeMap[typeof(byte)] = DbType.Byte;
typeMap[typeof(sbyte)] = DbType.SByte;
typeMap[typeof(short)] = DbType.Int16;
typeMap[typeof(ushort)] = DbType.UInt16;
typeMap[typeof(int)] = DbType.Int32;
typeMap[typeof(uint)] = DbType.UInt32;
typeMap[typeof(long)] = DbType.Int64;
typeMap[typeof(ulong)] = DbType.UInt64;
typeMap[typeof(float)] = DbType.Single;
typeMap[typeof(double)] = DbType.Double;
typeMap[typeof(decimal)] = DbType.Decimal;
typeMap[typeof(bool)] = DbType.Boolean;
typeMap[typeof(string)] = DbType.String;
typeMap[typeof(char)] = DbType.StringFixedLength;
typeMap[typeof(Guid)] = DbType.Guid;
typeMap[typeof(DateTime)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
typeMap[typeof(byte[])] = DbType.Binary;
typeMap[typeof(byte?)] = DbType.Byte;
typeMap[typeof(sbyte?)] = DbType.SByte;
typeMap[typeof(short?)] = DbType.Int16;
typeMap[typeof(ushort?)] = DbType.UInt16;
typeMap[typeof(int?)] = DbType.Int32;
typeMap[typeof(uint?)] = DbType.UInt32;
typeMap[typeof(long?)] = DbType.Int64;
typeMap[typeof(ulong?)] = DbType.UInt64;
typeMap[typeof(float?)] = DbType.Single;
typeMap[typeof(double?)] = DbType.Double;
typeMap[typeof(decimal?)] = DbType.Decimal;
typeMap[typeof(bool?)] = DbType.Boolean;
typeMap[typeof(char?)] = DbType.StringFixedLength;
typeMap[typeof(Guid?)] = DbType.Guid;
typeMap[typeof(DateTime?)] = DbType.DateTime;
typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
typeMap[typeof(System.Data.Linq.Binary)] = DbType.Binary;

只需调用您的 KVP 值的GetType,如下所示:

foreach (KeyValuePair<string, object> pair in paramvalues)

    var index = cmd.Parameters.IndexOf(pair.Key);
    cmd.Parameters[index].Value = pair.Value;

    // If null, you should use DbNull.
    var YOUR_TYPE = typeMap[pair.Value.GetType()];

名单的致谢:https://***.com/a/7952171/2630261

正如一些提示,我更喜欢var 而不是KeyValuePair&lt;string, object&gt;,但这对你的问题并不重要:)

【讨论】:

就像我刚刚对 Vendettamit 的帖子发表的评论一样,这将返回一个字符串类型。我想要做的是尝试将一个字符串转换为一个smallint,这样如果它是一个字符串,它将因为类型错误而失败。【参考方案3】:

感谢@BendEg 和@Vendettamit,我找到了解决方案。我根据他们的示例代码创建了一个字典Dictionary&lt;DbType, Type&gt;(),并将每个 DbType 映射到一个 Clr 类型。一个简单的GetClrType 方法从字典中获取 clr 类型。接下来,我尝试转换它。如果失败,我会捕获异常并向用户报告转换失败并且参数的值类型错误。

Type clrType = SqlDbTypeResolver.GetClrType(cmd.Parameters[index].DbType);
try
 
    Convert.ChangeType(parm.Value, clrType); // no need to store the value, I just need to catch the exception if thrown.

catch(SomeException ex)

    //report stuff to user about failed conversion

【讨论】:

以上是关于在将对象设置为参数值之前将对象变量转换为 DBType?的主要内容,如果未能解决你的问题,请参考以下文章

如何将哈希码对象值转换为字符串变量

在将列表存储为对象之前引用列表中的其他项目

Spring Integration DSL 变压器

在将对象转换为json期间,无法懒惰地初始化角色集合

将索引转换为日期时间对象后,MatplotLib 无法正确绘制熊猫时间序列 1 分钟数据

如何将整数变量转换为 QTime 对象