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


73

有没有一种方法可以将Enums存储为字符串名称而不是顺序值?

例:

想象一下我有这个枚举:

public enum Gender
{
    Female,
    Male
}

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

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

它将以{...“ Gender”:1 ...}的形式存储在MongoDb数据库中

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

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

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


7
我会避免的。字符串值比整数占用更多的空间。但是,如果涉及到持久性,我将为枚举中的每个项目提供确定性的值,因此,女性= 1,男性= 2,因此,如果将枚举添加到以后或更改项目的顺序,则最终不会出现问题。
删除

1
是的,我当时在考虑空间问题,但考虑到这不是问题,因为只有少数情况下我更喜欢诚实的枚举。而且您做对了,未来的变化令我感到困扰。我想标记每个枚举值确实是一个可行的解决方案。谢谢克里斯!
Kostassoid 2011年

Answers:


48

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

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

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

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

5
关于此的文档并不妙。我要对要序列化的属性使用[BsonRepresentation(BsonType.String)]方法,因为它非常明显(如下面的John Gietzen所建议,我猜他是从drdobbs.com/database/mongodb-获得的) with-c-deep-dive / 240152181?pgno = 2(给出了他使用的示例)。
巴特阅读

2
我发现这不适用于使用枚举的所有序列化情况。如果在您的数据结构中将枚举以对象类型装箱,就像您使用ExpandoObjects或将值放入Dictionary <string,object>进行序列化时那样,即使存在约定,枚举值也将被序列化为int值。如果要序列化的名义类型是枚举类型,则似乎mongodb序列化将对枚举值正确运行。但是,如果nomil类型为typeof(Object),则序列化为字符串不起作用。
sboisse '18年

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

2
这是最简单的实现,没有任何开销。
Deni Spasovski 2014年

更新mongo驱动程序2.0版后无法正常工作:( !!
Bimawa

我正在使用最新的驱动程序,并且可以运行。应该是公认的答案。
Henrique Miranda

3
公认的答案是更好的方法。通常,您希望模型是持久性忽略的。如果它具有MongoDB依赖性,则可以说不再是POCO类。任何引用该模型的内容都必须引用MongoDB.Bson,即使它不参与读取/写入。存储层中最好包含有关如何读取/写入的规则。
乍得Hedgcock

1
我倾向于同意@ChadHedgcock,最好在某些应用程序或容器级别注册这些类型的依赖项。但是,此答案显示了如何覆盖默认/常规行为,因此我认为它仍然非常有用。
John Gietzen

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

         });
      }

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


谢谢!这就是我想要的。我可以看到某些问题,例如用于实现cqrs的更复杂的查询模型,但没有任何无法解决的问题。
Kostassoid 2012年

1
对于Mongo 2.0驱动程序,此语法有效:BsonSerializer.RegisterSerializer(new EnumSerializer <RealTimeChroState>(BsonType.String));
BrokeMyLegBiking

5

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

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

我过去曾使用过此解决方案,但现在看来,在2.x版本的驱动程序中,类MemberSerializationOptionsConvention消失了。有谁知道如何使用2.x驱动程序版本获得相同的结果?
Alkampfer '16

4

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

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

2

我发现仅在某些情况下仅应用Ricardo Rodriguez的答案不足以正确地将枚举值序列化为String到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反序列化时,它们也将保留为值。

要更改此设置,可以编写一个自定义ObjectSerializerEnumRepresentationConvention如果装箱的值是枚举值,则将强制执行该集合。像这样:

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)存储枚举类型及其字符串值。


2

这些问题的答案张贴在这里的工作以及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) { }
}

这样的用法,其中key和value都是枚举类型,但是可以是序列化程序的任意组合:

    [BsonSerializer(typeof(DictionarySerializer<
        Dictionary<FeatureToggleTypeEnum, LicenseFeatureStateEnum>, 
        EnumStringSerializer<FeatureToggleTypeEnum>,
        EnumStringSerializer<LicenseFeatureStateEnum>>))]
    public Dictionary<FeatureToggleTypeEnum, LicenseFeatureStateEnum> FeatureSettings { get; set; }

1

正如克里斯·史密斯(Chris Smith)在评论中所建议的,我最终为枚举项分配了值:

我会避免的。字符串值比整数占用更多的空间。但是,如果涉及到持久性,我将为您的枚举中的每个项目提供确定性的值,因此Female = 1Male = 2如果将枚举添加到以后或更改项目的顺序,则最终不会出现问题。

并不是我一直在寻找的东西,但似乎没有其他办法。


1
对此不赞成-1:可接受的答案适用于所提出的问题,并且是一个很好的答案,因此不应更改。在进一步审查之后,询问者更改了问题/方法,并简单地将此答案添加为更多信息。但是我同意应该将其添加为注释,而不是其他答案。
天鹅网,2015年

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; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.