EF Core:如何将实体属性映射为数据库执行的表达式?
现有数据模型如下:
public class Model1 { public int id { get; set; } public List<Model2> model2s { get; set; } // 其他大量常规属性... } public class Model2 { public int id { get; set; } public int model1Id { get; set; } public Model1 model1 { get; set; } // 其他大量常规属性... }
需求是判断每个Model1实例是否关联至少一个Model2。现有两种解决方案但存在明显弊端:
- 使用
dbContext.Model1.Include(x => x.model2s).ToList()后在客户端检查x.models2.Any(),性能极差; - 通过AutoMapper搭配DTO或手动
Select(x => new { ... })创建匿名类型,会额外增加数据模型及大量样板代码。
希望在Model1中添加属性public bool hasOneOrMoreModel2 { get; set; },并在DbContext的OnModelCreating中配置类似如下代码:
modelBuilder.Entity<Model1>(entity => { entity.Property(x => x.hasOneOrMoreModel2).MapTo(x => x.models2.Any()); });
请问该方案是否可行?若不可行,是什么因素阻碍了这种简洁干净的实现方式?
这个方案不可行,阻碍的核心因素主要有以下几点:
EF Core属性映射的本质限制
EF Core的Property方法是用来绑定实体属性和数据库表的物理列的,而你想映射的x.models2.Any()是动态计算的逻辑,并非数据库中实际存储的字段。而且EF Core根本没有提供MapTo这类能把实体属性关联到关联集合聚合判断的API,这种写法本身就不符合EF Core的设计逻辑。实体与数据库表的绑定规则
Model1实体和数据库中的Model1表是一一对应的,实体属性默认会被EF Core当作表的列处理。如果给Model1加上hasOneOrMoreModel2属性,要么得在数据库表中新增这个布尔列(但这显然不是你想要的,因为值需要动态计算),要么得把它配置为未映射属性(用[NotMapped]或modelBuilder.Entity<Model1>().Ignore(x => x.hasOneOrMoreModel2)),但未映射属性无法让EF Core自动帮你从数据库中计算并赋值。EF Core查询翻译的局限性
就算EF Core支持这种配置,要把x.models2.Any()翻译成SQL里的EXISTS子查询,需要EF Core能在查询时自动为这个属性注入对应的SQL逻辑,但目前EF Core的设计是:实体属性要么绑定物理列,要么是基于自身属性的未映射计算属性——后者无法关联其他表的数据,自然没法实现跨表的Any()判断。
如果想要接近这种简洁的实现,可以试试这两种替代方案:
- 未映射属性+投影查询:给
Model1加上[NotMapped]标记的hasOneOrMoreModel2属性,查询时通过投影手动赋值:
这种方式不用额外建DTO,但如果var model1List = dbContext.Model1 .Select(m => new Model1 { id = m.id, // 其他所有属性逐一赋值 hasOneOrMoreModel2 = m.model2s.Any() }).ToList();Model1属性多,还是会有不少样板代码。 - 封装扩展方法:把投影逻辑封装成扩展方法,减少重复代码:
之后查询时只需调用public static IQueryable<Model1> WithHasModel2Flag(this IQueryable<Model1> query) { return query.Select(m => new Model1 { id = m.id, // 其他属性 hasOneOrMoreModel2 = m.model2s.Any() }); }dbContext.Model1.WithHasModel2Flag().ToList()即可。
内容的提问来源于stack exchange,提问作者Tom

