Хранение перечислений в виде строк в MongoDB


есть ли способ хранить перечисления в виде строковых имен, а не порядковых значений?

пример:

представьте, что у меня есть это перечисление:

public enum Gender
{
    Female,
    Male
}

теперь, если какой-то воображаемый пользователь существует с

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

он будет храниться в базе данных MongoDb как { ... "Пол" : 1 ... }

но я бы предпочел что-то вроде этого { ... "Пол":" Мужчина"... }

это возможно? Пользовательские отображения, отражение трюки, что угодно.

мой контекст: я использую строго типизированные коллекции над POCO (ну, я отмечаю ARs и иногда использую полиморфизм). У меня есть тонкий уровень абстракции доступа к данным в виде единицы работы. Поэтому я не сериализую / десериализую каждый объект, но я могу (и делаю) определить некоторые ClassMaps. Я использую официальный драйвер MongoDb + fluent-mongodb.

8 52

8 ответов:

драйвер MongoDB .NET позволяет применять соглашения чтобы определить, как обрабатываются определенные сопоставления между типами среды CLR и элементами базы данных.

Если вы хотите, чтобы это применялось ко всем вашим перечислениям, вам нужно только настроить соглашения один раз на AppDomain (обычно при запуске приложения), а не добавлять атрибуты ко всем вашим типам или вручную сопоставлять каждый тип:

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
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; }
}

можно настроить сопоставление классов для класса, содержащего перечисление, и указать, что элемент должен быть представлен строкой. Это будет обрабатывать как сериализацию, так и десериализацию перечисления.

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

         });
      }

Я все еще ищу способ указать, что перечисления должны быть глобально представлены в виде строк, но это метод, который я использую в настоящее время.

использовать MemberSerializationOptionsConvention чтобы определить соглашение о том, как перечисление будет сохранено.

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

с драйвером 2.х я решил с помощью конкретные сериализатор:

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

Я закончил присвоение значений перечислению элементов, как предложил Крис Смит в комментарии:

Я бы этого избегал. Строковое значение занимает гораздо больше места, чем целое число. Я бы, однако, если настойчивость участвует дать детерминированные значения для каждого элемента в вашем перечислении так Female = 1,Male = 2 Так что если перечисление добавляется позже или порядок элементов изменен, что вы не в конечном итоге с проблемами.

не совсем то, что я искал, но, кажется, нет все наоборот.

я обнаружил, что только применение ответ Рикардо Родригеса в некоторых случаях недостаточно правильно сериализовать значения перечисления для строки в MongoDb:

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

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

если ваша структура данных включает значения перечисления, помещаемые в объекты, сериализация MongoDb не будет использовать set EnumRepresentationConvention сериализовать его.

действительно, если вы посмотрите на реализацию драйвера MongoDb ObjectSerializer, это позволит решить TypeCode of значение в штучной упаковке (Int32 для значения перечисления), и использовать этот тип для хранения значения enum в базе данных. Таким образом, упакованные значения перечисления в конечном итоге сериализуются как 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());

это гарантирует, что упакованные значения перечисления будут храниться в виде строк так же, как и распакованные.

имейте в виду, однако, что при десериализации документа, в коробке значение останется строкой. Он не будет преобразован обратно в исходное значение enum. Если вам нужно преобразовать строку обратно в исходное значение перечисления, поле дискриминации, вероятно, придется добавить в документ, чтобы сериализатор мог знать, что такое тип перечисления десриализировать В.

одним из способов сделать это было бы хранить документ bson вместо просто строки, в которую поле дискриминации (_t) и значение поля (_v) будет использоваться для хранения перечислимый тип и его значение.

ответы, опубликованные здесь, хорошо работают для TEnum и TEnum[], однако не будет работать с 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; }