27

我最近从已弃用的 v1.9迁移到新的 MongoDB C# 驱动程序 v2.0

现在,当我序列化一个有字典的类时,我有时会遇到以下情况BsonSerializationException

MongoDB.Bson.BsonSerializationException:使用 DictionaryRepresentation.Document 时,键值必须序列化为字符串。

这是一个最小的复制:

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

3 回答 3

53

问题是新驱动程序默认将字典序列化为文档。

MongoDB C# 驱动程序有 3 种方法来序列化字典:DocumentArrayOfArrays& ArrayOfDocuments更多内容请参见文档)。当它序列化为文档时,字典键是 BSON 元素的名称,它有一些限制(例如,如错误所示,它们必须序列化为字符串)。

在这种情况下,字典的键是DateTimes ,它没有被序列化为字符串,但是作为Dates 我们需要选择另一个DictionaryRepresentation

要更改此特定属性的序列化,我们可以使用BsonDictionaryOptions具有不同的属性DictionaryRepresentation

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

但是,我们需要对每个有问题的成员单独执行此操作。要将其应用于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));
    }
} 

我们注册如下:

ConventionRegistry.Register(
    "DictionaryRepresentationConvention",
    new ConventionPack {new DictionaryRepresentationConvention(DictionaryRepresentation.ArrayOfArrays)},
    _ => true);
于 2015-01-23T14:16:06.503 回答
5

上面的答案很好,但不幸的是在某些递归对象层次结构上触发了 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;
    }
于 2019-04-30T09:46:14.623 回答
3

如果像我一样,您只是想将其应用于课程中的单个字段,那么我可以做到这一点(感谢其他答案):

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

或作为扩展方法:

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;
    }
}
于 2019-09-18T10:05:57.207 回答