实体框架核心-一般设置值转换器

时间:2018-10-24 11:48:03

标签: c# entity-framework-core ef-core-2.1

我目前正在试用Entity Framework Core 2.1,以期在我工作的公司的业务应用程序中使用它。我已经在测试项目中实现了价值转换器的大部分方法,但是我现有的知识库使我无奈了!

我要做什么

我的理解是,对于枚举值,内置的类型转换器可以从枚举值转换为等效的字符串(EnumToStringConverter),也可以从枚举值转换为其数值表示形式(EnumToNumberConverter)。但是,我们使用自定义字符串值表示数据库中的枚举,因此我编写了一个自定义EnumToDbStringEquivalentConvertor进行此转换,并将数据库字符串值指定为模型中每个枚举值的属性。

代码如下:

模型

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; }

    foreach (var entityType in modelBuilder.Model.GetEntityTypes())
    {
            foreach (var property in entityType.GetProperties())
            {
                if (property.ClrType.IsEnum)
                {
                    property.SetValueConverter(new EnumToDbStringEquivalentConvertor<EmployeeType>());
                }
             }
         }
    }
}

价值转换器

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
    }
}

此代码(我很高兴地说)似乎可以正常工作。

我的问题

关于值转换器的文档似乎建议我们在OnModelCreating方法中分配它们的方式是将每个单独的类型转换器物理地分配给模型中的每个单独的属性。我不想这样做-我希望我的模型成为驱动程序。稍后我将实现它,但是现在,在当前版本的代码中,我将遍历模型中的实体类型,检查“ IsEnum”属性值,然后在该位置分配值转换器。

我的问题是我正在使用的SetValueConverter扩展方法要求我向其传递EnumToDbStringEquivalentConvertor的新实例,在我的示例中,该实例被硬编码为EnumToDbStringEquivalentConvertor可以正常工作。但是我不希望将其硬编码-我想传递实体类型的ClrType。

我以前曾使用反射来创建泛型类型和泛型方法,但我似乎找不到合适的代码来使之正常工作。

此:

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) });
                }
             }
         }
    }
}

在Microsoft.EntityFrameworkCore.Infrastructure的GetModel方法上给我一个错误“ System.MissingMethodException :'没有为此对象定义无参数的构造函数。'”

所以我的问题是,有人可以建议我如何将我的值转换器通用地传递给EF Core的“ SetValueConveter”方法吗?

预先感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

您快到了。问题是这段代码

Activator.CreateInstance(genericConverterType)

尝试查找并调用转换器类的无参数构造函数。但是您的类构造函数确实具有参数,尽管是可选的。常规参数只是编译器的糖,在使用反射时,应显式传递它们。

因此,您需要使用CreateInstance overload接受params object[] args并将null传递给mappingHints

此外,也无需通过反射调用SetValueConverter-它是公共API的一部分。

工作代码可能是这样的:

if (property.ClrType.IsEnum)
{
    var converterType = typeof(EnumToDbStringEquivalentConvertor<>)
        .MakeGenericType(property.ClrType);    
    var converter = (ValueConverter)Activator.CreateInstance(converterType, (object)null);
    property.SetValueConverter(converter);
}