Вопрос по mongodb, mongodb-.net-driver, c# – Такое использование, где и ключ, и значение являются типами перечислений, но могут представлять собой любую комбинацию сериализаторов:

57

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

Пример:

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

public enum Gender
{
    Female,
    Male
}

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

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

он будет сохранен в базе данных MongoDb как {... "Gender": 1 ...}

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

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

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

Да, я думал о космосе, но считал, что это не проблема, поскольку у меня есть всего несколько случаев, когда я предпочитаю честные Enums. И вы поняли это правильно, будущие изменения - вот что беспокоит меня. Я полагаю, что маркировка каждого значения перечисления является жизнеспособным решением. Спасибо Крис! Kostassoid
Я бы избежал этого. Значение строки занимает намного больше места, чем целое число. Однако я хотел бы, чтобы при наличии постоянства давались детерминированные значения для каждого элемента в вашем перечислении, поэтому Женский = 1, Мужской = 2, поэтому, если перечисление добавлено позднее или порядок элементов изменился, у вас не возникнет проблем. Deleted

Ваш Ответ

8   ответов
34

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

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

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
Документация на это менее чем прекрасна. Я бы использовал подход [BsonRepresentation (BsonType.String)] для свойств, которые вы хотите сериализовать, потому что этоочень очевидно (как предложено Джоном Гитценом ниже, что, я думаю, он получил отdrdobbs.com/database/mongodb-with-c-deep-dive/240152181?pgno=2 учитывая пример, который он использовал). Bart Read
Конвенционный пакет. Вы можете получить больше информации в документации драйвера c # (docs.mongodb.org/ecosystem/tutorial/...) Ricardo Rodriguez
Я обнаружил, что это не работает для всех случаев сериализации, где используется enum. Если в вашей структуре данных ваши перечисления упакованы в тип объекта, как если бы вы использовали ExpandoObjects или помещали значения в Dictionary <string, object> для сериализации, значения перечисления будут сериализованы как значения int, даже если существует соглашение , Кажется, что сериализация mongodb будет работать правильно для значений перечисления, если номинальный тип для сериализации является типом перечисления. Но если типом nomil является typeof (Object), сериализация в строку не работает. sboisse
16

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

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

         });
      }

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

для драйвера Mongo 2.0 этот синтаксис работает: BsonSerializer.RegisterSerializer (новый EnumSerializer <RealTimeChroState> (BsonType.String)); BrokeMyLegBiking
Спасибо! вот что я вроде искал. Я вижу некоторые проблемы, такие как более сложная модель запросов для реализации cqrs, но ничего неуправляемого. Kostassoid
1

что присвоил значения элементам enum, как это было предложено Крисом Смитом в комментарии:

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

Не совсем то, что я искал, но, похоже, другого пути нет.

-1 потому что вы должны изменить принятый ответ. rsenna
Не соглашайтесь с -1 для этого: принятый ответ работает на заданный вопрос и является хорошим ответом, поэтому его не следует менять. После дальнейшего рассмотрения автор изменил проблему / подход и просто добавил этот ответ в качестве дополнительной информации. Но я согласен, что это должно было быть добавлено как комментарий, а не как дополнительный ответ. swannee
5

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

new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
Я использовал это решение в прошлом, но теперь кажется, что в версии драйверов 2.x класс MemberSerializationOptionsConvention исчез. Кто-нибудь знает, как получить такой же результат с версией драйвера 2.x? Alkampfer
96
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; }
}
после обновления версия 2.0 драйвера не работает :( !! Bimawa
Принятый ответ - лучший путь. В общем, вы хотите, чтобы ваша модель была невежественной. Возможно, он больше не является классом POCO, если он имеет зависимость от MongoDB. Все, что ссылается на модель, должно ссылаться на MongoDB.Bson, даже если оно не участвует в чтении / записи. Правила того, как читать / писать, лучше содержатся на уровне хранилища. Chad Hedgcock
Я думаю, это должен быть принятый ответ. Simon
это самая простая реализация без каких-либо накладных расходов. Deni Spasovski
Я использую последний драйвер, и он работает. Должен быть принятый ответ. Henrique Miranda
2

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

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

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

Если ваша структура данных включает в себя перечисление значений enum в объекты, сериализация MongoDb не будет использовать наборEnumRepresentationConvention сериализовать это.

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

Это обеспечит сохранение значений перечисленных в штучной упаковке в виде строк, точно так же, как и без распакованных.

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

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

0

размещенные здесь, хорошо подходят для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; }
2

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

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

Похожие вопросы