Open xamadev opened 4 years ago
@xamadev Can you explain a bit more what you mean by "aliases" in this case?
Sure. Imagine you have a status column in database which contains string values of "S" or "E". In code I'd like to map them to an enum with values Enum.Success and Enum.Error which I call aliases in my question. Therefore I need a mapping between the String value and the corresponding enum value.
@xamadev The way to handle that is with a custom value converter. Something like:
public enum Worm
{
Slow,
Earth
}
public class WormConverter : ValueConverter<Worm, string>
{
public WormConverter()
: base(v => Convert(v), v => Convert(v))
{
}
private static string Convert(Worm value)
{
return value switch
{
Worm.Slow => "S",
Worm.Earth => "E",
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
};
}
private static Worm Convert(string value)
{
return value switch
{
"S" => Worm.Slow,
"E" => Worm.Earth,
_ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
};
}
}
All right, thanks for your help. Maybe it's worth introducing a data annotation to define such aliases / mappings? What do you think?
@xamadev I'm not sure if it should be a new attribute - but this feels like it should line up with the enum as string serialization attributes like System.Runtime.Serialization.EnumMember
@CZEMacLeod EnumMember is interesting--I wasn't aware of it before now. We will discuss.
@ajcvickers System.Runtime.Serialization.EnumMember
is used by DataContractSerializer
and is also used by Json.NET's Newtonsoft.Json.Converters.StringEnumConverter
.
The class Newtonsoft.Json.Utilities.EnumUtils
seems to have some good code for Enum->String and String->Enum handling.
I'm not sure about Microsoft's System.Text.Json
implementation JsonStringEnumConverter
but it doesn't look like it :(
I got as far as System.Text.Json.Serialization.Converters.EnumConverter
but there is a comment in the docs stating that
Attributes from the
[System.Runtime.Serialization](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization)
namespace aren't currently supported inSystem.Text.Json
.
Although it does look like there are extension points for it with custom converters.
It does seem like there is a fair bit of re-inventing the wheel going on here between all these projects doing value conversion and specifically Enum
/String
conversion...
From my point of view, adding the possibility to automatically export the enum text and value mapping into the field description would be a great feature to increase the readability of such columns... db-tools like dbeaver show the column description if you hover the column.
I wrote a value converter that can handle EnumMemberAttribute
:
/// <summary>
/// A value converter for enums that use the <see cref="EnumMemberAttribute"/> value as the stored value.
/// </summary>
/// <remarks>
/// It turns out that EF Core doesn't respect the EnumMemberAttribute yet.
/// </remarks>
/// <typeparam name="TEnum">The enum type.</typeparam>
public class EnumMemberValueConverter<TEnum> : ValueConverter<TEnum, string> where TEnum : struct, Enum
{
// Yeah, these dictionaries are never collected, but they're going to generally be small and
// there's not going to be too many of them.
static readonly Dictionary<string, TEnum> StringToEnumLookup = CreateStringToEnumLookup();
static readonly Dictionary<TEnum, string> EnumToStringLookup = CreateEnumToStringLookup(StringToEnumLookup);
public EnumMemberValueConverter()
: base(
value => GetStringFromEnum(value),
storedValue => GetEnumFromString(storedValue))
{
}
static TEnum GetEnumFromString(string value) => StringToEnumLookup.TryGetValue(value, out var enumValue)
? enumValue
: default;
static string GetStringFromEnum(TEnum value) => EnumToStringLookup.TryGetValue(value, out var enumValue)
? enumValue
: string.Empty;
static Dictionary<string, TEnum> CreateStringToEnumLookup() =>
Enum.GetValues<TEnum>().ToDictionary(value => value.GetEnumMemberValueName(), value => value);
static Dictionary<TEnum, string> CreateEnumToStringLookup(Dictionary<string, TEnum> stringToEnumLookup) =>
stringToEnumLookup.ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
}
public static class EnumExtensions {
public static string GetEnumMemberValueName(this Enum? item)
=> item.GetEnumAttributeValue<EnumMemberAttribute>(attr => attr.Value);
static string GetEnumAttributeValue<TAttribute>(this Enum? item, Func<TAttribute, string?> getAttributeValue)
where TAttribute : Attribute
{
var field = item?.GetType().GetField(item.ToString());
if (field is null)
return string.Empty;
var memberAttribute = field.GetCustomAttribute<TAttribute>(inherit: false);
if (memberAttribute is not null)
{
return getAttributeValue(memberAttribute) ?? field.Name;
}
return field.Name;
}
}
Note that these dictionaries are never collected until the process goes down, but the expectation is there won't be too many values so the overhead should be small.
If an enum value doesn't have the attribute, then the normal conversion to/from string
applies.
Just register the converter in your OnModelCreating
method of your DbContext
derived class like so:
modelBuilder.Entity<MyEntity>()
.Property(a => a.EnumProperty)
.HasConversion(new EnumMemberValueConverter<EnumPropertyType>());
That will let you use enum aliases until EF supports it natively.
@haacked This looks like a good start, although I think it would be better to use a source generator to build the converter with the finite list of values rather than reflection to build the dictionaries. Also it does not cover enums which are defined as Flags (which most of my enums stored as text in the database happen to be). Although this may be a subset of subset of the use case and not required by many, it would be better if any given solution actually covered this case.
Hi,
is it possible to define aliases for enum's values like using the Description annotation?
Document Details
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.