Working with nullable DateTime

3,904 views
Skip to first unread message

Pepri

unread,
Jul 29, 2011, 7:31:01 AM7/29/11
to ravendb
Hi all,

I am storing documents that contain a nullable DateTime field named
From. I would like to create an index with its value based on this
field - I want to use only its Date part and in case when it is set to
null, the DateTime.MinValue should be used in the Map definition. I
have problems to do this because of different data types on the server
and the client.

Here are my class definitions:
public class Item
{
public DateTime? From { get; set; }
}

public class ItemInfo
{
public DateTime Part { get; set; }
}

public class Items : AbstractIndexCreationTask<Item, ItemInfo>
{
public Items()
{
Map = items =>
from item in items
select new
{
Part = ...
};

Store(x => x.Part, FieldStorage.Yes);
}
}

Attempt #1: Part = item.From != null ? item.From.Value.Date :
DateTime.MinValue,

This is the classic way used in C# to handle the case. But if I use
the Value property to access the DateTime value in the From field, the
evaluation fails with the RuntimeBinderException exception with its
message set to "'System.DateTimeOffset' does not contain a definition
for 'Value'.". This is understandable because the item.From does not
have any Value field on the server.

Attempt #2: Part = item.From != null ?
Convert.ToDateTime(item.From).Date : DateTime.MinValue

If I use the Convert.ToDateTime, the evaluation fails with the
InvalidCastException with its message set to "Unable to cast object of
type 'System.DateTimeOffset' to type 'System.IConvertible'.". I think,
this used to work in previous builds. Now the data type on the server
is DateTime offset, so next to the third attempt.

Attempt #3: Part = item.From != null ?
((DateTimeOffset)item.From).Date : DateTime.MinValue

On the server, the item.From is DateTimeOffset, so I just cast it to
DateTimeOffset and get the Date value. This fails with the
ArgumentOutOfRangeException exception with its message set to "The UTC
time represented when the offset is applied must be between year 0 and
10,000.". This exception is followed by RuntimeBinderException
exception with its message set to "Cannot convert type
'System.DateTimeOffset' to 'System.DateTime'".

Attempt #4: Part = item.From != null ?
DateTime.Parse(item.From.ToString()).Date : DateTime.MinValue

Finally, this one works.

My question is:
Am I supposed to work with the nullable DateTime this way?


Here is the test method that was used:
[TestMethod]
public void CanWorkWithNullableDateTime()
{
using (IDocumentStore store = NewDocumentStore())
{
DateTime now = new DateTime(2011, 7, 29, 12, 0, 0);
using (IDocumentSession session = store.OpenSession())
{
Item[] items = new[]
{
new Item { From = null },
new Item { From = now },
};
foreach (Item item in items)
session.Store(item);
session.SaveChanges();
}

new Items().Execute(store);

using (IDocumentSession session = store.OpenSession())
{
ItemInfo[] items = session
.Query<Item, Items>()
.Customize(x => x.WaitForNonStaleResults())
.AsProjection<ItemInfo>()
.ToArray();
Assert.AreEqual(2, items.Length);
}
}
}

Itamar Syn-Hershko

unread,
Jul 29, 2011, 9:10:57 AM7/29/11
to rav...@googlegroups.com
Pepri, this has nothing to do with the server since the server is type agnostic. Probably just some C# kong fu.

What does come to mind is the reason for using a nullable type here - if you need the nullable type to be indexed as DateTime.MinValue, why can't you use DateTime.MinValue to represent a null DateTime with a standard non-nullable DataTime variable?

Ayende Rahien

unread,
Jul 29, 2011, 9:35:36 AM7/29/11
to rav...@googlegroups.com
Pepri,
You can use;

item.From.Date ?? DateTime.MinValue

On Fri, Jul 29, 2011 at 2:31 PM, Pepri <peto.p...@gmail.com> wrote:

Tomáš Zeman

unread,
Jul 29, 2011, 9:45:24 AM7/29/11
to ravendb
So, the recommandation at top of ravendb's docs should be: NEVER use
nullable value types or you get into troubles, instead use old fashion
MinValue solution. If you made this mistake -> here is a "beautiful"
workaround from @pepri DateTime.Parse(item.From.ToString()).Date
or is there something that smells less?


On Jul 29, 3:10 pm, Itamar Syn-Hershko <ita...@hibernatingrhinos.com>
wrote:

Itamar Syn-Hershko

unread,
Jul 29, 2011, 9:50:38 AM7/29/11
to rav...@googlegroups.com
Not sure what makes you say all that. In that specific case, yes - as far as I can see it makes more sense to use MinValue instead of making this a nullable value. If Pepri wants to keep this implementation - then there are nicer ways to make this work. See Ayende's reply.

2011/7/29 Tomáš Zeman <tzem...@gmail.com>

Tomáš Zeman

unread,
Jul 29, 2011, 9:51:30 AM7/29/11
to ravendb
Sure not!

Pepri

unread,
Jul 29, 2011, 10:02:54 AM7/29/11
to ravendb
Itamar,
I have a lot of POCO classes that have nullable DateTime fields for
communication using JSON in WCF service. In this specific index, the
null value may be considered to be same as DateTime.MinValue, but this
has not to be the same for another index over the very same document.
Yes, I can handle it with always comparing to MinValue, but it is
counterintuitive. There are also issues with DateTime.MinValue when it
comes to JSON serialization in WCF, so I rather would not change the
logic of the documents.

Ayende,
this cannot does not even compile, because item.From is
Nullable<DateTime> and it does not have any "Date" property.

Pepri

unread,
Jul 29, 2011, 10:31:08 AM7/29/11
to ravendb
Attempt #5: Part = (item.From ?? DateTime.MinValue).Date
This does compile, it passes the test, but the results are wrong. The
date contains not only the date part. I consider this more dangerous.
After adding these assert, the test fails:
Assert.AreEqual(items[0].Part, DateTime.MinValue);
Assert.AreEqual(items[1].Part, now.Date);


On 29. Júl, 15:35 h., Ayende Rahien <aye...@ayende.com> wrote:

Itamar Syn-Hershko

unread,
Jul 29, 2011, 11:28:11 AM7/29/11
to rav...@googlegroups.com
Ok, lets take this one step at a time.

The following test does not throw any errors like you describe. I'm pretty sure the logic is complete and it should be working, but for some reason the first document is always stored in the index as null. It's either we convert DateTime.MinValue to null internally, or something gets lost in translation. We will have a look at it by Sunday.

public class Doc
{
public string Id { get; set; }
public DateTime? Date { get; set; }
}

public class DocsByDate : AbstractIndexCreationTask<Doc>
{
public DocsByDate()
{
Map = docs =>
from doc in docs
let datetime = (doc.Date != null) ? doc.Date.Value : DateTime.MinValue
select new
{
Date = datetime
};

Store(x => x.Date, FieldStorage.Yes);
}
}

[Fact]
public void CanWorkWithNullableDateTime()
{
// Test assumptions
var testdoc = new Doc { Date = null };
Assert.Null(testdoc.Date);
var now = new DateTime(2011, 7, 29, 12, 0, 0);
testdoc = new Doc {Date = now};
Assert.NotNull(testdoc.Date);
Assert.Equal(testdoc.Date.Value, new DateTime(2011, 7, 29, 12, 0, 0));

using (var store = NewDocumentStore())
{
new DocsByDate().Execute(store);

using (var session = store.OpenSession())
{
var items = new[]
            {
            new Doc {Date = null},
            new Doc {Date = now},
            };
foreach (var item in items)
session.Store(item);
session.SaveChanges();
}

using (var session = store.OpenSession())
{
var items = session
.Query<Doc, DocsByDate>()
.Customize(x => x.WaitForNonStaleResultsAsOfLastWrite())
.ToArray();

WaitForUserToContinueTheTest(store);

Assert.Equal(2, items.Length);
}
}
}

Ayende Rahien

unread,
Jul 30, 2011, 7:34:15 AM7/30/11
to rav...@googlegroups.com
I am afraid that i am not following you here.

2011/7/29 Tomáš Zeman <tzem...@gmail.com>

Ayende Rahien

unread,
Jul 31, 2011, 2:15:43 AM7/31/11
to rav...@googlegroups.com
The problem is with this line:

let datetime = (doc.Date != null) ? doc.Date.Value : DateTime.MinValue
On the server, we don't know whatever you are using nullable or standard date time.
So .Value is going to throw.

If you use:
let datetime = doc.Date ?? DateTime.MinValue

It works.

Here is the passing test:

public class DocsByDate : AbstractIndexCreationTask<Doc> { public DocsByDate() { Map = docs => from doc in docs let datetime = doc.Date ?? DateTime.MinValue select new { Date = datetime }; Store(x => x.Date, FieldStorage.Yes); } } [Fact] public void CanWorkWithNullableDateTime() { using (var store = NewDocumentStore()) { new DocsByDate().Execute(store); using (var session = store.OpenSession()) { var items = new[] { new Doc {Date = null}, new Doc {Date = DateTime.Now}, }; foreach (var item in items) session.Store(item); session.SaveChanges(); } using (var session = store.OpenSession()) { var items = session .Query<Doc, DocsByDate>() .Customize(x => x.WaitForNonStaleResultsAsOfLastWrite()) .ToArray(); WaitForUserToContinueTheTest(store); Assert.Equal(2, items.Length); } } }
Reply all
Reply to author
Forward
0 new messages