在MongoDB中自定义序列化枚举

本文关键字:序列化 枚举 自定义 MongoDB | 更新日期: 2023-09-27 18:05:36

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

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

在MongoDB中自定义序列化枚举

我正在使用package: packagerreference Include="MongoDB.Bson"Version ="2.12.1"

my map class:

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

我使用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);
    }
}

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

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