可空的 DateTime 与 SQLDataReader

25

我几乎不想问这个问题,好像已经有无数次被问到了,但是即使我研究了其他问题,我仍然不能在我的情况下弄清楚这个问题。

我读到DateTime是可空类型,我尝试了一些示例,但我正在尝试弄清楚它是否为NULL在数据库中,我的SQLDATAREADER失败了。

错误

System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot 'be called on Null values.'

DetailsClass

private DateTime? startingDate;

public DateTime? StartingDate
{
    get{ return startingDate; }
    set{ startingDate = value; }
}

// constructor
Public DetailsClass(DateTime? startingDate)
{
    this.startingDate = startingDate;
}

DBClass

   using (SqlConnection con = new SqlConnection(connectionString))
            using (SqlCommand cmd = con.CreateCommand())
            {

                List<DetailsClass> details = new List<DetailsClass>();
                DetailsClass dtl;
                try
                {
                    con.Open();
                    cmd.CommandText = "Stored Procedure Name";
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.AddWithValue("@MyParameter", myparameter);

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            dtl = new DetailsClass((
                                reader.GetInt32(reader.GetOrdinal("MEMBERSHIPGEN"))),
                                reader.IsDBNull(1) ? null : reader.GetString(reader.GetOrdinal("EMAIL")),
                                reader.GetDateTime(reader.GetOrdinal("STARTINGDATE")));


                            details.Add(dtl);
                        }
                        reader.Close();
                        return details;

                    }
                }

2
你读错了。DateTime 不是可空类型,DateTime? 才是。 - Corak
我现在明白你的意思了,只是忘记加问号了。 - Jay
5个回答

56
这里提供了一个辅助方法,可以从阅读器中获取值。
public static class ReaderExtensions {

  public static DateTime? GetNullableDateTime(this SqlDataReader reader, string name){ 
       var col = reader.GetOrdinal(name);
       return reader.IsDBNull(col) ? 
                   (DateTime?)null :
                   (DateTime?)reader.GetDateTime(col);
  }
}

如何根据评论更新使用方法

using (SqlConnection con = new SqlConnection(connectionString))
        using (SqlCommand cmd = con.CreateCommand())
        {

            List<DetailsClass> details = new List<DetailsClass>();
            DetailsClass dtl;
            try
            {
                con.Open();
                cmd.CommandText = "Stored Procedure Name";
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@MyParameter", myparameter);

                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        dtl = new DetailsClass((
                            reader.GetInt32(reader.GetOrdinal("MEMBERSHIPGEN"))),
                            reader.IsDBNull(1) ? null : reader.GetString(reader.GetOrdinal("EMAIL")),
                            reader.GetNullableDateTime("STARTINGDATE"));


                        details.Add(dtl);
                    }
                    reader.Close();
                    return details;

                }
            }

请注意,您正在使用reader.IsDBNull(1)reader.GetOrdinal。 可能应该改为reader.IsDBNull(reader.GetOrdinal("EMAIL"))


@Jay 因为我打错了字,应该是 IsDBNull,现在已经更正。 - Bob Vale
我失去了它,无法相信我错过了那个...感谢您的帮助,还有一个问题,您在("STARTINGDATE");之后有一个;来结束语句,但我需要放一个逗号,这样我就可以向读取器添加更多列,如果您看一下我的代码,我没有将startdatetime分配给读取器,我该如何实现它。 - Jay
@Jay的回答已更新,还请查看关于电子邮件读取代码结尾的评论。 - Bob Vale
再次感谢,我根据最新的更新将您的答案标记为正确。感谢大家的帮助,这是一项使命。 - Jay
嗨,我也遇到了同样的问题,这个方法很有效 +1。我添加了一个列版本 public static DateTime? GetNullableDateTime(this SqlDataReader reader, int name) { var col =name; return reader.IsDBNull(col) ? (DateTime?) null: (DateTime?)reader.GetDateTime(col); } - Ggalla1779
显示剩余4条评论

4

我知道这个问题已经得到了答复,但是为了处理其他可空变量类型,需要进一步简化代码。上面的答案仅限于一个特定的变量类型。好处在于它还允许你包含一个默认值,而不是将其设置为 null。

public static T GetDataType<T>( this SqlDataReader r, string name, object def = null )
{
     var col = r.GetOrdinal(name);
     return r.IsDBNull(col) ? (T)def : (T)r[name];
}

然后在代码中,你可以使用如下

...
while(reader.Read())
{
    data1Bool = reader.GetDataType<bool?>("data1"); // if it's null then we'll set it as null
    data2intNotNull = reader.GetDataType<int>("data2", 0); // if the data is null, then we'll set to 0
    data3date = reader.GetDataType<DateTime?>("data3", DateTime.Now); // same example as above, but instead of setting to 0, I can default it to today's date.
}
...

我曾遇到过关于可空值对象的问题,而这个解决了我的难题。在尝试理解程序运行时发生了什么的几个小时中,WPF并没有给出明确的解释。


1
虽然这可能回答了作者的问题,但它缺少一些解释性的词语和文档链接。裸代码片段没有周围的一些短语是不太有帮助的。请编辑您的答案。 - hellow
谢谢,我已经修改了我的代码。如果还有其他需要改进的地方,请告诉我。 - user3882848
1
这段代码正是我一直在寻找的,而且它非常精美。 - zapoo

4
替换
DateTime startingDate;

使用

DateTime? startingDate;

问题标记将其标记为可空值,您的读者应该能够将startingdate设置为null而不是抛出异常。
在读取器工作时,您还可以检查null值并将其替换为空字符串。
while(reader.read())
{
  //column is an int value of your column. I.e: if the column ist the 8th column, set column to 7 (0-based)
  StartingDate = (reader.IsDBNull(column)) ? null : reader.GetOrdinal("STARTINGDATE"));
 //instead of null you could also return a specific date like 1.1.1900 or String.Empty
}

我根据您的建议编辑了我的代码,但仍然出现相同的错误。我知道是这个字段导致了错误,因为当我在数据库中放置一个值时,它可以正常填充。 - Jay
3
这段代码有误:reader.GetOrdinal获取名为"STARTINGDATE"的字段的整数索引,因此你为什么要将它赋值给一个DateTime字段? - Joe
details.StartingDate = reader.IsDBNull(15) ? null : reader.GetDateTime(reader.GetOrdinal("STARTINGDATE")); 接收到无法进行隐式转换的 null 和 system.datetime。 - Jay

1

请问您在调试时能否告诉我们哪一行代码出现了错误,这样会更容易排查问题。

if (! reader.IsDBNull(reader.GetOrdinal("STARTINGDATE"))) {
    obj.startingDate = reader.GetDateTime(reader.GetOrdinal("STARTINGDATE"));
}

不需要显式地将null赋给DBNull,因为它是可为空的类型,所以默认情况下会包含null。
根据您更新的代码(请注意注释):
while (reader.Read()) {
    dtl = new DetailsClass((reader.GetInt32(reader.GetOrdinal("MEMBERSHIPGEN"))),

    // here you are checking null for email
    reader.IsDBNull(1) ? null : reader.GetString(reader.GetOrdinal("EMAIL")),

    // here you are not checking null for startingdate ?
    reader.GetDateTime(reader.GetOrdinal("STARTINGDATE")));
    details.Add(dtl);
}

让我们以更详细的方式尝试一下:

while (reader.Read()) {

    dtl = new DetailsClass();

    dtl.membershipgen = reader.IsDBNull(reader.GetOrdinal("MEMBERSHIPGEN")) ? null : reader.GetInt32(reader.GetOrdinal("MEMBERSHIPGEN"));
    dtl.email = reader.IsDBNull(reader.GetOrdinal("EMAIL")) ? null : reader.GetString(reader.GetOrdinal("EMAIL")),
    dtl.startingdate = reader.IsDBNull(reader.GetOrdinal("STARTINGDATE")) ? null : reader.GetDateTime(reader.GetOrdinal("STARTINGDATE")));

    details.Add(dtl);
}

这是一个列表集合,所以当我调试它时,它会突出显示我的整个集合并显示我发布的上述错误,但在我进行一些操作后,一旦我在数据库中的startingdate列中放入一个值,一切都正常了,所以我知道问题出在我的sqldatareader的starting date行。 - Jay
@abhitalks,你需要将你的null转换为DateTime?,否则编译器会抛出一个转换异常。 - Bob Vale
可以把答案给两个人吗? - Jay
@Bob 哦,是的。来自 VB 的世界 :) - Abhitalks
@Jay 没问题。你的问题已经解决了,这才是最重要的。 - Abhitalks

0
尝试这样做:
将变量 startingDate 设置为可为空,就像这样:
DateTime? startingDate;

现在,当从SqlDataReader对象检索值时,您需要使用IsDbNull方法,该方法将确定从数据库返回的值是否为NULL,如下所示:

if !reader.IsDBNull(reader.GetOrdinal("STARTINGDATE"))
{
    startingDate = reader.GetDateTime(reader.GetOrdinal("STARTINGDATE"));
}
else
{
    startingDate = null;
}

注意:我不确定您在哪里将startingDate分配给从数据库读取的值,因为在发布的代码中没有显示。这就是为什么我在我的示例中直接分配它的原因,但您可能需要调整示例逻辑的位置。

感谢您的回复,将此代码直接放入读取器中,MiddleName是什么? - Jay
我会发布我的代码,以便您可以看到我尝试展示我已经做了一些工作来隔离我的问题,所以他们不认为我只是来这里要求帮助,但我感谢您的帮助,谢谢。 - Jay
我发布了我的代码,不是全部,但是我认为你需要的部分。我列出了我的详细信息类、构造函数和我的DB类以及代码。再次感谢。 - Jay
@Karl,你正在检查字段MiddleName是否为空,然后获取STARTINGDATE,同时你可能需要反转检查。 - Bob Vale
@BobVale - 是的,谢谢,这是我从其他地方复制粘贴的代码。现在正在更新。 - Karl Anderson

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