繁体   English   中英

无法将“System.DBNull”类型的 object 转换为类型“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。

如果您知道结果集的第一列是一个字符串,那么要覆盖所有基数,您需要检查 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值! 如果该项为nullDBNull.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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM