在MongoDB中以字符串形式存储枚举
本文关键字:存储 枚举 字符串 MongoDB | 更新日期: 2023-09-27 18:05:12
是否有一种方法将枚举存储为字符串名称而不是序数值?
的例子:
假设我有这个enum:
public enum Gender
{
Female,
Male
}
现在如果有一个假想的用户存在
...
Gender gender = Gender.Male;
...
它将存储在MongoDb数据库中为{…"性别":1……}
但是我更喜欢这样的{…"性别":"男性"……}
这可能吗?自定义映射,反射技巧,等等。
我的上下文:我在POCO上使用强类型集合(好吧,我标记ar并偶尔使用多态性)。我有一个工作单元形式的瘦数据访问抽象层。所以我没有序列化/反序列化每个对象,但我可以(也确实)定义一些classmap。我使用官方的MongoDb驱动+ fluent-mongodb
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public class Person
{
[JsonConverter(typeof(StringEnumConverter))] // JSON.Net
[BsonRepresentation(BsonType.String)] // Mongo
public Gender Gender { get; set; }
}
MongoDB . net Driver允许您应用约定来确定CLR类型和数据库元素之间的某些映射是如何处理的。
如果你想让它适用于所有的枚举,你只需要为每个AppDomain设置一次约定(通常在启动应用程序时),而不是为所有类型添加属性或手动映射每个类型:
// Set up MongoDB conventions
var pack = new ConventionPack
{
new EnumRepresentationConvention(BsonType.String)
};
ConventionRegistry.Register("EnumStringConvention", pack, t => true);
可以为包含枚举的类定制类映射,并指定该成员用字符串表示。这将处理枚举的序列化和反序列化。
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);
});
}
我仍然在寻找一种方法来指定枚举全局表示为字符串,但这是我目前使用的方法。
我发现,只是应用里卡多罗德里格斯的答案是不够的,在某些情况下,正确序列化枚举值字符串到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
(枚举值的Int32
),并使用该类型将枚举值存储在数据库中。因此盒装枚举值最终被序列化为int
值。在反序列化时,它们也将保持为int
值。
要改变这种情况,可以编写自定义ObjectSerializer
,如果盒装值是enum,则强制设置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类型及其字符串值。
与驱动程序2。我使用一个特定的序列化器来解决:
BsonClassMap.RegisterClassMap<Person>(cm =>
{
cm.AutoMap();
cm.MapMember(c => c.Gender).SetSerializer(new EnumSerializer<Gender>(BsonType.String));
});
使用MemberSerializationOptionsConvention定义如何保存enum的约定。
new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
如果你使用的是。net Core 3.1及以上版本,请使用微软最新的超快速Json序列化器/反序列化器,System.Text.Json (https://www.nuget.org/packages/System.Text.Json)。
查看指标比较:https://medium.com/@samichkhachkhi/system-text- jsonvs-newtonsoftjsond01935068143
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; }
}
这里发布的答案适用于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; }
。NET 7.0
改进@sboisse非常好的答案,我找到了一种满足我所有用例的方法。
泛型枚举序列化器
// for boxed enums
BsonSerializer.RegisterSerializer(typeof(object), new BoxedEnumStringSerializer());
// for specifix unboxed enum
BsonSerializer.RegisterSerializer(typeof(MyEnum), new EnumStringSerializer<MyEnum>());
// serializer class
public class BoxedEnumStringSerializer : ObjectSerializer
{
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
var bsonWriter = context.Writer;
string? serialized = null;
if (value.GetType().IsEnum && value.ToString() is string valStr)
{
var conventions = ConventionRegistry.Lookup(value.GetType());
var enumRpz = conventions.Conventions.FirstOrDefault(convention => convention is EnumRepresentationConvention) as EnumRepresentationConvention;
switch (enumRpz?.Representation)
{
case BsonType.String:
serialized = valStr;
break;
}
}
if (serialized != null)
base.Serialize(context, args, serialized);
else
base.Serialize(context, args, value);
}
public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var val = context.Reader.ReadString();
return Enum.Parse(args.NominalType, val);
}
}
public class EnumStringSerializer<T> : BoxedEnumStringSerializer, IBsonSerializer<T> where T : struct, Enum
{
public new Type ValueType => typeof(T);
public void Serialize(BsonSerializationContext context, BsonSerializationArgs args, T value)
{
base.Serialize(context, args, value);
}
T IBsonSerializer<T>.Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
return (T)base.Deserialize(context, args);
}
}
注册程序集中的每个枚举
var enums = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsEnum && t.IsPublic);
foreach (var e in enums)
{
var serializer = typeof(EnumStringSerializer<>).MakeGenericType(e);
BsonSerializer.RegisterSerializer(e, Activator.CreateInstance(serializer) as IBsonSerializer);
}
指出:
- 如果你想在汇编中自动注册所有枚举,你可以考虑创建自己的enum属性,以便标记它们,而不是盲目地将它们全部取走
- 这是一个使用自定义枚举序列化案例的好模板,我使用蛇形案例,因此convention pack无法工作