Speichern von Enums als Zeichenfolgen in MongoDB

73

Gibt es eine Möglichkeit, Enums als Zeichenfolgennamen und nicht als Ordnungswerte zu speichern?

Beispiel:

Stellen Sie sich vor, ich habe diese Aufzählung:

public enum Gender
{
    Female,
    Male
}

Nun, wenn ein imaginärer Benutzer mit existiert

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

Es wird in der MongoDb-Datenbank als {... "Geschlecht": 1 ...} gespeichert

aber ich würde so etwas bevorzugen {... "Geschlecht": "Männlich" ...}

Ist das möglich? Benutzerdefiniertes Mapping, Reflexionstricks, was auch immer.

Mein Kontext: Ich verwende stark typisierte Sammlungen über POCO (nun, ich markiere ARs und verwende gelegentlich Polymorphismus). Ich habe eine dünne Abstraktionsschicht für den Datenzugriff in Form von Unit Of Work. Ich serialisiere / deserialisiere also nicht jedes Objekt, sondern kann (und tue) einige ClassMaps definieren. Ich benutze den offiziellen MongoDb-Treiber + Fluent-Mongodb.

Kostassoid
quelle
7
Ich würde es vermeiden. Der Zeichenfolgenwert nimmt viel mehr Platz ein als eine Ganzzahl. Ich würde jedoch, wenn es um Persistenz geht, jedem Element in Ihrer Aufzählung deterministische Werte geben, also weiblich = 1, männlich = 2, wenn die Aufzählung später hinzugefügt oder die Reihenfolge der Elemente geändert wird, sodass Sie keine Probleme haben.
Gelöscht
1
Ja, ich habe über den Weltraum nachgedacht, aber es ist kein Problem, da ich nur wenige Fälle habe, in denen ich ehrliche Aufzählungen bevorzuge. Und du hast es richtig gemacht, zukünftige Veränderungen stören mich. Ich denke, das Markieren aller Enum-Werte ist in der Tat eine praktikable Lösung. Danke Chris!
Kostassoid

Antworten:

48

Mit dem MongoDB .NET-Treiber können Sie Konventionen anwenden , um zu bestimmen, wie bestimmte Zuordnungen zwischen CLR-Typen und Datenbankelementen behandelt werden.

Wenn Sie möchten, dass dies auf alle Ihre Aufzählungen angewendet wird, müssen Sie Konventionen nur einmal pro AppDomain einrichten (normalerweise beim Starten Ihrer Anwendung), anstatt allen Typen Attribute hinzuzufügen oder jeden Typ manuell zuzuordnen:

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
Ricardo Rodriguez
quelle
5
Die Dokumentation dazu ist alles andere als wunderbar. Ich würde den Ansatz [BsonRepresentation (BsonType.String)] für die Eigenschaften verwenden, die Sie serialisieren möchten, da dies sehr offensichtlich ist (wie von John Gietzen unten vorgeschlagen, den er vermutlich von drdobbs.com/database/mongodb- erhalten hat) with-c-deep-tauch / 240152181? pgno = 2 in Anbetracht des Beispiels, das er verwendet hat).
Bart Read
2
Ich habe festgestellt, dass dies nicht für alle Serialisierungsfälle funktioniert, in denen eine Aufzählung verwendet wird. Wenn in Ihrer Datenstruktur Ihre Aufzählungen in einem Objekttyp zusammengefasst sind, wie wenn Sie ExpandoObjects verwenden oder Werte in das zu serialisierende Wörterbuch <Zeichenfolge, Objekt> einfügen würden, werden die Aufzählungswerte auch dann als int-Werte serialisiert, wenn die Konvention vorhanden ist . Es scheint, als würde die Mongodb-Serialisierung für Aufzählungswerte korrekt funktionieren, wenn der zu serialisierende Nominaltyp der Aufzählungstyp ist. Wenn der Nomiltyp typeof (Object) ist, funktioniert die Serialisierung in einen String nicht.
Sboisse
130
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

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

public class Person
{
    [JsonConverter(typeof(StringEnumConverter))]  // JSON.Net
    [BsonRepresentation(BsonType.String)]         // Mongo
    public Gender Gender { get; set; }
}
John Gietzen
quelle
2
Dies ist die einfachste Implementierung ohne Overhead.
Deni Spasovski
nach dem Update Mongo Treiber Version 2.0 funktioniert nicht :( !!
Bimawa
Ich benutze den neuesten Treiber und es funktioniert. Sollte die akzeptierte Antwort sein.
Henrique Miranda
3
Die akzeptierte Antwort ist der bessere Weg. Im Allgemeinen möchten Sie, dass Ihr Modell persistenzunabhängig ist. Es ist wohl keine POCO-Klasse mehr, wenn es eine MongoDB-Abhängigkeit hat. Alles, was auf das Modell verweist, muss auf MongoDB.Bson verweisen, auch wenn es nicht am Lesen / Schreiben beteiligt ist. Die Regeln zum Lesen / Schreiben sind besser in der Repository-Schicht enthalten.
Chad Hedgcock
1
Ich stimme @ChadHedgcock eher zu, dass es besser ist, diese Arten von Abhängigkeiten auf App- oder Containerebene zu registrieren. Diese Antwort zeigt jedoch, wie das Standard- / konventionelle Verhalten überschrieben werden kann. Ich denke, es ist immer noch sehr nützlich.
John Gietzen
16

Sie können die Klassenzuordnung für die Klasse anpassen, die die Aufzählung enthält, und angeben, dass das Mitglied durch eine Zeichenfolge dargestellt werden soll. Dies behandelt sowohl die Serialisierung als auch die Deserialisierung der Aufzählung.

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

         });
      }

Ich suche immer noch nach einer Möglichkeit, anzugeben, dass Aufzählungen global als Zeichenfolgen dargestellt werden sollen, aber dies ist die Methode, die ich derzeit verwende.

Wade Kaple
quelle
Vielen Dank! das ist es, wonach ich gesucht habe. Ich sehe bestimmte Probleme, wie ein ausgefeilteres Abfragemodell für die Implementierung von cqrs, aber nichts Unverwaltbares.
Kostassoid
1
Für den Mongo 2.0-Treiber funktioniert diese Syntax: BsonSerializer.RegisterSerializer (neuer EnumSerializer <RealTimeChroState> (BsonType.String));
BrokeMyLegBiking
5

Verwenden Sie MemberSerializationOptionsConvention , um eine Konvention zum Speichern einer Aufzählung zu definieren.

new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
Boypula
quelle
Ich habe diese Lösung in der Vergangenheit verwendet, aber jetzt scheint die Klasse MemberSerializationOptionsConvention in der 2.x-Version der Treiber verschwunden zu sein. Weiß jemand, wie man mit der 2.x-Treiberversion das gleiche Ergebnis erzielt?
Alkampfer
4

Mit Treiber 2.x habe ich mit einem bestimmten Serializer gelöst :

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

Ich habe festgestellt, dass es in einigen Fällen nicht ausreicht , nur die Antwort von Ricardo Rodriguez anzuwenden , um die Enum-Werte für die Zeichenfolge in MongoDb ordnungsgemäß zu serialisieren:

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

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

Wenn Ihre Datenstruktur beinhaltet, dass Enum-Werte in Objekte gepackt werden, verwendet die MongoDb-Serialisierung das Set nicht EnumRepresentationConventionzum Serialisieren.

Wenn Sie sich die Implementierung des ObjectSerializer des MongoDb-Treibers ansehen , wird der BoxwertTypeCode ( Int32für Aufzählungswerte) aufgelöst und dieser Typ zum Speichern Ihres Aufzählungswerts in der Datenbank verwendet. Boxed Enum-Werte werden also als intWerte serialisiert . Sie bleiben auch intbei der Deserialisierung als Werte erhalten.

Um dies zu ändern, können Sie eine benutzerdefinierte ObjectSerializerDatei schreiben , die die Menge erzwingt, EnumRepresentationConventionwenn der umrahmte Wert eine Aufzählung ist. Etwas wie das:

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

Legen Sie dann den benutzerdefinierten Serializer als den für die Serialisierung von Objekten zu verwendenden fest:

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

Auf diese Weise wird sichergestellt, dass die Aufzählungswerte in Kästchen genauso wie die nicht in Kästchen verpackten Zeichenfolgen gespeichert werden.

Beachten Sie jedoch, dass beim Deserialisieren Ihres Dokuments der Wert in der Box eine Zeichenfolge bleibt. Es wird nicht wieder in den ursprünglichen Aufzählungswert konvertiert. Wenn Sie die Zeichenfolge wieder in den ursprünglichen Aufzählungswert konvertieren müssen, muss wahrscheinlich ein Unterscheidungsfeld in Ihr Dokument eingefügt werden, damit der Serializer weiß, in welchen Aufzählungstyp derrialisiert werden soll.

Eine Möglichkeit wäre, ein bson-Dokument anstelle einer Zeichenfolge zu speichern, in dem das Unterscheidungsfeld ( _t) und ein Wertefeld ( _v) zum Speichern des Aufzählungstyps und seines Zeichenfolgenwerts verwendet werden.

sboisse
quelle
2

Die hier veröffentlichten Antworten funktionieren gut für TEnumund TEnum[]funktionieren jedoch nicht mit Dictionary<TEnum, object>. Sie können dies erreichen, wenn Sie den Serializer mithilfe von Code initialisieren. Ich wollte dies jedoch über Attribute tun. Ich habe eine flexible Datei erstellt DictionarySerializer, die mit einem Serializer für den Schlüssel und den Wert konfiguriert werden kann.

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

Verwendung wie diese, bei der sowohl Schlüssel als auch Wert Aufzählungstypen sind, aber eine beliebige Kombination von Serialisierern sein können:

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

Am Ende habe ich Aufzählungselementen Werte zugewiesen, wie von Chris Smith in einem Kommentar vorgeschlagen:

Ich würde es vermeiden. Der Zeichenfolgenwert nimmt viel mehr Platz ein als eine Ganzzahl. Ich würde jedoch , wenn Persistenz beteiligt geben determinis Werte zu jedem Elemente in Ihrem Enum ist so Female = 1, Male = 2also , wenn die Enumeration hinzugefügt später oder die Reihenfolge der Elemente geändert , dass Sie nicht mit Problemen enden.

Nicht genau das, wonach ich gesucht habe, aber es scheint, dass es keinen anderen Weg gibt.

Kostassoid
quelle
1
Nicht einverstanden mit -1 dafür: Die akzeptierte Antwort funktioniert für die gestellte Frage und ist eine gute Antwort, daher sollte sie nicht geändert werden. Nach weiterer Überprüfung änderte der Fragesteller das Problem / den Ansatz und fügte diese Antwort einfach als weitere Information hinzu. Aber ich bin damit einverstanden, dass es als Kommentar und nicht als zusätzliche Antwort hätte hinzugefügt werden sollen.
Swannee
1

Wenn Sie .NET Core 3.1 und höher verwenden, verwenden Sie den neuesten ultraschnellen Json Serializer / Deserializer von Microsoft, System.Text.Json ( https://www.nuget.org/packages/System.Text.Json ).

Den Metrikvergleich finden Sie unter https://medium.com/@samichkhachkhi/system-text-json-vs-newtonsoft-json-d01935068143

using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using System.Text.Json.Serialization;;

public class Person
{
    [JsonConverter(typeof(JsonStringEnumConverter))]  // System.Text.Json.Serialization
    [BsonRepresentation(BsonType.String)]         // MongoDB.Bson.Serialization.Attributes
    public Gender Gender { get; set; }
}
Chipo Hamayobe
quelle