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

xdnvmnnf  于 2023-08-04  发布在  Go
关注(0)|答案(9)|浏览(138)

有没有一种方法可以将Enum存储为字符串名称而不是序号值?
示例如下:
假设我有这个枚举:

public enum Gender
{
    Female,
    Male
}

字符串
现在,如果某个虚构的用户存在

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


它将被存储在MongoDb数据库中为{...“性别”:1...}
但我更喜欢这样的东西{...“性别”:“男”……}
这可能吗?自定义Map,反射技巧,无论什么。
我的上下文:我在POCO上使用强类型集合(好吧,我标记AR并偶尔使用多态性)。我有一个工作单元形式的瘦数据访问抽象层。所以我没有序列化/反序列化每个对象,但我可以(并且确实)定义一些ClassMap。我使用官方的MongoDb驱动程序+ fluent-mongodb。

bttbmeg0

bttbmeg01#

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

字符串

p8ekf7hl

p8ekf7hl2#

MongoDB .NET驱动程序lets you apply conventions,用于确定如何处理CLR类型和数据库元素之间的某些Map。
如果你想将其应用于所有枚举,你只需要为每个AppDomain设置一次约定(通常在启动应用程序时),而不是将属性添加到所有类型或手动Map每个类型:

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

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

字符串

rkue9o1l

rkue9o1l3#

可以为包含枚举的类自定义类Map,并指定成员由字符串表示。这将处理枚举的序列化和反序列化。

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

         });
      }

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

uujelgoq

uujelgoq4#

我发现在某些情况下,仅仅应用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的实现,它会解析装箱值的TypeCodeInt32用于枚举值),并使用该类型将枚举值存储在数据库中。因此,装箱的枚举值最终被序列化为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)将用于存储枚举类型及其字符串值。

cigdeys3

cigdeys35#

在驱动程序2.x中,我使用特定的串行器解决了这个问题:

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

字符串

6jygbczu

6jygbczu6#

使用MemberSerializationOptionsConvention定义枚举保存的约定。

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

字符串

gwbalxhn

gwbalxhn7#

如果您使用的是.NET Core 3.1及更高版本,请使用Microsoft最新的超快Json Serializer/Deserializer,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; }
}

字符串

68bkxrlz

68bkxrlz8#

这里给出的答案适用于TEnumTEnum[],但不适用于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; }

bejyjqdl

bejyjqdl9#

.NET 7.0

改进@sboisse非常好的答案,我已经找到了一种满足我所有用例的方法。

通用枚举序列化器

// for boxed enums
BsonSerializer.RegisterSerializer(typeof(object), new BoxedEnumStringSerializer());
// for specifix unboxed enum
BsonSerializer.RegisterSerializer(typeof(MyEnum), new EnumStringSerializer<MyEnum>());

个字符

注册assembly中的每个enum

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


备注:

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

相关问题