Entity Framework如何为TPH类型通用设置ValueConverter?
我编写了如下扩展方法,可为实体中所有指定类型的属性统一设置ValueConverter:
public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter) { var entityTypes = modelBuilder.Model.GetEntityTypes().ToList(); foreach (var entityType in entityTypes) { var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == type); foreach (var property in properties) modelBuilder.Entity(entityType.Name).Property(property.Name) .HasConversion(converter); return modelBuilder; }
该方法对于我DbContext中DbSet对应的实体运行正常。
我现在有部分实体采用了表-per-hierarchy(TPH)配置,代码如下:
modelBuilder.Entity<ModLogEntry>() .HasDiscriminator<string>("discriminator") .HasValue<ActionModLogEntry>("action_entry") .HasValue<RevertedActionModLogEntry>("reverted_action_entry") .HasValue<RevertModLogEntry>("revert_entry") .HasValue<SupportBanModLogEntry>("support_ban_entry") .HasValue<SupportUnbanModLogEntry>("support_unban_entry");
这些TPH子类型不会被modelBuilder.Model.GetEntityTypes()返回,因此现有扩展方法无法对其生效。
我尝试在扩展方法中加入如下代码,遍历程序集中继承自DbSet对应实体的子类并为其属性应用转换器:
// within the foreach entityType in entityTypes var assembly = Assembly.GetAssembly(entityType.ClrType); var childTypes = assembly.GetTypes().Where(x => entityType.ClrType.IsAssignableFrom(x)); foreach (var childType in childTypes) { var childProperties = childType.GetProperties().Where(p => p.PropertyType == type); foreach (var property in childProperties) modelBuilder.Entity(childType.Name).Property(property.Name) .HasConversion(converter); }
但运行后抛出异常:
System.InvalidOperationException: The property 'ModeratorId' cannot be added to the type 'ActionModLogEntry' because no property type was specified and there is no corresponding CLR property or field. To add a shadow state property, the property type must be specified.
如果我手动指定子属性的类型,又会抛出另一个异常:
System.InvalidOperationException: The entity type 'ActionModLogEntry' is in shadow state. A valid model requires all entity types to have corresponding CLR type.
错误原因
报错核心来自三个问题:
- 调用
modelBuilder.Entity()时传入的是实体类型名称字符串,EF无法关联到对应的CLR类型,会误以为你要创建影子实体,从而触发类型校验错误。 - 原扩展方法存在语法错误,遍历实体类型的循环缺少闭合大括号,导致仅处理第一个实体就直接返回。
- 手动扫描程序集获取子类的逻辑没有校验该子类是否已纳入EF模型,会尝试给未注册的实体配置属性,引发异常。
修正方案
实际上EF Core的modelBuilder.Model.GetEntityTypes()方法本身就会返回所有已纳入模型的TPH子类型,你只需要调整两个点即可:
- 保证
UseValueConverterForType扩展方法的调用时机在所有TPH配置完成之后 - 改用
IMutableEntityType提供的API直接配置属性,不用再调用modelBuilder.Entity()重复查找实体
修正后的代码如下:
public static ModelBuilder UseValueConverterForType(this ModelBuilder modelBuilder, Type type, ValueConverter converter) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { // 查找当前实体类型下所有目标类型的属性 var properties = entityType.GetProperties() .Where(p => p.ClrType == type); foreach (var property in properties) { // 直接通过IMutableProperty设置转换器,无需二次查找实体 property.SetValueConverter(converter); } } return modelBuilder; }
如果确实需要手动处理未被自动纳入的子类,也需要先判断类型是否已在模型中存在,同时传入CLR类型对象操作:
// 可选补充逻辑,仅处理已注册的子类型 foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { var baseType = entityType.ClrType.BaseType; while (baseType != null && baseType != typeof(object)) { var baseEntityType = modelBuilder.Model.FindEntityType(baseType); if (baseEntityType != null) { // 可在此处补充继承基类的转换器配置逻辑 break; } baseType = baseType.BaseType; } }
内容的提问来源于stack exchange,提问作者230Daniel

