Entity Framework 核心 - 通用设置值转换器

Entity Framework Core - Setting Value Converter generically

我目前正在试用 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; }

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

值转换器

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: 'No parameterless constructor defined for this object.'”

所以我的问题是任何人都可以告诉我如何将我的值转换器一般传递给 EF Core 的 'SetValueConveter' 方法吗?

提前感谢您的帮助。

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

Activator.CreateInstance(genericConverterType)

它试图找到并调用转换器的无参数构造函数 class。但是您的 class 构造函数 确实有 一个参数,尽管是可选的。可选参数只是编译器的糖分;使用反射时,您应该显式传递它们。

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

此外,无需通过反射调用 SetValueConverter - 它是 public API.

的一部分

工作代码可能是这样的:

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