NodaTime with MongoDB: NodaTime.ZonedDateTime Value Class Cannot Be Deserialized

I use the latest versions of NodaTime and the Official Mongo DB driver . I have a simple POCO class that uses NodaTime ZonedDateTime as a replacement for .NET DateTime in multiple properties.

 public class MyPOCO { [BsonId] [Key] public ObjectId SomeId { get; set; } public string SomeProperty { get; set; } public ZonedDateTime SomeDateTime { get; set; } } 

I can easily add the model to the collection, but when I try to read the requested models, I get the following MongoDB.Bson.BsonSerializationException :

NodaTime.ZonedDateTime Value Class Cannot Be Deserialized

What is the good or best practice to solve / solve this problem?

UPDATE

After publishing my solution to the problem, I ran into a possible new problem ... When I request a collection and use DateTime in my request, for example where SomeDateTime < now' (where now is 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 seems like a big performance issue, right? I really need to think about returning to BCL DateTime again, even if it hurts.

UPDATE 2

I make a decision using the ZonedDateTimeSerializer , but I don’t feel comfortable with NodaTime in combination with MongoDB, while both are great individual solutions. But they just do not work well together at the moment without heavy manipulation.

+4
source share
2 answers

After long readings and experiments, Nevermind finally found it. I wrote a custom implementation of BsonBaseSerializer 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); } } 

Remember to register the Serializer. I could not learn how to register a Serializer for each type, but you can register it for each type, which looks like this:

 BsonClassMap.RegisterClassMap<MyPOCO>(cm => { cm.AutoMap(); cm.GetMemberMap(a => a.SomeDateTime).SetSerializer(ZonedDateTimeSerializer.Instance); }); 

Hope this helps.

+5
source

The following is a modified version of the thmshd class, which also stores time zone information:

 public class ZonedDateTimeSerializer : IBsonSerializer<ZonedDateTime> { public static ZonedDateTimeSerializer Instance { get; } = new ZonedDateTimeSerializer(); object IBsonSerializer.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { return Deserialize(context, args); } public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ZonedDateTime value) { if (value == null) throw new ArgumentNullException(nameof(value)); var zonedDateTime = value; SerializeAsDocument(context, zonedDateTime); } private static void SerializeAsDocument(BsonSerializationContext context, ZonedDateTime zonedDateTime) { context.Writer.WriteStartDocument(); context.Writer.WriteString("tz", zonedDateTime.Zone.Id); context.Writer.WriteInt64("ticks", zonedDateTime.ToInstant().Ticks); context.Writer.WriteEndDocument(); } public ZonedDateTime Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonType = context.Reader.GetCurrentBsonType(); if (bsonType != BsonType.Document) { throw new InvalidOperationException($"Cannot deserialize ZonedDateTime from BsonType {bsonType}."); } context.Reader.ReadStartDocument(); var timezoneId = context.Reader.ReadString("tz"); var ticks = context.Reader.ReadInt64("ticks"); var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId); if (timezone == null) { throw new Exception($"Unknown timezone id: {timezoneId}"); } context.Reader.ReadEndDocument(); return new Instant(ticks).InZone(timezone); } public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) { if (value == null) { throw new ArgumentNullException(nameof(value)); } var zonedDateTime = (ZonedDateTime)value; SerializeAsDocument(context, zonedDateTime); } public Type ValueType => typeof(ZonedDateTime); } 

It can be registered globally as follows:

 BsonSerializer.RegisterSerializer(ZonedDateTimeSerializer.Instance); 

Edit: Instead of serializing for a subdocument, it is better to use NodaTimes built-in parsing.

Serialization:

 context.Writer.WriteString(ZonedDateTimePattern.CreateWithInvariantCulture("G", DateTimeZoneProviders.Tzdb).Format(zonedDateTime)); 

Deserialize:

  var zonedDateTimeString = context.Reader.ReadString(); var parseResult = ZonedDateTimePattern.CreateWithInvariantCulture("G", DateTimeZoneProviders.Tzdb)n.Parse(zonedDateTimeString); if (!parseResult.Success) { throw parseResult.Exception; } return parseResult.Value; 
+1
source

Source: https://habr.com/ru/post/1493678/


All Articles