【问题标题】:Unable to cast object of type 'System.DBNull' to type 'System.String`无法将“System.DBNull”类型的对象转换为“System.String”类型
【发布时间】:2021-04-26 07:14:05
【问题描述】:

我的应用程序出现上述错误。这是原始代码

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

有没有更好的解决方法?

【问题讨论】:

  • 你真的应该看看@rein 的答案,从长远来看会为你节省很多时间

标签: c# asp.net database null


【解决方案1】:

通过一个简单的通用函数,您可以轻松完成此操作。只需这样做:

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;
    }
}

【讨论】:

  • 是的,像这样的函数是唯一实用的解决方案。在您复制和粘贴一千次之后,任何类型的内联逻辑都会失败。 :-)
  • 如果您尝试将 1 转换为 bool,这将不起作用(Convert.ToBoolean(1) 可以正常工作)
  • @roman: 那么我们希望有一个额外的检查(在检查 null 之前)来检查布尔类型......
  • 如果您想或需要使用转换功能,那么这不起作用。在几种情况下,您可能更喜欢转换为显式强制转换。 @romanm 注意到其中之一。另一个是当您使用小数并关心 Convert.ToInt32 和 (int) 使用的不同舍入机制时。前者四舍五入到最接近的偶数值,而显式转换只是截断该值:stackoverflow.com/questions/1608801/… 如果可能,我会使用 T-SQL ISNULL 函数从混合中消除 NULL
  • @Jaime 该函数的作用类似于从 SQL 数据类型到 C#/.NET 数据类型的隐式转换。如果您需要显式转换,请不要使用此函数 - 而是显式执行。
【解决方案2】:

可以使用更短的形式:

return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()

编辑:没有关注 ExecuteScalar。如果返回结果中不存在该字段,它确实返回 null。所以改用:

return (accountNumber == null) ? string.Empty : accountNumber.ToString() 

【讨论】:

  • 那行不通——“accountNumber”不是一个数据库值,而是一个普通的普通旧 .NET“对象”实例——你需要检查正常的“空”值。 DBNull.Value 适用于 SqlDataReader 或 SqlParameter - 但不适用于此处的此对象。
  • 你说得对,我开始优化条件检查部分,之前没有看线。过失。
  • 您的帖子中有错字,我无法真正编辑,因为编辑需要更改 6 个字符。有人可以将 accountNumber.TosString() 更改为 accountNumber.ToString()
  • @marc_s 根据 db/query 布局,您需要检查其中一个甚至两者。如果 WHERE 没有匹配任何行,你会得到一个null,如果选中的行在该列中有NULL,则返回值为System.DBNull
  • 在第一种情况下@Alexander 提到 - 不匹配任何行 - 如果您对从 null 转换时返回的值感到满意,您可以依赖 Convert.ToString 或任何其他 Convert 方法:空字符串字符串,数值为 0,布尔值为 false,日期时间为 MinValue...msdn.microsoft.com/en-us/library/vstudio/…
【解决方案3】:

ExecuteScalar 将返回

  • 如果没有结果集则为空
  • 否则结果集第一行的第一列,可能是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 是错误的。

【讨论】:

  • 我的结果集不会总是返回一行。
【解决方案4】:

您可以使用 C# 的 null 合并运算符

return accountNumber ?? string.Empty;

【讨论】:

  • -1:那不会编译:该方法返回一个字符串,而 accountNumber 是一个对象。
  • 返回 Cmd.ExecuteScalar().ToString() ?? String.Empty;
  • return Cmd.ExecuteScalar().ToString() 为我完成了这项工作
【解决方案5】:

还有另一种解决此问题的方法。如何修改您的存储程序?通过使用 ISNULL(your field, "") sql 函数,如果返回值为 null,则可以返回空字符串。

那么你的干净代码就是原始版本。

【讨论】:

    【解决方案6】:

    这是我用来转换任何可能是 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);
    

    【讨论】:

      【解决方案7】:

      我想你可以这样做:

      string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;
      

      如果 accountNumber 为 null,则表示它是 DBNull 而不是字符串 :)

      【讨论】:

      • return (accountNumber as string) ?? string.Empty; ,其中 accountNumber 仍然是 object。如果您希望将数据库调用保持在单独的行上。
      【解决方案8】:

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

      但是,我认为您在代码可理解性方面失去了一些东西

      【讨论】:

      • 如果你写return "" + accountNumber;会发生什么?
      【解决方案9】:

      因为我得到了一个不为空的实例,如果我与 DBNULL 进行比较,我得到了Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull' 异常, 如果我尝试更改为与 NULL 进行比较,它根本不起作用(因为 DBNull 是一个对象),即使这是公认的答案。

      我决定简单地使用“is”关键字。 所以结果可读性很强:

      data = (item is DBNull) ? String.Empty : item

      【讨论】:

        【解决方案10】:

        基于@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);
        

        附言我还没有想出如何在不牺牲代码长度的情况下使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;
        }
        

        只要表列名称与类的属性名称匹配,解决方案就可以工作。并且可能不是生产级性能,所以使用或修改需要您自担风险:)

        【讨论】:

          【解决方案11】:

          我使用一个扩展来为我解决这个问题,这可能是也可能不是你想要的。

          是这样的:

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

          【讨论】:

            【解决方案12】:

            像这样转换它

            string s = System.DBNull.value.ToString();
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-06-19
              • 2012-09-14
              相关资源
              最近更新 更多