2013-07-26 3 views
4

Я использую последние версии NodaTime и Mongo DB Official Driver. У меня есть простой класс POCO, который использует NodaTime ZonedDateTime в качестве замены для .NET DateTime в нескольких свойствах.NodaTime с MongoDB: Класс ценности NodaTime.ZonedDateTime нельзя десеризировать

public class MyPOCO 
{ 
    [BsonId] 
    [Key] 
    public ObjectId SomeId { get; set; } 

    public string SomeProperty { get; set; } 

    public ZonedDateTime SomeDateTime { get; set; } 
} 

Я могу поставить модель в коллекции легко, но когда я пытаюсь читать запрошенные модели, я получаю следующее MongoDB.Bson.BsonSerializationException: класс

Значение NodaTime.ZonedDateTime не может быть десериализован

Какая хорошая или лучшая практика для решения этой проблемы?

UPDATE

После размещения моего solution проблемы, я столкнулся возможный новый вопрос ... Когда я запрашиваю коллекцию и использовать DateTime в моем запросе, как where SomeDateTime < now' (where теперь is a variable I create from system time) it seems that each document must be deserialized using my ZonedDateTimeSerializer` перед тем предложение where может быть оценено. Это похоже на большую проблему с производительностью, не так ли? Мне действительно нужно думать о возвращении к BCL DateTime снова, даже если это больно.

UPDATE 2

Я принял мое решение, используя ZonedDateTimeSerializer, но я не чувствую себя комфортно с NodaTime в сочетании с MongoDB, в то время как большие индивидуальные решения. Но на данный момент они просто не работают хорошо, без тяжелых манипуляций.

+1

базы данных сериализации всегда будет конкретная реализация. Вы можете проверить те, что я сделал для [Noda Time с RavenDB] (https://github.com/mj1856/RavenDB-NodaTime). Я также работаю над (еще не готова) поддержкой SQL Server. Мы были бы в восторге от поддержки MongoDB, если вы хотели его развивать. –

+0

Кроме того, 'NodaTime.Serialization.JsonNet' почти завершен. Проверьте дерево источников Noda Time. Вы можете начать там, но если MongoDB ожидает BSON, вам все равно придется переводить. –

ответ

5

Ничего, наконец, после долгих чтений и экспериментов, нашел его наконец. Я написал пользовательскую реализацию BsonBaseSerializer для обработки ZonedDateTime.

Вот код моего 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); 
    } 
} 

Не забудьте зарегистрировать Serializer. Я не был в состоянии выяснить, как зарегистрировать Serializer каждого типа, но вы можете зарегистрировать его на тип, который выглядит следующим образом:

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

Надеется, что это помогает.

+2

Это похоже на сериализацию только эквивалента 'Instant', правильно? Я бы подумал, что вы захотите сериализовать пару строк DateTimeZone.Id и значение «OffsetDateTime». В противном случае, почему бы просто не использовать тип «Мгновенный» для начала? –

1

Вот модифицированная версия thmshd's класса, который хранит информацию о временных зонах, а также:

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); 
} 

Это может быть глобально зарегистрирован как так:

BsonSerializer.RegisterSerializer(ZonedDateTimeSerializer.Instance); 

Edit: Вместо сериализация в поддокумент, возможно, лучше использовать встроенный синтаксический анализ NodaTimes.

Serialize:

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; 

 Смежные вопросы

  • Нет связанных вопросов^_^