web-dev-qa-db-ja.com

Dictionary <DateTime、T>をBSONにシリアル化するときのBsonSerializationException

最近、 非推奨のv1.9 から 新しいMongoDB C#ドライバーv2. に移動しました。

さて、辞書を持つクラスをシリアル化すると、次の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());
}
19
i3arnon

問題は、新しいドライバーがデフォルトで辞書をドキュメントとしてシリアル化することです。

MongoDB C#ドライバーには、辞書をシリアル化する3つの方法があります:DocumentArrayOfArraysArrayOfDocumentsドキュメントで詳しく説明します )。ドキュメントとしてシリアル化する場合、辞書キーはいくつかの制限があるBSON要素の名前です(たとえば、エラーが示すように、文字列としてシリアル化する必要があります)。

この場合、辞書のキーはDateTimesであり、文字列としてではなくDatesとしてシリアル化されるため、別のDictionaryRepresentationを選択する必要があります。

この特定のプロパティのシリアル化を変更するには、別のBsonDictionaryOptionsDictionaryRepresentation属性を使用できます。

[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);
38
i3arnon

上記の答えは素晴らしいですが、残念ながら、特定の再帰オブジェクト階層で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;
    }
2
IanBru

私のように、これをクラスの1つのフィールドに適用したいだけの場合は、次のように達成しました(他の回答のおかげで)。

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;
    }
}
0
QTom