在将对象设置为参数值之前将对象变量转换为 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 来执行存储过程。参数是使用类SqlCommandBuilder
的DeriveParameters
方法根据存储过程的定义自动创建的。这会自动为我设置 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<string, object>
,但这对你的问题并不重要:)
【讨论】:
就像我刚刚对 Vendettamit 的帖子发表的评论一样,这将返回一个字符串类型。我想要做的是尝试将一个字符串转换为一个smallint,这样如果它是一个字符串,它将因为类型错误而失败。【参考方案3】:感谢@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
【讨论】:
以上是关于在将对象设置为参数值之前将对象变量转换为 DBType?的主要内容,如果未能解决你的问题,请参考以下文章