将Enums存储为MongoDB中的字符串

时间:2011-08-09 12:47:32

标签: c# mongodb mongodb-.net-driver

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

示例:

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

public enum Gender
{
    Female,
    Male
}

现在,如果某个想象中的用户存在

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

它将以{...“Gender”:1 ...}

存储在MongoDb数据库中

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

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

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

9 个答案:

答案 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 = 1Male = 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)

此处发布的答案适用于DocumentClientExceptionTEnum,但是不适用于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) { }
}