将枚举作为字符串存储在 MongoDB 中

问题描述 投票:0回答:10

有没有办法将枚举存储为字符串名称而不是序数值?

示例:

想象我有这个枚举:

public enum Gender
{
    Female,
    Male
}

现在如果某个虚构的用户存在

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

它将作为 { ... "Gender" : 1 ... } 存储在 MongoDb 数据库中

但我更喜欢这样的东西 { ... "Gender" : "Male" ... }

这可能吗?自定义映射、反射技巧等等。

我的背景:我在 POCO 上使用强类型集合(嗯,我标记 AR 并偶尔使用多态性)。我有一个工作单元形式的薄数据访问抽象层。所以我不会序列化/反序列化每个对象,但我可以(并且确实)定义一些 ClassMap。我使用官方 MongoDb 驱动 + Fluent-mongodb。

c# mongodb mongodb-.net-driver
10个回答
159
投票
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; }
}

67
投票

MongoDB .NET 驱动程序允许您应用约定来确定如何处理 CLR 类型和数据库元素之间的某些映射。

如果您希望这适用于所有枚举,则只需为每个 AppDomain 设置一次约定(通常在启动应用程序时),而不是向所有类型添加属性或手动映射每种类型:

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

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

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

         });
      }

我仍在寻找一种方法来指定枚举全局表示为字符串,但这是我当前正在使用的方法。


9
投票

我发现仅应用 Ricardo Rodriguez' 答案在某些情况下不足以正确地将枚举值序列化为 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
) 将用于存储枚举类型及其字符串值.


7
投票

使用驱动程序 2.x,我使用特定序列化器解决了问题:

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

5
投票

使用 MemberSerializationOptionsConvention 定义有关如何保存枚举的约定。

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

5
投票

如果您使用 .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; }
}

2
投票

此处发布的答案适用于

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

0
投票

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

备注:

  • 如果您想自动注册程序集中的所有枚举,您可以考虑创建自己的枚举属性来标记它们,而不是盲目地全部采用
  • 这是一个使用自定义枚举序列化案例的好模板,我使用蛇案例,因此约定包无法工作

0
投票
            ConventionRegistry.Register(
            name: "EnumRepresentation",
            conventions: new ConventionPack { new EnumRepresentationConvention(BsonType.String) },
            filter: _ => true);
© www.soinside.com 2019 - 2024. All rights reserved.