我正在使用NodaTime和Mongo DB Official Driver的最新版本。我有一个简单的POCO类,它使用NodaTime的ZonedDateTime
代替几个属性中的.NET DateTime。
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无法反序列化
解决/解决此问题的好或最佳做法是什么?
更新
在我的solution发布问题之后,我面临一个可能的新问题......当我查询集合并在我的查询中使用DateTime时,如where SomeDateTime < now' (where
现在{{1}可以评估where子句之前的ZonedDateTimeSerializer`。这看起来像是一个很大的性能问题,不是吗?我真的要考虑再次回到BCL DateTime,即使它会受到伤害。
更新2
我正在使用is a variable I create from system time) it seems that each document must be deserialized using my
接受我的解决方案,但我对NodaTime与MongoDB的结合感到不舒服,而两者都是很好的个人解决方案。但是他们目前在没有大量操纵的情况下一起工作得不好。
答案 0 :(得分: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);
});
希望这有帮助。
答案 1 :(得分:1)
这是thmshd类的修改版本,它也存储时区信息:
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);
编辑: 使用NodaTimes内置字符串解析可能更好,而不是序列化到子文档。
序列化:
context.Writer.WriteString(ZonedDateTimePattern.CreateWithInvariantCulture("G", DateTimeZoneProviders.Tzdb).Format(zonedDateTime));
反序列化:
var zonedDateTimeString = context.Reader.ReadString();
var parseResult = ZonedDateTimePattern.CreateWithInvariantCulture("G", DateTimeZoneProviders.Tzdb)n.Parse(zonedDateTimeString);
if (!parseResult.Success)
{
throw parseResult.Exception;
}
return parseResult.Value;