简体   繁体   English

序列化字典时出现 BsonSerializationException<DateTime,T> 到 BSON

[英]BsonSerializationException when serializing a Dictionary<DateTime,T> to BSON

I've recently moved to the new MongoDB C# driver v2.0 from the deprecated v1.9 .我最近从已弃用的 v1.9迁移到新的 MongoDB C# 驱动程序 v2.0

Now, when I serialize a class that has a dictionary I sometimes run into the following BsonSerializationException :现在,当我序列化具有字典的类时,有时会遇到以下BsonSerializationException

MongoDB.Bson.BsonSerializationException: When using DictionaryRepresentation.Document key values must serialize as strings. MongoDB.Bson.BsonSerializationException:使用 DictionaryRepresentation.Document 时,键值必须序列化为字符串。

Here's a minimal reproduce:这是一个最小的复制:

class Hamster
{
    public ObjectId Id { get; private set; }
    public Dictionary<DateTime,int> Dictionary { get; private set; }
    public Hamster()
    {
        Id = ObjectId.GenerateNewId();
        Dictionary = new Dictionary<DateTime, int>();
        Dictionary[DateTime.UtcNow] = 0;
    }
}

static void Main()
{
    Console.WriteLine(new Hamster().ToJson());
}

The problem is that the new driver serializes dictionaries as a document by default.问题是新驱动程序默认将字典序列化为文档。

The MongoDB C# driver has 3 ways to serialize a dictionary: Document , ArrayOfArrays & ArrayOfDocuments ( more on that in the docs ). MongoDB C# 驱动程序有 3 种方法来序列化字典: DocumentArrayOfArraysArrayOfDocuments更多内容请参见 docs )。 When it serializes as a document the dictionary keys are the names of the BSON element which has some limitations (for example, as the error suggests, they must be serialized as strings).当它序列化为文档时,字典键是 BSON 元素的名称,它有一些限制(例如,如错误所示,它们必须序列化为字符串)。

In this case, the dictionary's keys are DateTime s which aren't serialized as strings, but as Date s so we need to choose another DictionaryRepresentation .在这种情况下,字典的键是DateTime ,它没有序列化为字符串,而是作为Date ,所以我们需要选择另一个DictionaryRepresentation

To change the serialization of this specific property we can use the BsonDictionaryOptions attribute with a different DictionaryRepresentation :要更改此特定属性的序列化,我们可以将BsonDictionaryOptions属性与不同的DictionaryRepresentation一起使用:

[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
public Dictionary<DateTime, int> Dictionary { get; private set; }

However, we need to do that on every problematic member individually.但是,我们需要对每个有问题的成员单独执行此操作。 To apply this DictionaryRepresentation to all the relevant members we can implement aa new convention:要将此DictionaryRepresentation应用于所有相关成员,我们可以实现一个新约定:

class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
    private readonly DictionaryRepresentation _dictionaryRepresentation;
    public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation)
    {
        _dictionaryRepresentation = dictionaryRepresentation;
    }
    public void Apply(BsonMemberMap memberMap)
    {
        memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer()));
    }
    private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer)
    {
        var dictionaryRepresentationConfigurable = serializer as IDictionaryRepresentationConfigurable;
        if (dictionaryRepresentationConfigurable != null)
        {
            serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
        }

        var childSerializerConfigurable = serializer as IChildSerializerConfigurable;
        return childSerializerConfigurable == null
            ? serializer
            : childSerializerConfigurable.WithChildSerializer(ConfigureSerializer(childSerializerConfigurable.ChildSerializer));
    }
} 

Which we register as follows:我们注册如下:

ConventionRegistry.Register(
    "DictionaryRepresentationConvention",
    new ConventionPack {new DictionaryRepresentationConvention(DictionaryRepresentation.ArrayOfArrays)},
    _ => true);

The answer above is great, but unfortunately triggers a StackOverflowException on certain recursive object hierarchies - here's a slightly improved and up to date version.上面的答案很好,但不幸的是在某些递归对象层次结构上触发了 StackOverflowException - 这是一个稍微改进的最新版本。

public class DictionaryRepresentationConvention : ConventionBase, IMemberMapConvention
{
    private readonly DictionaryRepresentation _dictionaryRepresentation;

    public DictionaryRepresentationConvention(DictionaryRepresentation dictionaryRepresentation = DictionaryRepresentation.ArrayOfDocuments)
    {
        // see http://mongodb.github.io/mongo-csharp-driver/2.2/reference/bson/mapping/#dictionary-serialization-options

        _dictionaryRepresentation = dictionaryRepresentation;
    }

    public void Apply(BsonMemberMap memberMap)
    {
        memberMap.SetSerializer(ConfigureSerializer(memberMap.GetSerializer(),Array.Empty<IBsonSerializer>()));
    }

    private IBsonSerializer ConfigureSerializer(IBsonSerializer serializer, IBsonSerializer[] stack)
    {
        if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationConfigurable)
        {
            serializer = dictionaryRepresentationConfigurable.WithDictionaryRepresentation(_dictionaryRepresentation);
        }

        if (serializer is IChildSerializerConfigurable childSerializerConfigurable)
        {
            if (!stack.Contains(childSerializerConfigurable.ChildSerializer))
            {
                var newStack = stack.Union(new[] { serializer }).ToArray();
                var childConfigured = ConfigureSerializer(childSerializerConfigurable.ChildSerializer, newStack);
                return childSerializerConfigurable.WithChildSerializer(childConfigured);
            }
        }

        return serializer;
    }

If like me you just wanted to apply this for a single field in a class, I achieved that like this (thanks to the other answers):如果像我一样,您只想将其应用于课程中的单个字段,那么我可以做到这一点(感谢其他答案):

BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
    cm.AutoMap();
    var memberMap = cm.GetMemberMap(x => x.DictionaryField);
    var serializer = memberMap.GetSerializer();
    if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
        serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(DictionaryRepresentation.ArrayOfDocuments);
    memberMap.SetSerializer(serializer);
});

Or as an extention method:或作为扩展方法:

BsonClassMap.RegisterClassMap<TestClass>(cm =>
{
    cm.AutoMap();
    cm.SetDictionaryRepresentation(x => x.DictionaryField, DictionaryRepresentation.ArrayOfDocuments);
});

public static class MapHelpers
{
    public static BsonClassMap<T> SetDictionaryRepresentation<T, TMember>(this BsonClassMap<T> classMap, Expression<Func<T,TMember>> memberLambda, DictionaryRepresentation representation)
    {
        var memberMap = classMap.GetMemberMap(memberLambda);
        var serializer = memberMap.GetSerializer();
        if (serializer is IDictionaryRepresentationConfigurable dictionaryRepresentationSerializer)
            serializer = dictionaryRepresentationSerializer.WithDictionaryRepresentation(representation);
        memberMap.SetSerializer(serializer);
        return classMap;
    }
}

With above code, I get System.InvalidOperationException: 'ValueFactory attempted to access the Value property of this instance.'使用上面的代码,我得到 System.InvalidOperationException: 'ValueFactory 试图访问此实例的 Value 属性。

Has something to do with a List<> property.与 List<> 属性有关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM