web-dev-qa-db-fra.com

Stocker des énumérations sous forme de chaînes dans MongoDB

Existe-t-il un moyen de stocker Enums en tant que noms de chaîne plutôt que de valeurs ordinales?

Exemple:

Imaginez que j'ai cet enum:

public enum Gender
{
    Female,
    Male
}

Maintenant, si un utilisateur imaginaire existe avec

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

il sera stocké dans la base de données MongoDb sous le nom {... "Gender": 1 ...}

mais je préférerais quelque chose comme ça {... "Genre": "Homme" ...}

Est-ce possible? Cartographie personnalisée, astuces de réflexion, peu importe.

Mon contexte: j'utilise des collections fortement typées sur POCO (enfin, je marque les AR et utilise parfois un polymorphisme). J'ai une couche d'abstraction d'accès aux données mince dans une forme d'unité de travail. Donc, je ne sérialise/désérialise pas chaque objet mais je peux (et fais) définir des classmaps. J'utilise le pilote officiel MongoDb + fluent-mongodb.

55
Kostassoid

Le pilote MongoDB .NET vous permet d’appliquer des conventions pour déterminer comment certaines correspondances entre les types CLR et les éléments de base de données sont gérées.

Si vous souhaitez que cela s'applique à tous vos enums, il vous suffit de configurer les conventions une fois par AppDomain (généralement lors du démarrage de votre application), par opposition à l'ajout d'attributs à tous vos types ou à la cartographie manuelle de chaque type:

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
34
Ricardo Rodriguez
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; }
}
96
John Gietzen

Vous pouvez personnaliser la mappe de classes pour la classe contenant l'énumération et spécifier que le membre soit représenté par une chaîne. Cela gérera à la fois la sérialisation et la désérialisation de l'énum.

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

         });
      }

Je cherche toujours un moyen de spécifier que les énumérations soient globalement représentées sous forme de chaînes, mais c'est la méthode que j'utilise actuellement.

16
Wade Kaple

Utilisez MemberSerializationOptionsConvention pour définir une convention sur la manière dont une énumération sera sauvegardée.

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

J'ai constaté que le simple fait d'appliquer la réponse de Ricardo Rodriguez n'est pas suffisant dans certains cas pour sérialiser correctement les valeurs enum en chaîne dans MongoDb:

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

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

Si votre structure de données implique que les valeurs enum soient encadrées dans des objets, la sérialisation MongoDb n'utilisera pas l'ensemble EnumRepresentationConvention pour la sérialiser.

En effet, si vous regardez l'implémentation du ObjectSerializer du pilote MongoDb, il va résoudre la valeur TypeCode de la valeur encadrée (Int32 pour les valeurs enum) et utiliser ce type pour stocker votre valeur enum dans la base de données. Ainsi, les valeurs enum encadrées finissent par être sérialisées en tant que valeurs int. Elles resteront en tant que valeurs int lors de la désérialisation.

Pour changer cela, il est possible d'écrire une variable ObjectSerializer personnalisée qui appliquera l'ensemble EnumRepresentationConvention si la valeur encadrée est une enum. Quelque chose comme ça:

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

puis définissez le sérialiseur personnalisé comme celui à utiliser pour la sérialisation des objets:

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

Cela garantira que les valeurs enum encadrées seront stockées sous forme de chaînes, tout comme les valeurs sans boîte.

Gardez toutefois à l'esprit que lors de la désérialisation de votre document, la valeur encadrée restera une chaîne. Il ne sera pas reconverti à la valeur enum d'origine. Si vous devez reconvertir la chaîne en valeur d'énumération d'origine, un champ de discrimination devra probablement être ajouté à votre document afin que le sérialiseur puisse connaître le type d'énum dans lequel désrialiser.

Une façon de le faire serait de stocker un document bson au lieu d'une chaîne dans laquelle le champ de discrimination (_t) et un champ de valeur (_v) seraient utilisés pour stocker le type enum et sa valeur de chaîne.

2
sboisse

Avec le pilote 2.x j'ai résolu en utilisant un sérialiseur spécifique :

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

J'ai fini par assigner des valeurs aux items enum, comme suggéré par Chris Smith dans un commentaire:

Je l'éviterais. La valeur de chaîne prend beaucoup plus d'espace qu'un entier. Cependant, si la persistance est impliquée, donnez des valeurs déterministes à chaque élément de votre enum, donc Female = 1, Male = 2, de sorte que si l'énum est ajouté ultérieurement ou que l'ordre des éléments est modifié, vous ne rencontrez pas de problèmes. 

Pas exactement ce que je cherchais mais il semble qu'il n'y ait pas d'autre moyen.

1
Kostassoid

Les réponses affichées ici fonctionnent bien pour TEnum et TEnum[], mais ne fonctionneront pas avec Dictionary<TEnum, object>. Vous pouvez y parvenir lors de l’initialisation du sérialiseur à l’aide de code, mais j’ai voulu le faire au moyen d’attributs. J'ai créé une variable DictionarySerializer pouvant être configurée avec un sérialiseur pour la clé et la valeur.

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

Une utilisation comme celle-ci, où clé et valeur sont des types enum, mais qui pourrait être n’importe quelle combinaison de sérialiseurs:

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