Хранение перечислений в виде строк в MongoDB
есть ли способ хранить перечисления в виде строковых имен, а не порядковых значений?
пример:
представьте, что у меня есть это перечисление:
public enum Gender
{
Female,
Male
}
теперь, если какой-то воображаемый пользователь существует с
...
Gender gender = Gender.Male;
...
он будет храниться в базе данных MongoDb как { ... "Пол" : 1 ... }
но я бы предпочел что-то вроде этого { ... "Пол":" Мужчина"... }
это возможно? Пользовательские отображения, отражение трюки, что угодно.
мой контекст: я использую строго типизированные коллекции над POCO (ну, я отмечаю ARs и иногда использую полиморфизм). У меня есть тонкий уровень абстракции доступа к данным в виде единицы работы. Поэтому я не сериализую / десериализую каждый объект, но я могу (и делаю) определить некоторые ClassMaps. Я использую официальный драйвер MongoDb + fluent-mongodb.
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; }