【问题标题】:Custom serialization of enums in MongoDBMongoDB中枚举的自定义序列化
【发布时间】:2015-10-21 15:33:15
【问题描述】:

引用这个SO question 关于自定义序列化字符串到枚举,反之亦然在 Json.NET 中,使用 EnumMember 属性装饰枚举成员 - 有没有办法让 MongoDB 执行相同的壮举?

我刚刚将一些以前的字符串字段重构为枚举,并且想知道是否有任何方法可以指示 Mongo 在(反)序列化时也读取 EnumMember 值,并避免我必须通过数据库并更新所有当前文本值。

【问题讨论】:

  • 仅供参考 - 我对集合中的相关字段进行了 JS 更新 - 很简单。知道这是否可能仍然很有趣。

标签: c# mongodb serialization enums


【解决方案1】:

我正在使用包:PackageReference Include="MongoDB.Bson" Version="2.12.1"

我的地图类:

    public class OfferMap
{
    public static void Configure()
    {
        BsonClassMap.RegisterClassMap<Offer>(map => //Offer is a class
        {
            map.AutoMap();
            map.SetIgnoreExtraElements(true);

            map
            .SetIsRootClass(true);

            map
            .MapMember(x => x.OfferType)
            .SetSerializer(new EnumSerializer<OfferType>(MongoDB.Bson.BsonType.String)) // OfferType is an Enum
            .SetElementName("offerType")
            .SetIgnoreIfNull(false)
            .SetIsRequired(true);

【讨论】:

    【解决方案2】:

    我使用 CustomEnumSerializer 来处理 EnumMember 属性

    public class CustomEnumSerializer<TEnum> : StructSerializerBase<TEnum>, IRepresentationConfigurable<CustomEnumSerializer<TEnum>> where TEnum : struct
    {
        private static readonly Dictionary<Type, Dictionary<string, object>> _fromValueMap = new Dictionary<Type, Dictionary<string, object>>(); // string representation to Enum value map
    
        private static readonly Dictionary<Type, Dictionary<object, string>> _toValueMap = new Dictionary<Type, Dictionary<object, string>>(); // Enum value to string map
    
        // private fields
        private readonly BsonType _representation;
    
        // constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="EnumSerializer{TEnum}"/> class.
        /// </summary>
        public CustomEnumSerializer()
            : this((BsonType)0) // 0 means use underlying type
        {
        }
    
        /// <summary>
        /// Initializes a new instance of the <see cref="EnumSerializer{TEnum}"/> class.
        /// </summary>
        /// <param name="representation">The representation.</param>
        public CustomEnumSerializer(BsonType representation)
        {
            switch (representation)
            {
                case 0:
                case BsonType.Int32:
                case BsonType.Int64:
                case BsonType.String:
    
                    break;
    
                default:
                    var message = string.Format("{0} is not a valid representation for an EnumSerializer.", representation);
                    throw new ArgumentException(message);
            }
    
            // don't know of a way to enforce this at compile time
            var enumTypeInfo = typeof(TEnum).GetTypeInfo();
            if (!enumTypeInfo.IsEnum)
            {
                var message = string.Format("{0} is not an enum type.", typeof(TEnum).FullName);
                throw new BsonSerializationException(message);
            }
    
            _representation = representation;
    
            if (representation == BsonType.String)
            {
                var enumType = typeof(TEnum);
                if (!_fromValueMap.ContainsKey(enumType))
                {
                    Dictionary<string, object> fromMap = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
                    Dictionary<object, string> toMap = new Dictionary<object, string>();
    
                    FieldInfo[] fields = enumType.GetFields(BindingFlags.Static | BindingFlags.Public);
    
                    foreach (FieldInfo field in fields)
                    {
                        string name = field.Name;
                        object enumValue = Enum.Parse(enumType, name);
    
                        // use EnumMember attribute if exists
                        EnumMemberAttribute enumMemberAttrbiute = field.GetCustomAttribute<EnumMemberAttribute>();
    
                        if (enumMemberAttrbiute != null)
                        {
                            string enumMemberValue = enumMemberAttrbiute.Value;
    
                            fromMap[enumMemberValue] = enumValue;
                            toMap[enumValue] = enumMemberValue;
                        }
                        else
                        {
                            toMap[enumValue] = name;
                        }
    
                        fromMap[name] = enumValue;
                    }
    
                    _fromValueMap[enumType] = fromMap;
                    _toValueMap[enumType] = toMap;
                }
            }
        }
    
        // public properties
        /// <summary>
        /// Gets the representation.
        /// </summary>
        /// <value>
        /// The representation.
        /// </value>
        public BsonType Representation
        {
            get { return _representation; }
        }
    
        // public methods
        /// <summary>
        /// Deserializes a value.
        /// </summary>
        /// <param name="context">The deserialization context.</param>
        /// <param name="args">The deserialization args.</param>
        /// <returns>A deserialized value.</returns>
        public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
        {
            var bsonReader = context.Reader;
    
            var bsonType = bsonReader.GetCurrentBsonType();
            switch (bsonType)
            {
                case BsonType.Int32: return (TEnum)Enum.ToObject(typeof(TEnum), bsonReader.ReadInt32());
                case BsonType.Int64: return (TEnum)Enum.ToObject(typeof(TEnum), bsonReader.ReadInt64());
                case BsonType.Double: return (TEnum)Enum.ToObject(typeof(TEnum), (long)bsonReader.ReadDouble());
                case BsonType.String:
                    var fromValue = FromValue(typeof(TEnum), bsonReader.ReadString());
                    return (TEnum)Enum.Parse(typeof(TEnum), fromValue.ToString());
                default:
                    throw CreateCannotDeserializeFromBsonTypeException(bsonType);
            }
        }
    
        /// <summary>
        /// Serializes a value.
        /// </summary>
        /// <param name="context">The serialization context.</param>
        /// <param name="args">The serialization args.</param>
        /// <param name="value">The object.</param>
        public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
        {
            var bsonWriter = context.Writer;
    
            switch (_representation)
            {
                case 0:
                    var underlyingTypeCode = Type.GetTypeCode(Enum.GetUnderlyingType(typeof(TEnum)));
                    if (underlyingTypeCode == TypeCode.Int64 || underlyingTypeCode == TypeCode.UInt64)
                    {
                        goto case BsonType.Int64;
                    }
                    else
                    {
                        goto case BsonType.Int32;
                    }
    
                case BsonType.Int32:
                    bsonWriter.WriteInt32(Convert.ToInt32(value));
                    break;
    
                case BsonType.Int64:
                    bsonWriter.WriteInt64(Convert.ToInt64(value));
                    break;
    
                case BsonType.String:
                    var val = ToValue(typeof(TEnum), value);
                    bsonWriter.WriteString(val);
                    break;
    
                default:
                    throw new BsonInternalException("Unexpected EnumRepresentation.");
            }
        }
    
        private string ToValue(Type enumType, object obj)
        {
            Dictionary<object, string> map = _toValueMap[enumType];
    
            return map[obj];
        }
        private object FromValue(Type enumType, string value)
        {
            Dictionary<string, object> map = _fromValueMap[enumType];
    
            if (!map.ContainsKey(value))
                return value;
    
            return map[value];
        }
    
        /// <summary>
        /// Returns a serializer that has been reconfigured with the specified representation.
        /// </summary>
        /// <param name="representation">The representation.</param>
        /// <returns>The reconfigured serializer.</returns>
        public CustomEnumSerializer<TEnum> WithRepresentation(BsonType representation)
        {
            if (representation == _representation)
            {
                return this;
            }
            else
            {
                return new CustomEnumSerializer<TEnum>(representation);
            }
        }
    
        // explicit interface implementations
        IBsonSerializer IRepresentationConfigurable.WithRepresentation(BsonType representation)
        {
            return WithRepresentation(representation);
        }
    }
    

    【讨论】:

      【解决方案3】:

      我需要一个自定义的反序列化器,当遇到数据中的意外值时,它会返回默认值,而不是抛出反序列化异常的默认行为。

         public class CustomEnumSerializer<TEnum>: MongoDB.Bson.Serialization.Serializers.EnumSerializer<TEnum>
              where TEnum : struct, IComparable, IFormattable, IConvertible
          {
              public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
              {
                  var bsonReader = context.Reader;
                  var bsonType = bsonReader.GetCurrentBsonType();
                  var val = "";
      
                  switch (bsonType)
                  {
                      case BsonType.String:
                          val = bsonReader.ReadString() ?? "";
                          break;
                      case BsonType.Int32:
                          val = bsonReader.ReadInt32().ToString();
                          break;
                      case BsonType.Int64:
                          val = bsonReader.ReadInt64().ToString();
                          break;
                      case BsonType.Null:
                          return default(TEnum);
                      default:
                          return base.Deserialize(context, args);
                  }
      
                  if(Enum.TryParse(val, true, out TEnum result)){
                      return result;
                  }
                  return default(TEnum);
              }
      
          }
      

      在您的存储库中实现它:

          static MyRepository()
          {
              BsonClassMap.RegisterClassMap<MyDataType>(ms =>
              {
                  ms.AutoMap();
                  ms.GetMemberMap(i => i.MyEnum)
                  .SetSerializer(new CustomEnumSerializer<MyEnumType>());
              });
          }
      

      【讨论】:

        猜你喜欢
        • 2021-05-11
        • 2014-12-12
        • 1970-01-01
        • 1970-01-01
        • 2013-03-09
        • 2015-11-15
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多