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

Convert a object variable to a DBType before setting object as value of a parameter?

我正在使用 SqlCommand 执行存储过程。使用classSqlCommandBuilderDeriveParameters方法,根据存储过程的定义自动创建参数。这会自动为我设置 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 类型,对象包含一个字符串。现在,当我执行数据读取器时,我得到一个 "input string is not in a correct format" FormatException,它没有告诉我哪个参数导致了这个问题。

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

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

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;

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

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()];
}

列表的致谢名单:

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

已更新

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

您需要维护 CLR 类型映射列表 w.r.t。 SqlDbType 然后检查它是否是字符串类型和 parse/convert 字符串类型到相应的 clr 类型和 return 它作为对象。这会将基础类型从字符串更改为 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;
}

感谢@BendEg 和@Vendettamit,我找到了解决方案。我根据他们的示例代码创建了一个字典 Dictionary<DbType, Type>(),并将每个 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
}