I'm currently trialing Entity Framework Core 2.1 with a view to using it in the company I work for's business applications. I've got most of the way in implementing Value Converters in my test project but my existing knowledge base has let me down at the last hurdle!
My understanding is that for enum values, the built in type converters can convert from the enum value to the string equivalent (EnumToStringConverter) or from the enum value to it's numerical representation (EnumToNumberConverter). However we use a custom string value to represent the enum in our database, so I have written a custom EnumToDbStringEquivalentConvertor to do this conversion and the database string value is specified as an attribute on each of the enum values in my model.
The code is as follows:
Model
public class User
{
[Key] public int ID { get; set; }
public EmployeeType EmployeeType { get; set; }
}
public enum EmployeeType
{
[EnumDbStringValue("D")]
Director,
[EnumDbStringValue("W")]
Weekly,
[EnumDbStringValue("S")]
Salaried
}
DataContext
public class MyDataContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.IsEnum)
{
property.SetValueConverter(new EnumToDbStringEquivalentConvertor<EmployeeType>());
}
}
}
}
}
Value Converter
public class EnumToDbStringEquivalentConvertor<T> : ValueConverter<T, string>
{
public EnumToDbStringEquivalentConvertor(ConverterMappingHints mappingHints = null) : base(convertToProviderExpression, convertFromProviderExpression, mappingHints)
{ }
private static Expression<Func<T, string>> convertToProviderExpression = x => ToDbString(x);
private static Expression<Func<string, T>> convertFromProviderExpression = x => ToEnum<T>(x);
public static string ToDbString<TEnum>(TEnum tEnum)
{
var enumType = tEnum.GetType();
var enumTypeMemberInfo = enumType.GetMember(tEnum.ToString());
EnumDbStringValueAttribute enumDbStringValueAttribute = (EnumDbStringValueAttribute)enumTypeMemberInfo[0]
.GetCustomAttributes(typeof(EnumDbStringValueAttribute), false)
.FirstOrDefault();
return enumDbStringValueAttribute.StringValue;
}
public static TEnum ToEnum<TEnum>(string stringValue)
{
// Code not included for brevity
}
}
This code (I'm glad to say) seems to be working without any issues.
The documentation around value converters seems to suggest the way we assign them in the OnModelCreating method is to physically assign each individual type converter to each individual property in the model. I don't want to have to do this - I want my model to be the driver. I'll implement this later but, for now, in the current version of the code I'm looping through the entity types in my model, checking the 'IsEnum' property value and then assigning the value converter at that point.
My problem is that the SetValueConverter extension method that I'm using requires me to pass it a new instance of EnumToDbStringEquivalentConvertor, which in my example is hard coded to be EnumToDbStringEquivalentConvertor which works. However I don't want that to be hardcoded - I want to pass the entity type's ClrType.
I have used reflection to create generic types and generic methods before but I can't seem to find the right code to get this working.
This:
public class MyDataContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.IsEnum)
{
var converterType = typeof(EnumToDbStringEquivalentConvertor<>);
var genericConverterType = converterType.MakeGenericType(property.ClrType);
MethodInfo setValueConverterMethodInfo = typeof(MutablePropertyExtensions).GetMethod("SetValueConverter");
setValueConverterMethodInfo.Invoke(property,
new object[] { property, Activator.CreateInstance(genericConverterType) });
}
}
}
}
}
gives me an error of "System.MissingMethodException: 'No parameterless constructor defined for this object.'" on the GetModel method in Microsoft.EntityFrameworkCore.Infrastructure
So my question is can anyone advise me of how I can pass my value converter generically to EF Core's 'SetValueConveter' method?
Thank you in advance for your assistance.
You are almost there. The problem is this code
Activator.CreateInstance(genericConverterType)
which tries to find and invoke parameterless constructor of your converter class. But your class constructor does have a parameter, although optional. Optional parameters are just compiler sugar; when using reflection you should pass them explicitly.
So you need to use the CreateInstance
overload accepting params object[] args
and pass null
for mappingHints
.
Also, there is no need to call SetValueConverter via reflection - it's part of the public API.
The working code could be like this:
if (property.ClrType.IsEnum)
{
var converterType = typeof(EnumToDbStringEquivalentConvertor<>)
.MakeGenericType(property.ClrType);
var converter = (ValueConverter)Activator.CreateInstance(converterType, (object)null);
property.SetValueConverter(converter);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With