将Enums存储为MongoDB中的字符串

Kos*_*oid 65 c# mongodb mongodb-.net-driver

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

例:

想象一下,我有这个枚举:

public enum Gender
{
    Female,
    Male
}
Run Code Online (Sandbox Code Playgroud)

现在,如果有一些想象中的用户存在

...
Gender gender = Gender.Male;
...
Run Code Online (Sandbox Code Playgroud)

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

但我更喜欢这样的事情{......"性别":"男性"...}

这可能吗?自定义映射,反射技巧,等等.

我的上下文:我使用强类型集合而不是POCO(好吧,我标记AR并偶尔使用多态).我有一个工作单元形式的瘦数据访问抽象层.所以我没有序列化/反序列化每个对象,但我可以(并且确实)定义一些ClassMaps.我使用官方的MongoDb驱动程序+ fluent-mongodb.

Joh*_*zen 111

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; }
}
Run Code Online (Sandbox Code Playgroud)

  • 我正在使用最新的驱动程序并且它可以工作。应该是公认的答案。 (4认同)
  • 公认的答案是更好的方法。通常,您希望模型是持久性忽略的。如果它具有MongoDB依赖性,则可以说不再是POCO类。任何引用该模型的内容都必须引用MongoDB.Bson,即使它不参与读取/编写。存储层中最好包含有关如何读取/写入的规则。 (3认同)
  • 这是最简单的实现,没有任何开销. (2认同)

Ric*_*uez 40

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

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

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
Run Code Online (Sandbox Code Playgroud)

  • 关于这方面的文件并不精彩.我会对你要序列化的属性使用[BsonRepresentation(BsonType.String)]方法,因为它非常*非常明显(正如John Gietzen所建议的那样,我猜他是从http:// www. drdobbs.com/database/mongodb-with-c-deep-dive/240152181?pgno=2给出了他使用过的例子. (4认同)
  • 我发现这不适用于使用枚举的所有序列化情况。如果在您的数据结构中,您的枚举以对象类型装箱,就像您使用 ExpandoObjects 或将值放入 Dictionary<string, object> 以进行序列化一样,即使约定存在,枚举值也将被序列化为 int 值. 如果要序列化的标称类型是枚举类型,则 mongodb 序列化似乎可以为枚举值正常工作。但是,如果 nomil 类型是 typeof(Object),则序列化为字符串将不起作用。 (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);

         });
      }
Run Code Online (Sandbox Code Playgroud)

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

  • 对于 Mongo 2.0 驱动程序,此语法有效: BsonSerializer.RegisterSerializer(new EnumSerializer&lt;RealTimeChroState&gt;(BsonType.String)); (2认同)

sbo*_*sse 9

我发现在某些情况下仅应用Ricardo Rodriguez 的答案不足以正确序列化枚举值以字符串到 MongoDb 中:

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

ConventionRegistry.Register("EnumStringConvention", pack, t => true);
Run Code Online (Sandbox Code Playgroud)

如果您的数据结构涉及将枚举值装箱到对象中,则 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将自定义序列化器设置为用于序列化对象的序列化器:

BsonSerializer.RegisterSerializer(typeof(object), new ObjectSerializer());
Run Code Online (Sandbox Code Playgroud)

这样做将确保装箱的枚举值将像未装箱的枚举值一样存储为字符串。

但请记住,反序列化文档时,装箱的值将保留为字符串。它不会被转换回原始枚举值。如果您需要将字符串转换回原始枚举值,则可能需要在文档中添加区分字段,以便序列化程序可以知道要反序列化的枚举类型。

一种方法是存储一个 bson 文档而不仅仅是一个字符串,其中区分字段 ( _t) 和值字段 ( _v) 将用于存储枚举类型及其字符串值。


小智 5

使用MemberSerializationOptionsConvention定义关于如何保存枚举的约定.

new MemberSerializationOptionsConvention(typeof(Gender), new RepresentationSerializationOptions(BsonType.String))
Run Code Online (Sandbox Code Playgroud)


wil*_*ver 5

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

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


Chi*_*obe 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; }
}
Run Code Online (Sandbox Code Playgroud)