MongoDb c#驱动枚举映射

问题描述 投票:1回答:1

我有Enum:

public enum SomeType
{
   TypeA,
   TypeB, 
   TypeC
}

但是在MongoDB中我希望这个地图:键入类型b类型c

我正在使用EnumRepresentationConvention(BsonType.String)

我试过了:

public enum SomeType
{
   [BsonElement("type_a")]
   TypeA,
   [BsonElement("type_b")]
   TypeB, 
   [BsonElement("type_c")]
   TypeC
}

但这不起作用。我得到例外:

未找到请求的值“type_a”。

有谁知道如何在MongoDb C#驱动程序中实现这样的映射?

c# mongodb mongodb-.net-driver
1个回答
0
投票

UPDATE

所以我写了一个新的序列化器来完成你需要的东西。我把它作为SharpExtensions的一部分写的一些代码构建了它。它当然没有优化(或尽可能简化),但它的工作原理。

首先,我创建了一个示例类Foo并重用了您的示例Enum。然后我利用DescriptionAttribute来指定你完全控制的Enum的替代表示。虽然如果你利用像Humanizer这样的东西来持续改变表示,这可能会被简化。

然后我创建了一个BsonSerializationProvider让驱动程序知道何时应该使用该序列化器(很像我的原始答案)。肉是在EnumDescriptionSerializer,它使用反射来找到SomeType特定值的字符串表示。这是我利用SharpExtensions的样板代码在字符串和实际的Enum值之间移动的地方。您会注意到代码也适用于EnumMemberAttribute以及DescriptionAttribute。如果您不想直接使用样板代码,请随意导入SharpExtensions库。

public class Foo
{
    public ObjectId Id {get;set;}
    public SomeType Enum {get;set;}
}

public enum SomeType
{
    [Description("type_a")]
    TypeA,
    [Description("type_b")]
    TypeB,
    [Description("type_c")]
    TypeC
}

public class EnumDescriptionSerializerProvider : BsonSerializationProviderBase
{
    public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
    {
        if (!type.GetTypeInfo().IsEnum) return null;

        var enumSerializerType = typeof(EnumDescriptionSerializer<>).MakeGenericType(type);
        var enumSerializerConstructor = enumSerializerType.GetConstructor(new Type[0]);
        var enumSerializer = (IBsonSerializer)enumSerializerConstructor?.Invoke(new object[0]);

        return enumSerializer;
    }
}

public class EnumDescriptionSerializer<TEnum> : StructSerializerBase<TEnum> where TEnum : struct
{
    public BsonType Representation => BsonType.String;

    public override TEnum Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
    {
        var valAsString = context.Reader.ReadString();
        var enumValue = valAsString.GetValueFromDescription<TEnum>();
        return enumValue;
    }

    public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnum value)
    {
        context.Writer.WriteString(value.GetDescription());
    }
}

public static class EnumExtensions
{

    public enum StringCase
    {
        /// <summary>
        /// The default capitalization
        /// </summary>
        Default,
        /// <summary>
        /// Lower Case, ex. i like widgets.
        /// </summary>
        [Description("Lower Case")]
        Lower,
        /// <summary>
        /// Upper Case, ex. I LIKE WIDGETS.
        /// </summary>
        [Description("Upper Case")]
        Upper,
        /// <summary>
        /// Lower Camelcase, ex: iLikeWidgets.
        /// </summary>
        [Description("Lower Camelcase")]
        LowerCamel,
        /// <summary>
        /// Upper Camelcase, ex: ILikeWidgets.
        /// </summary>
        [Description("Upper Camelcase")]
        UpperCamel
    }

    /// <summary>
    /// Get the value of an enum as a string.
    /// </summary>
    /// <param name="val"> The enum to convert to a <see cref="string"/>. </param>
    /// <param name="case"> A <see cref="StringCase"/> indicating which case to return.  Valid enumerations are StringCase.Lower and StringCase.Upper. </param>
    /// <exception cref="ArgumentNullException"> If the enum is null. </exception>
    /// <returns></returns>
    public static string GetName<TEnum>(this TEnum val, StringCase @case = StringCase.Default) where TEnum : struct
    {
        var name = Enum.GetName(val.GetType(), val);
        if (name == null) return null;

        switch (@case)
        {
            case StringCase.Lower:
                return name.ToLower();
            case StringCase.Upper:
                return name.ToUpper();
            default:
                return name;
        }
    }

    /// <summary>
    /// Gets the description for the supplied Enum Value.
    /// </summary>
    /// <param name="val">The value for which to get the description attribute.</param>
    /// <returns>The <see cref="string"/> description.</returns>
    public static string GetDescription<TEnum>(this TEnum val) where TEnum : struct
    {
        var fields = val.GetType().GetTypeInfo().GetDeclaredField(GetName(val));

        // first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
        if (fields.GetCustomAttributes(typeof(EnumMemberAttribute), false).FirstOrDefault() is EnumMemberAttribute jsonAttribute) return jsonAttribute.Value;

        // If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
        return !(fields.GetCustomAttributes(typeof(DescriptionAttribute), false).FirstOrDefault() is DescriptionAttribute attribute) ? GetName(val) : attribute.Description;
    }

    /// <summary>
    /// Get the value of an <see cref="Enum"/> based on its description attribute.
    /// </summary>
    /// <typeparam name="T">The type of the <see cref="Enum"/>.</typeparam>
    /// <param name="description">The Description attribute of the <see cref="Enum"/>.</param>
    /// <returns>The value of T or default(T) if the description is not found.</returns>
    public static T GetValueFromDescription<T>(this string description) where T : struct
    {
        if (string.IsNullOrWhiteSpace(description)) throw new ArgumentNullException(nameof(description));

        var type = typeof(T);
        if (!type.GetTypeInfo().IsEnum) throw new ArgumentOutOfRangeException(nameof(T), $"{typeof(T)} is not an Enum.");
        var fields = type.GetRuntimeFields();

        foreach (var field in fields)
        {
            if (field.Name == description) return (T)field.GetValue(null);

            // first try and pull out the EnumMemberAttribute, common when using a JsonSerializer
            if (field.GetCustomAttribute(typeof(EnumMemberAttribute), false) is EnumMemberAttribute jsonAttribute && jsonAttribute.Value == description) return (T)field.GetValue(null);

            // If that doesn't work, do the regular description, that still fails, just return a pretty ToString().
            if (field.GetCustomAttribute(typeof(DescriptionAttribute), false) is DescriptionAttribute attribute && attribute.Description == description) return (T)field.GetValue(null);
        }

        throw new Exception($"Failed to parse value {description} into enum {typeof(T)}");
    }
}

我写了一个简单的测试,将几个Foo文档插入到一个集合中。这就是他们在数据库中的样子

> db.enum.find()
{ "_id" : ObjectId("5c76c0240bba918778cc6b7f"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c0580bba918778cc6b80"), "Enum" : "type_a" }
{ "_id" : ObjectId("5c76c05d0bba918778cc6b81"), "Enum" : "type_b" }

我还证实他们正确往返。除了使用LINQPad的一些简单代码,我还没有运行任何测试。我相信这就是你要找的东西。

原始答案

我为此编写了一个自定义序列化程序,因此我可以注册它并且“正常工作”。

public class EnumAsStringSerializationProvider : BsonSerializationProviderBase
{
    public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry registry)
    {
        if (!type.GetTypeInfo().IsEnum) return null;

        var enumSerializerType = typeof(EnumSerializer<>).MakeGenericType(type);
        var enumSerializerConstructor = enumSerializerType.GetConstructor(new[] { typeof(BsonType) });
        var enumSerializer = (IBsonSerializer) enumSerializerConstructor?.Invoke(new object[] { BsonType.String });

        return enumSerializer;
    }
}

然后我用BsonSerializer注册它。

var enumAsStringSerializationProvider = new EnumAsStringSerializationProvider();
BsonSerializer.RegisterSerializationProvider(enumAsStringSerializationProvider);

对我来说,它只是工作,我不需要记得装饰枚举。

© www.soinside.com 2019 - 2024. All rights reserved.