关于c#:处理DBNull的最佳方法是什么

关于c#:处理DBNull的最佳方法是什么

What is the best way to deal with DBNull's

我经常在处理从SqlDataAdapters返回的DataRows时遇到问题。 当我尝试使用如下代码填充对象时:

1
2
DataRow row = ds.Tables[0].Rows[0];
string value = (string)row;

在这种情况下处理DBNull's的最佳方法是什么。


可空类型是好的,但仅适用于不可为空的类型。

要使类型"可为空",请在类型后附加一个问号,例如:

1
int? value = 5;

我还建议使用"as"关键字而不是强制转换。您只能在可为空的类型上使用" as"关键字,因此请确保您要投射已经为可为空的内容(例如字符串),或者如上所述使用可为空的类型。原因是

  • 如果类型为可为空,则如果值是DBNull,则" as"关键字将返回null
  • 尽管只是在某些情况下,它比铸造速度快得多。这本身就不足以成为使用as的理由,但加上上面的原因它很有用。
  • 我建议做这样的事情

    1
    2
    DataRow row = ds.Tables[0].Rows[0];
    string value = row as string;

    在上述情况下,如果row返回为DBNull,则value将变为null而不是引发异常。请注意,如果您的数据库查询更改了要返回的列/类型,则使用as将导致您的代码默默地失败,并使值变得简单null,而不是在返回错误数据时引发适当的异常,因此建议您有测试以其他方式验证您的查询,以确保随着代码库的发展数据的完整性。


    如果您没有使用可空类型,最好的办法是检查列的值是否为DBNull。如果它是DBNull,则将您的引用设置为对相应数据类型使用null / empty的引用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    DataRow row = ds.Tables[0].Rows[0];
    string value;

    if (row["fooColumn"] == DBNull.Value)
    {
       value = string.Empty;
    }
    else
    {
       value = Convert.ToString(row["fooColumn"]);
    }

    就像Manu所说的那样,您可以使用每种类型的重载convert方法创建一个convert类,这样就不必在if / else块中添加代码了。

    但是,我会强调,如果可以使用可空类型,那是更好的选择。原因在于,对于非可空类型,您将不得不求助于"魔术数字"来表示null。例如,如果要将列映射到int变量,将如何表示DBNull?通常你不能使用0,因为0在大多数程序中都有有效含义。我经常看到人们将DBNull映射到int.MinValue,但这也可能存在问题。我最好的建议是:

    • 对于数据库中可以为null的列,请使用可空类型。
    • 对于数据库中不能为null的列,请使用常规类型。

    可以使用可空类型来解决这个问题。话虽如此,如果您使用的是较旧版本的框架,或者是为不使用可空类型的人工作的,那么代码示例将解决问题。


    我总是使用If / Else检查版本,只有三元运算符才能清楚,简洁,无问题。将所有内容保留在一行中,包括如果列为空则指定默认值。

    因此,假设名为"MyCol"的可为空的Int32列,如果列为null,则返回-99,但如果列不为null,则返回整数值:

    1
    return row["MyCol"] == DBNull.Value ? -99 : Convert.ToInt32(Row["MyCol"]);

    它与上面的If / Else获胜者的方法相同 - 但我发现如果你从一个数据加载器中读取多个列,这是一个真正的奖励,所有列读取行都在另一个下面,排成一行,因为它是更容易发现错误:

    1
    2
    3
    Object.ID = DataReader["ID"] == DBNull.Value ? -99 : Convert.ToInt32(DataReader["ID"]);
    Object.Name = DataReader["Name"] == DBNull.Value ?"None" : Convert.ToString(DataReader["Name"]);
    Object.Price = DataReader["Price"] == DBNull.Value ? 0.0 : Convert.ToFloat(DataReader["Price"]);

    添加对System.Data.DataSetExtensions的引用,该引用增加了Linq对查询数据表的支持。

    这将是这样的:

    1
    2
    3
    string value = (
        from row in ds.Tables[0].Rows
        select row.Field<string>(0) ).FirstOrDefault();

    如果您可以控制返回结果的查询,则可以使用ISNULL()返回非空值,如下所示:

    1
    2
    3
    4
    5
    SELECT
      ISNULL(name,'') AS name
      ,ISNULL(age, 0) AS age
    FROM
      names

    如果您的情况可以容忍这些不可思议的值来代替NULL,则采用这种方法可以解决整个应用程序中的问题,而不会使代码混乱。


    值得一提的是,DBNull.Value.ToString()等于String.Empty

    你可以利用这个优势:

    1
    2
    DataRow row = ds.Tables[0].Rows[0];
    string value = row["name"].ToString();

    但是,这仅适用于字符串,对于其他所有我将使用linq方式或扩展方法。对于我自己,我编写了一个小的扩展方法来检查DBNull,甚至通过Convert.ChangeType(...)进行转换

    1
    2
    int value = row.GetValueOrDefault<int>("count");
    int value = row.GetValueOrDefault<int>("count", 15);


    DBNull像其他一样实现.ToString()。无需做任何事情。而不是硬强制转换,调用对象的.ToString()方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    DataRow row = ds.Tables[0].Rows[0];
    string value;

    if (row["fooColumn"] == DBNull.Value)
    {
       value = string.Empty;
    }
    else
    {
       value = Convert.ToString(row["fooColumn"]);
    }

    这成了:

    1
    2
    DataRow row = ds.Tables[0].Rows[0];
    string value = row.ToString()

    DBNull.ToString()返回string.Empty

    我想这是你正在寻找的最佳实践


    通常在使用DataTables时你必须处理这种情况,其中row字段可以是null或DBNull,通常我会像这样处理:

    1
    string myValue = (myDataTable.Rows[i]["MyDbNullableField"] as string) ?? string.Empty;

    'as'运算符为无效的强制转换返回null,比如DBNull为string,'??'回报
    如果第一个为null,则表达式右侧的术语。


    您还应该查看扩展方法。以下是一些处理此场景的示例。

    推荐阅读


    就在几天前,布拉德艾布拉姆斯发布了一些相关信息
    http://blogs.msdn.com/brada/archive/2009/02/09/framework-design-guidelines-system-dbnull.aspx

    在摘要"AVOID using System.DBNull。Prefer Nullable"。

    这是我的两分钱(未经测试的代码:))

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Or if (row["fooColumn"] == DBNull.Value)
    if (row.IsNull["fooColumn"])
    {
       // use a null for strings and a Nullable for value types
       // if it is a value type and null is invalid throw a
       // InvalidOperationException here with some descriptive text.
       // or dont check for null at all and let the cast exception below bubble  
       value = null;
    }
    else
    {
       // do a direct cast here. dont use"as","convert","parse" or"tostring"
       // as all of these will swallow the case where is the incorect type.
       // (Unless it is a string in the DB and really do want to convert it)
       value = (string)row["fooColumn"];
    }

    还有一个问题......你有没有使用ORM的原因?


    由于某种原因,我在对DBNull.Value进行检查时遇到了问题,因此我做了一些稍有不同的事情,并利用了DataRow对象中的一个属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    if (row.IsNull["fooColumn"])
    {
       value = string.Empty();
    }
    {
    else
    {
       value = row["fooColumn"].ToString;
    }


    我通常编写自己的ConvertDBNull类,该类包装内置的Convert类。如果值为DBNull,则它的引用类型为null,否则为默认值。
    例:
    -ConvertDBNull.ToInt64(object obj)返回Convert.ToInt64(obj),除非obj是DBNull,在这种情况下它将返回0。


    您还可以使用Convert.IsDBNull(MSDN)进行测试。


    如果您担心在期望字符串时获取DBNull,则可以选择将DataTable中的所有DBNull值转换为空字符串。

    这样做非常简单,但它会增加一些开销,尤其是在处理大型DataTable时。如果您有兴趣,请查看显示如何操作的链接


    推荐阅读