【问题标题】:Storing Enums as strings in MongoDB在 MongoDB 中将枚举存储为字符串
【发布时间】:2011-08-09 12:47:31
【问题描述】:

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

例子:

想象一下我有这个枚举:

public enum Gender
{
    Female,
    Male
}

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

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

它将作为 { ... "Gender" : 1 ... }

存储在 MongoDb 数据库中

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

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

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

【问题讨论】:

  • 我会避免它。字符串值比整数占用更多空间。但是,如果涉及持久性,我会为枚举中的每个项目赋予确定性值,因此女性 = 1,男性 = 2,因此如果稍后添加枚举或项目的顺序发生更改,那么您最终不会遇到问题。
  • 是的,我在考虑空间,但认为这不是问题,因为我只有少数情况下我更喜欢诚实的枚举。你说得对,未来的变化是困扰我的。我想标记每个枚举值确实是一个可行的解决方案。谢谢克里斯!

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


【解决方案1】:
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; }
}

【讨论】:

  • 这是最简单的实现,没有任何开销。
  • 更新 mongo 驱动程序 2.0 版后不工作 :( !!
  • 我正在使用最新的驱动程序,它可以工作。应该是公认的答案。
  • 接受的答案是更好的方法。一般来说,您希望您的模型是持久性无知的。如果它具有 MongoDB 依赖关系,它可能不再是 POCO 类。任何引用模型的东西都必须引用 MongoDB.Bson,即使它不涉及读/写。如何读/写的规则更好地包含在存储库层中。
  • 我倾向于同意@ChadHedgcock 的观点,即最好在某些应用程序或容器级别注册这些类型的依赖项。但是,这个答案显示了如何覆盖默认/常规行为,所以我认为它仍然非常有用。
【解决方案2】:

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

【讨论】:

  • 这方面的文档并不精彩。我会在您想要序列化的属性上使用 [BsonRepresentation(BsonType.String)] 方法,因为它非常很明显(正如下面的 John Gietzen 所建议的那样,我猜他是从 @ 987654322@给出了他使用的例子)。
  • 我发现这不适用于所有使用枚举的序列化情况。如果在您的数据结构中,您的枚举被装箱在一个对象类型中,就像您使用 ExpandoObjects 或将值放入 Dictionary 以进行序列化一样,即使存在约定,枚举值也将被序列化为 int 值.如果要序列化的标称类型是枚举类型,则 mongodb 序列化似乎对枚举值正常工作。但如果 nomil 类型是 typeof(Object),则序列化为字符串不起作用。
【解决方案3】:

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

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 的更精细的查询模型,但没有什么不可管理的。
  • 对于 Mongo 2.0 驱动程序,此语法有效: BsonSerializer.RegisterSerializer(new EnumSerializer(BsonType.String));
【解决方案4】:

在驱动程序 2.x 中,我使用 specific serializer 解决了问题:

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

    【讨论】:

    • 我过去使用过这个解决方案,但现在似乎在 2.x 版本的驱动程序中,类 MemberSerializationOptionsConvention 消失了。任何人都知道如何使用 2.x 驱动程序版本获得相同的结果?
    【解决方案6】:

    我发现在某些情况下仅应用 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) 将用于存储枚举类型及其字符串值。

    【讨论】:

      【解决方案7】:

      此处发布的答案适用于TEnumTEnum[],但不适用于Dictionary&lt;TEnum, object&gt;。您可以在使用代码初始化序列化程序时实现这一点,但是我想通过属性来做到这一点。我创建了一个灵活的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; }
      

      【讨论】:

        【解决方案8】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-09-05
          • 2011-09-05
          • 1970-01-01
          • 2015-06-19
          • 2018-05-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多