[英]System.InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.String'
[英]Unable to cast object of type 'System.DBNull' to type 'System.String`
我的应用程序出现上述错误。 这是原始代码
public string GetCustomerNumber(Guid id)
{
string accountNumber =
(string)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("@id", id));
return accountNumber.ToString();
}
我换成了
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("@id", id));
if (accountNumber is System.DBNull)
{
return string.Empty;
}
else
{
return accountNumber.ToString();
}
}
有没有更好的方法解决这个问题?
使用一个简单的通用函数,您可以很容易地做到这一点。 只需这样做:
return ConvertFromDBVal<string>(accountNumber);
使用函数:
public static T ConvertFromDBVal<T>(object obj)
{
if (obj == null || obj == DBNull.Value)
{
return default(T); // returns the default value for the type
}
else
{
return (T)obj;
}
}
可以使用更短的形式:
return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()
编辑:还没有关注 ExecuteScalar。 如果该字段在返回结果中不存在,它确实会返回 null。 所以改用:
return (accountNumber == null) ? string.Empty : accountNumber.ToString()
ExecuteScalar 将返回
如果您知道结果集的第一列是一个字符串,那么要覆盖所有基数,您需要检查 null 和 DBNull。 就像是:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null) ? String.Empty : accountNumber.ToString();
上面的代码依赖于 DBNull.ToString 返回一个空字符串的事实。
如果 accountNumber 是另一种类型(比如整数),那么您需要更加明确:
object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null || Convert.IsDBNull(accountNumber) ?
(int) accountNumber : 0;
如果您确定您的结果集总是至少有一行(例如 SELECT COUNT(*)...),那么您可以跳过对 null 的检查。
在您的情况下,错误消息“无法将类型为 'System.DBNull' 的对象强制转换为类型为 'System.String`”表示结果集的第一列是 DBNUll 值。 这是从第一行的强制转换到字符串:
string accountNumber = (string) ... ExecuteScalar(...);
Marc_s 关于您不需要检查 DBNull.Value 的评论是错误的。
您可以使用 C# 的空合并运算符
return accountNumber ?? string.Empty;
这是我用来转换任何可能是 DBNull.Value 的对象的通用方法:
public static T ConvertDBNull<T>(object value, Func<object, T> conversionFunction)
{
return conversionFunction(value == DBNull.Value ? null : value);
}
用法:
var result = command.ExecuteScalar();
return result.ConvertDBNull(Convert.ToInt32);
更短:
return command
.ExecuteScalar()
.ConvertDBNull(Convert.ToInt32);
还有另一种方法可以解决此问题。 如何修改你的存储过程? 通过使用 ISNULL(your field, "") sql 函数,如果返回值为空,则可以返回空字符串。
然后您将干净的代码作为原始版本。
我想你可以这样做:
string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;
如果 accountNumber 为 null,则表示它是 DBNull 而不是字符串 :)
String.Concat 将 DBNull 和 null 值转换为空字符串。
public string GetCustomerNumber(Guid id)
{
object accountNumber =
(object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM,
CommandType.StoredProcedure,
"spx_GetCustomerNumber",
new SqlParameter("@id", id));
return String.Concat(accountNumber);
}
但是,我认为您在代码可理解性方面失去了一些东西
由于我得到了一个不为空的实例,如果我与 DBNULL 进行比较,我得到了Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull'
,并且如果我尝试更改以进行比较NULL,它根本不起作用(因为 DBNull 是一个对象),即使这是公认的答案。
我决定简单地使用“is”关键字。 所以结果是非常可读的:
data = (item is DBNull) ? String.Empty : item
基于@rein的回答
public static class DbDataReaderExtensions
{
public static TObjProp Get<TObj, TObjProp>(
this DbDataReader reader,
Expression<Func<TObj, TObjProp>> expression)
{
MemberExpression member = expression.Body as MemberExpression;
string propertyName = member.Member.Name;
//PropertyInfo propInfo = member.Member as PropertyInfo;
var recordOrdinal = reader.GetOrdinal(propertyName);
var obj = reader.GetValue(recordOrdinal);
if (obj == null || obj == DBNull.Value)
{
return default(TObjProp);
}
else
{
return (TObjProp)obj;
}
}
}
鉴于:
public class MyClass
{
public bool? IsCheckPassed { get; set; }
}
用于:
var test = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
或者,如果您在异常方法中硬编码类类型:
var test = reader.Get(o => o.IsCheckPassed);
ps我还没有想出如何在不牺牲代码长度的情况下隐式generics
..免费发表评论并提出改进建议
完整示例:
public async Task<MyClass> Test(string connectionString) {
var result = new MyClass();
await using var con = new SQLiteConnection(connectionString);
con.Open();
await using var cmd = con.CreateCommand();
cmd.CommandText = @$"SELECT Id, IsCheckPassed FROM mytable";
var reader = await cmd.ExecuteReaderAsync();
while (reader.Read()) {
// old, not working! Throws exception!
//bool? isCheckPassed1 = reader.GetBoolean(reader.GetOrdinal("IsCheckPassed"));
// old, working, but too long (also if you have like 20 properties then all the more reasons to refactor..)
bool? isCheckPassed2 = null;
bool? isCheckPassed2Temp = reader.GetValue(reader.GetOrdinal("IsCheckPassed"));
if (isCheckPassed2Temp != null && isCheckPassed2Temp != DBNull.Value)
isCheckPassed2 = (bool?)isCheckPassed2Temp;
// new
var isCheckPassed3 = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
// repeat for 20 more properties :)
result.IsCheckPassed = isCheckPassed3;
}
return result;
}
只要表列名称与类的属性名称匹配,解决方案就会起作用。 并且可能不是生产级性能明智的,因此使用或修改风险自负:)
使用更新的 C# 语法并考虑可为空类型的更简洁的方法:
private static T? FromDbNull<T>(object? obj) =>
obj == null || obj == DBNull.Value ? default : (T)obj;
可以与数据读取器一起使用,如下所示:
while (reader.Read())
{
var newObject = new SomeObject(
FromDbNull<string?>(reader["nullable_field_1"]),
FromDbNull<string?>(reader["nullable_field_2"]),
FromDbNull<string?>(reader["nullable_field_3"]),
FromDbNull<double?>(reader["nullable_field_4"])
);
response.Add(newObject);
}
我使用一个扩展来为我解决这个问题,这可能是也可能不是你想要的。
它是这样的:
public static class Extensions
{
public String TrimString(this object item)
{
return String.Format("{0}", item).Trim();
}
}
此扩展不返回null
值! 如果该项为null
或DBNull.Value ,它将返回一个空字符串。
public string GetCustomerNumber(Guid id)
{
var obj =
DBSqlHelperFactory.ExecuteScalar(
connectionStringSplendidmyApp,
CommandType.StoredProcedure,
"GetCustomerNumber",
new SqlParameter("@id", id)
);
return obj.TrimString();
}
转换它喜欢
string s = System.DBNull.value.ToString();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.