有没有办法将Enums存储为字符串名称而不是序数值?
示例:
想象一下,我有这个枚举:
public enum Gender
{
Female,
Male
}
现在,如果某个想象中的用户存在
...
Gender gender = Gender.Male;
...
它将以{...“Gender”:1 ...}
存储在MongoDb数据库中但我更喜欢这样的事情{......“性别”:“男性”......}
这可能吗?自定义映射,反射技巧等等。
我的上下文:我在POCO上使用强类型集合(好吧,我标记AR并偶尔使用多态)。我有一个工作单元形式的瘦数据访问抽象层。所以我没有序列化/反序列化每个对象,但我可以(并且确实)定义一些ClassMaps。我使用官方的MongoDb驱动程序+ fluent-mongodb。
答案 0 :(得分:101)
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; }
}
答案 1 :(得分:37)
MongoDB .NET驱动程序lets you apply conventions,用于确定如何处理CLR类型和数据库元素之间的某些映射。
如果您希望将其应用于所有枚举,则只需为每个AppDomain设置一次约定(通常在启动应用程序时),而不是向所有类型添加属性或手动映射每种类型:
// Set up MongoDB conventions
var pack = new ConventionPack
{
new EnumRepresentationConvention(BsonType.String)
};
ConventionRegistry.Register("EnumStringConvention", pack, t => true);
答案 2 :(得分:16)
您可以为包含枚举的类自定义类映射,并指定该成员由字符串表示。这将处理枚举的序列化和反序列化。
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);
});
}
我仍在寻找一种方法来指定枚举全局表示为字符串,但这是我目前使用的方法。
答案 3 :(得分:5)
使用 MemberSerializationOptionsConvention 来定义关于如何保存枚举的约定。
new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
答案 4 :(得分:2)
使用驱动程序2.x我使用specific serializer:
解决了问题BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Gender).SetSerializer(new EnumSerializer<Gender>(BsonType.String));
});
答案 5 :(得分:2)
我发现仅仅应用Ricardo Rodriguez' answer在某些情况下不足以将枚举值正确地序列化为字符串到MongoDb:
// Set up MongoDB conventions
var pack = new ConventionPack
{
new EnumRepresentationConvention(BsonType.String)
};
ConventionRegistry.Register("EnumStringConvention", pack, t => true);
如果您的数据结构涉及将枚举值装入对象,则MongoDb序列化将不会使用集合EnumRepresentationConvention
来序列化它。
的确,如果你看一下MongoDb驱动程序ObjectSerializer的实现,它将解析盒装值的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
)将用于存储枚举类型及其字符串值。
答案 6 :(得分:1)
我最终将值分配给枚举项目,正如Chris Smith在评论中所建议的那样:
我会避免它。字符串值占用的空间比整数多。但是,如果涉及持久性,请为枚举中的每个项目提供确定性值,以便
Female = 1
,Male = 2
如果枚举被添加到以后或者项目的顺序发生变化,那么您最终不会有问题。
不完全是我想要的,但似乎没有别的办法。
答案 7 :(得分:1)
如果您使用的是.NET Core 3.1和更高版本,请使用Microsoft最新的超快速Json序列化器/反序列化器System.Text.Json(https://www.nuget.org/packages/System.Text.Json)。
在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; }
}
答案 8 :(得分:0)
此处发布的答案适用于DocumentClientException
和TEnum
,但是不适用于TEnum[]
。使用代码初始化序列化程序时,您可以实现此目的,但是我想通过属性来实现。我创建了一个灵活的Dictionary<TEnum, object>
,可以为该键和值配置一个序列化器。
DictionarySerializer
这样的用法,其中key和value都是枚举类型,但是可以是序列化程序的任意组合:
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) { }
}