MongoDb c#驱动程序枚举映射

时间:2019-02-22 14:38:13

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

我有枚举:

public enum SomeType
{
   TypeA,
   TypeB, 
   TypeC
}

但是在MongoDB中,我想将此地图映射到: type_a type_b type_c

我正在使用EnumRepresentationConvention(BsonType.String)

我尝试过:

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

但这不起作用。我收到异常消息:

未找到请求的值'type_a'。

有人知道如何在MongoDb C#驱动程序中实现这种映射吗?

1 个答案:

答案 0 :(得分:0)

更新

所以我写了一个新的序列化程序,它可以满足您的需求。我以SharpExtensions的一部分编写的代码为基础。当然,它没有经过优化(或尽可能简化),但可以正常工作。

首先,我创建了一个示例类Foo,并重用了您的示例Enum。然后,我利用DescriptionAttribute指定了您完全控制的Enum的替代表示形式。尽管如果您利用Humanizer之类的方法来不断更改表示形式,则可以简化此操作。

然后,我创建了一个BsonSerializationProvider,以告知驱动程序何时应使用该序列化程序(类似于我的原始答案)。肉在EnumDescriptionSerializer中,它使用反射来查找特定值SomeType的字符串表示形式。这是我利用SharpExtensions中的样板代码在字符串和实际Enum值之间移动的地方。您会注意到该代码还将与EnumMemberAttributeDescriptionAttribute一起使用。如果您不想直接使用样板代码,请随时导入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);

对我来说,它可以正常工作,我不需要记住装饰枚举。