在C#中处理DBNull

41

有更好/更清晰的方法来做这件事吗?

int stockvalue = 0;
if (!Convert.IsDBNull(reader["StockValue"]))
    stockvalue = (int)reader["StockValue"];

你还应该考虑扩展方法。以下是提供其他可能方式的几个示例: http://shahanayyub.wordpress.com/2012/10/04/best-practice-to-check-for-dbnull-using-net/ - NeverHopeless
13个回答

65

我认为最短的是:

int stockvalue = (reader["StockValue"] as int?) ?? 0;

解释:

  • 如果reader["StockValue"]的类型是int,则返回其值,"??"运算符将返回结果。
  • 如果reader["StockValue"]的类型不是int(例如 DBNull),则返回 null,并且"??"运算符将返回 0(零)。

3
如果将列的类型更改为Double或其他类型,会发生什么? - John Saunders
1
+1 - 这是我编写代码的方式,尽管如果空值是有效的,则不需要默认值;int? stockvalue = reader["StockValue"] as int? - stevehipwell
2
@John:它将返回0。但无论你选择什么解决方案,如果一列更改了数据类型,你就会出现问题,除非你加入Convert.ToInt32()或类似的调用。 - Philippe Leybaert
4
但如果使用显式的拆箱转换 int x = (int)obj,当列的类型更改时,您的代码将会出错,因此您需要将其更正为 double x = (double)obj。@JohnSaunders 的警示是正确的,因为使用 as int? 方法将悄无声息地吞噬这种变化;只有当有人注意到一个不应该存在的零值时,您才能发现它。 - phoog
phoog 是完全正确的。如果一列不是你所期望的 int 类型,结果总是 0。想象一下其他人试图阅读这段代码,以找出为什么结果总是 0?Digicoder 下面的解决方案更易于阅读和更灵活。只需使用基本的 if 简写即可。 - Frug

31

我处理这个问题的方式是

int? stockvalue = reader["StockValue"] as int?;

非常简单、清晰且只有一行代码。如果由于某种原因我绝对不能有空值(通常来说我认为这是不好的理由,因为我更想知道一个值是否有意义,或者是否对原始类型进行了初始化),那么我会这样做:

int stockvalue = (reader["StockValue"] as int?).GetValueOrDefault(-1);

啊,我在使用 (int?) reader["ColumnName"] 时遇到了一个异常,于是开始寻找正确的做法。这应该被接受为答案。 - Anlo
很好。这也保留了DBNull状态,以防有人对此感兴趣。其他一些解决方案会丢弃它。 - Jonas

13
我几天前编写了一个扩展方法。使用它,您只需执行以下操作:
int? stockvalue = reader.GetValue<int?>("StockValue");

这是一个扩展方法(根据您的需求进行修改):

public static class ReaderHelper
{
    public static bool IsNullableType(Type valueType)
    {
        return (valueType.IsGenericType &&
            valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
    }

    public static T GetValue<T>(this IDataReader reader, string columnName)
    {
        object value = reader[columnName];
        Type valueType = typeof(T);
        if (value != DBNull.Value)
        {
            if (!IsNullableType(valueType))
            {
                return (T)Convert.ChangeType(value, valueType);
            }
            else
            {
                NullableConverter nc = new NullableConverter(valueType);
                return (T)Convert.ChangeType(value, nc.UnderlyingType);
            }
        }
        return default(T);
    }
}

2
我非常喜欢C#,并且在某些地方有类似于这样的代码,但我们需要编写这样的代码实在是太愚蠢了。我希望随着DLR的不断发展,总有一天我们不需要编写这样的代码来进行类型转换。 - Chris Marisic

10
int? stockvalue = (int?)(!Convert.IsDBNull(result) ? result : null);

以下是一种可能的解决方案,以确保DBNull可以传递到您的代码中。对于我们的团队,最佳实践是尽量避免在数据库中使用NULL列,除非确实需要。处理它需要更多的编码开销,有时候重新思考问题会使其不再需要。


我认为这个解决方案最清晰和灵活。我选择了几乎相同但不允许空值的东西: Int32 aNum = Convert.IsDBNull(row["stockvalue"]) ? 0 : Convert.ToInt32(row["stockvalue"]) - Frug

7
是的,您可以使用 int? 这种方式,这样您就可以有一个默认值为 null 而不是 0。由于 stockvalue 的结果可能是 0,因此不会混淆数据库是否为 0 或 null。例如,像这样(可空之前),我们有一个默认初始化为 -1 来表示没有分配值。就我个人而言,我认为这有点危险,因为如果您忘记将其设置为 -1,则可能存在数据损坏问题,难以跟踪。
链接:http://msdn.microsoft.com/en-us/library/2cf62fcy(VS.80).aspx
int? stockvalue = null;

if (!Convert.IsDBNull(reader["StockValue"]))
    stockvalue = (int)reader["StockValue"];

//Then you can check 

if(stockValue.HasValue)
{
  // do something here.
}

2
+1 表示使用正确的数据类型。但是,如果我没记错的话,DBNull 仍然需要进行一些检查才能转换为 null - Jon Seigel
1
使用 int? 怎么做?只是将 reader["StockValue"] 强制转换为 (int?) ,当 reader["StockValue"] 为 DBNull 时会抛出异常。 - Philippe Leybaert
你可以使用int?,这样就不必使用默认初始化为0了。 - kemiller2002

7

虽然引用reader["StockValue"]很方便,但并不是很高效。它也没有强类型检查,因为返回的类型是object

相反,在您的代码中,可以像这样进行操作:

int stockValueOrdinal = reader.GetOrdinal("StockValue");
int? stockValue = reader.IsDbNull(stockValueOrdinal) ?
    null : 
    reader.GetInt32(stockValueOrdinal);

当然,最好一次性获取所有序数,然后在整个代码中使用它们。

@John:在你对我的回答中的评论中,你问我“如果列改成double会怎样?”。这里提供的代码将抛出一个异常,如果该列保存的值不是int类型。(另外,小细节:数据读取器上没有.GetInt()方法。应该是.GetInt32()) - Philippe Leybaert
3
抛出异常是代码在列类型更改且代码未能处理时应该执行的操作,这正是其要做的。当出现严重错误时返回零是一个非常糟糕的想法。 - John Saunders

3
int stockvalue = reader["StockValue"] != DbNull.Value ? Convert.ToInt32(reader["StockValue"]) : 0;

1

这是一种方法。

int stockvalue = Convert.IsDbNull(reader["StockValue"]) ? 0 : (int)reader["StockValue"];

你也可以使用 TryParse

int stockvalue = 0
Int32.TryParse(reader["StockValue"].ToString(), out stockvalue);

请告诉我们哪种方式适合您


我不介意被踩,只是请留下评论告诉我如何改进这个答案。 - sidney.andrews
1
这里绝对没有必要使用DV,因为你的答案是正确的解决方案。 - Chris Marisic
没问题,我已经给你点赞来平衡一下了。 :-) - Brian Scott
1
我没有给你的代码投反对票,但这确实是一段非常低效的代码。你的第一行代码调用了reader[]索引器两次。第二行使用了非常低效的字符串转换,完全没有必要。 - Philippe Leybaert
好观点,我通常不在我的应用程序中使用任何类型的数据读取器。相反,选择基于对象的后端数据表示方式。 - sidney.andrews

1

你可以直接在数据库查询中进行此转换,从而完全避免特殊情况。

但是,除非您可以在代码中一致地使用该形式,否则我不会称其为“更清晰”,因为您将通过从数据库返回“0”而不是NULL来丢失信息。


关于信息丢失的争论是有道理的。在数据库应用中,“0”的值与NULL不同。一个表示完全没有值,而另一个是恰巧为零的值。 - sidney.andrews

0

不一定。你可以将它封装在一个方法中:

public int getDBIntValue(object value, int defaultValue) {
  if (!Convert.IsDBNull(value)) {
    return (int)value;
  }
  else {
    return defaultValue;
  }

并像这样调用它:

stockVaue = getDBIntVaue(reader["StockValue"], 0);

或者您可以在查询中使用coalesce来强制返回的值为非空。

编辑 - 根据收到的评论纠正了愚蠢的代码错误。


1
(我没有给你投反对票,但)首先,在“value”参数中传递了reader [“StockValue”],但是在函数内部忽略了“value”参数,而是使用了“reader [“StockValue”]”,因此它无法编译。 - Adam V
@Ray: 你的 getDBIntValue 方法会尝试从 reader["StockValue"] 中提取 int,无论传入的值是什么。而且你的参数列表中有一个关键字 default,这是不能编译通过的(你需要将其改为 @default,或者更好的方法是更改名称)。 - Dan Tao
@John - 我纠正了一个复制粘贴错误。然而,“如果你不知道,我就不告诉你”的说法并没有帮助我(或任何其他阅读这些评论的人)学习。 - Ray
@Dan:他可能意识到它总是会得到一个整数,因此命名为getDBIntVaue(顺便说一句,Ray,请添加缺失的“l”。拼写错误让我感到不安)。 - Adam V
这会教会我不要在检查代码之前就随便打字和发布 - 对于愚蠢的错误感到抱歉 - 我认为现在已经修复了。 - Ray
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接