ОБНОВЛЕНИЕ
Итак, я написал новый сериализатор, который делает то, что вам нужно.Я создал его из кода, написанного мной как часть 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);
Для меня это просто работает, и мне не нужно помнить, чтобы украшать перечисления.