I'm using latest versions of NodaTime and Mongo DB Official Driver. I have a simple POCO class which uses NodaTime's ZonedDateTime
as a replacement for the .NET DateTime in a few Properties.
public class MyPOCO
{
[BsonId]
[Key]
public ObjectId SomeId { get; set; }
public string SomeProperty { get; set; }
public ZonedDateTime SomeDateTime { get; set; }
}
I can put the Model into the collection easily, but when I try to read queried Models, I get the following MongoDB.Bson.BsonSerializationException
:
Value class NodaTime.ZonedDateTime cannot be deserialized
What`s a good or the best practice to solve/work around this issue?
UPDATE
After posting my solution to the problem, I'm facing a possible new issue... When I query the collection and use the DateTime in my query, like where SomeDateTime < now' (where
nowis a variable I create from system time) it seems that each document must be deserialized using my
ZonedDateTimeSerializer` before the where clause can be evaluated. This looks like a big performance issue, doesn't it? I really have to think about going back to the BCL DateTime again, even if it hurts.
UPDATE 2
I'm accepting my solution using ZonedDateTimeSerializer
, but I'm not feeling comfortable with NodaTime in combination with MongoDB, while both are great individual solutions. But they just don't work well together at the moment without heavy manipulation.
Nevermind, finally after much reading and experimenting, found it finally out. I wrote a custom BsonBaseSerializer
implementation to handle ZonedDateTime
.
Here is the code of my ZonedDateTimeSerializer
:
/// <summary>
/// Serializer for the Noda
/// </summary>
public class ZonedDateTimeSerializer : BsonBaseSerializer
{
private static ZonedDateTimeSerializer __instance = new ZonedDateTimeSerializer();
/// <summary>
/// Initializes a new instance of the ZonedDateTimeSerializer class.
/// </summary>
public ZonedDateTimeSerializer()
{
}
/// <summary>
/// Gets an instance of the ZonedDateTimeSerializer class.
/// </summary>
public static ZonedDateTimeSerializer Instance
{
get { return __instance; }
}
/// <summary>
/// Deserializes an object from a BsonReader.
/// </summary>
/// <param name="bsonReader">The BsonReader.</param>
/// <param name="nominalType">The nominal type of the object.</param>
/// <param name="actualType">The actual type of the object.</param>
/// <param name="options">The serialization options.</param>
/// <returns>
/// An object.
/// </returns>
public override object Deserialize(BsonReader bsonReader, Type nominalType, Type actualType, IBsonSerializationOptions options)
{
VerifyTypes(nominalType, actualType, typeof(ZonedDateTime));
var bsonType = bsonReader.GetCurrentBsonType();
if (bsonType == BsonType.DateTime)
{
var millisecondsSinceEpoch = bsonReader.ReadDateTime();
return new Instant(millisecondsSinceEpoch).InUtc();
}
throw new InvalidOperationException(string.Format("Cannot deserialize ZonedDateTime from BsonType {0}.", bsonType));
}
/// <summary>
/// Serializes an object to a BsonWriter.
/// </summary>
/// <param name="bsonWriter">The BsonWriter.</param>
/// <param name="nominalType">The nominal type.</param>
/// <param name="value">The object.</param>
/// <param name="options">The serialization options.</param>
public override void Serialize(BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
if (value == null)
throw new ArgumentNullException("value");
var ZonedDateTime = (ZonedDateTime)value;
bsonWriter.WriteDateTime(ZonedDateTime.ToInstant().Ticks);
}
}
Don't forget to register the Serializer. I wasn't able to find out how to register the Serializer per type, but you can register it per type, which looks like this:
BsonClassMap.RegisterClassMap<MyPOCO>(cm =>
{
cm.AutoMap();
cm.GetMemberMap(a => a.SomeDateTime).SetSerializer(ZonedDateTimeSerializer.Instance);
});
Hope this helps.