web-dev-qa-db-ja.com

MongoDBに文字列として列挙型を保存する

列挙値を順序値ではなく文字列名として保存する方法はありますか?

例:

私はこの列挙型を持っていると想像してください:

public enum Gender
{
    Female,
    Male
}

架空のユーザーが存在する場合

...
Gender gender = Gender.Male;
...

mongoDbデータベースに{... "Gender":1 ...}として保存されます

しかし、私はこのようなものを好む{... "性別:"男性 "...}

これは可能ですか?カスタムマッピング、リフレクショントリック、何でも。

私のコンテキスト:POCOよりも強く型付けされたコレクションを使用します(まあ、ARをマークし、ときどきポリモーフィズムを使用します)。作業単位の形式のシンデータアクセス抽象化レイヤーがあります。したがって、各オブジェクトをシリアライズ/デシリアライズしているわけではありませんが、いくつかのClassMapを定義できます(そして実際に定義します)。公式のMongoDbドライバー+ fluent-mongodbを使用します。

64
Kostassoid

MongoDB .NETドライバー 規則を適用できます CLRタイプとデータベース要素間の特定のマッピングの処理方法を決定します。

これをすべての列挙型に適用する場合、すべてのタイプに属性を追加したり、すべてのタイプを手動でマッピングしたりするのではなく、AppDomainごとに1回だけ規則をセットアップする必要があります(通常はアプリケーションの起動時)。

// Set up MongoDB conventions
var pack = new ConventionPack
{
    new EnumRepresentationConvention(BsonType.String)
};

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
39
using MongoDB.Bson;

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class Person
{
    [JsonConverter(typeof(StringEnumConverter))]  // JSON.Net
    [BsonRepresentation(BsonType.String)]         // Mongo
    public Gender Gender { get; set; }
}
106
John Gietzen

列挙型を含むクラスのクラスマップをカスタマイズし、メンバーが文字列で表されるように指定できます。これにより、列挙型のシリアル化と逆シリアル化の両方が処理されます。

if (!MongoDB.Bson.Serialization.BsonClassMap.IsClassMapRegistered(typeof(Person)))
      {
        MongoDB.Bson.Serialization.BsonClassMap.RegisterClassMap<Person>(cm =>
         {
           cm.AutoMap();
           cm.GetMemberMap(c => c.Gender).SetRepresentation(BsonType.String);

         });
      }

私はまだ列挙型を文字列としてグローバルに表現することを指定する方法を探していますが、これは現在使用している方法です。

16
Wade Kaple

MemberSerializationOptionsConventionを使用して、enumの保存方法に関する規則を定義します。

new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
5
boypula

Ricardo Rodriguezの答え を適用するだけでは、enum値を文字列に適切にシリアル化してMongoDbにするのに十分ではないことがわかりました。

// Set up MongoDB conventions
var pack = new ConventionPack
{
    new EnumRepresentationConvention(BsonType.String)
};

ConventionRegistry.Register("EnumStringConvention", pack, t => true);

オブジェクトにボックス化された列挙値がデータ構造に含まれる場合、MongoDbシリアル化は、セットEnumRepresentationConventionを使用してシリアル化しません。

実際、MongoDbドライバーの ObjectSerializer の実装を見ると、ボックス化された値のTypeCodeInt32列挙値の場合)、そのタイプを使用して、列挙値をデータベースに保存します。したがって、ボックス化された列挙値はint値としてシリアル化されます。デシリアライズされるときも、それらはint値のままになります。

これを変更するには、ボックス化された値が列挙型である場合にセットObjectSerializerを強制するカスタムEnumRepresentationConventionを記述することができます。このようなもの:

public class ObjectSerializer : MongoDB.Bson.Serialization.Serializers.ObjectSerializer
{
     public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
    {
        var bsonWriter = context.Writer;
        if (value != null && value.GetType().IsEnum)
        {
            var conventions = ConventionRegistry.Lookup(value.GetType());
            var enumRepresentationConvention = (EnumRepresentationConvention) conventions.Conventions.FirstOrDefault(convention => convention is EnumRepresentationConvention);
            if (enumRepresentationConvention != null)
            {
                switch (enumRepresentationConvention.Representation)
                {
                    case BsonType.String:
                        value = value.ToString();
                        bsonWriter.WriteString(value.ToString());
                        return;
                }
            }
        }

        base.Serialize(context, args, value);
    }
}

次に、カスタムシリアライザーをオブジェクトのシリアル化に使用するものとして設定します。

BsonSerializer.RegisterSerializer(typeof(object), new ObjectSerializer());

これを行うと、ボックス化された列挙値は、ボックス化されていないものと同様に文字列として保存されます。

ただし、ドキュメントを逆シリアル化する場合、ボックス化された値は文字列のままになることに注意してください。元の列挙値に変換されません。文字列を元の列挙値に変換する必要がある場合、文書に識別フィールドを追加する必要があります。これにより、シリアライザは、シリアル化する列挙型を知ることができます。

そのための1つの方法は、単なる文字列ではなくbsonドキュメントを保存することです。このフィールドには、識別フィールド(_t)および値フィールド(_v)は、列挙型とその文字列値を格納するために使用されます。

2
sboisse

ドライバ2.xでは、 特定のシリアライザ を使用して解決しました。

BsonClassMap.RegisterClassMap<Person>(cm =>
            {
                cm.AutoMap();
                cm.MapMember(c => c.Gender).SetSerializer(new EnumSerializer<Gender>(BsonType.String));
            });
2
wilver

ここに投稿された回答は、TEnumTEnum[]でうまく機能しますが、Dictionary<TEnum, object>では機能しません。コードを使用してシリアライザーを初期化するときにこれを達成できますが、属性を使用してこれを行いたいと思いました。キーと値のシリアライザーで構成できる柔軟なDictionarySerializerを作成しました。

public class DictionarySerializer<TDictionary, KeySerializer, ValueSerializer> : DictionarySerializerBase<TDictionary>
    where TDictionary : class, IDictionary, new()
    where KeySerializer : IBsonSerializer, new()
    where ValueSerializer : IBsonSerializer, new()
{
    public DictionarySerializer() : base(DictionaryRepresentation.Document, new KeySerializer(), new ValueSerializer())
    {
    }

    protected override TDictionary CreateInstance()
    {
        return new TDictionary();
    }
}

public class EnumStringSerializer<TEnum> : EnumSerializer<TEnum>
    where TEnum : struct
{
    public EnumStringSerializer() : base(BsonType.String) { }
}

このような使用法では、キーと値の両方が列挙型ですが、シリアライザーの任意の組み合わせが可能です。

    [BsonSerializer(typeof(DictionarySerializer<
        Dictionary<FeatureToggleTypeEnum, LicenseFeatureStateEnum>, 
        EnumStringSerializer<FeatureToggleTypeEnum>,
        EnumStringSerializer<LicenseFeatureStateEnum>>))]
    public Dictionary<FeatureToggleTypeEnum, LicenseFeatureStateEnum> FeatureSettings { get; set; }
1
Bouke

Chris Smithがコメントで示唆したように、列挙型アイテムに値を割り当てることになりました。

避けたいです。文字列値は整数よりもはるかに多くのスペースを占有します。ただし、永続性が関係する場合は、列挙型の各項目に決定論的な値を与えるので、Female = 1Male = 2そのため、enumが後から追加されたり、アイテムの順序が変更されても、問題が発生しません。

まさに私が探していたものではありませんが、他の方法はないようです。

1
Kostassoid