我有枚举:
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#驱动程序中实现这种映射吗?
答案 0 :(得分:0)
更新
所以我写了一个新的序列化程序,它可以满足您的需求。我以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);
对我来说,它可以正常工作,我不需要记住装饰枚举。