我正在尝试使用EF Core 3.1在多个表之间构建查询,但这根本无法正常工作。
我将尝试用一些虚拟的例子来解释。
假设我的SQL表在SQL Server数据库中都有以下定义:
虚拟实体如下:
外键关系:
所有实体都设置了其导航属性,并且我已经在数据库上下文中设置了主键和外键。
这些表中的绝大多数都有一个“已启用”位字段,因此可以禁用行而不删除它们
所以我要编写的查询类似于以下内容:
var data = await context.Town.AsNoTracking()
.Where(t => t.TownName == request.TownName)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars.Where(c => c.Enabled))
.ThenInclude(c => c.Manufacturer.Where(m => m.Enabled))
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars.Where(c => c.Enabled))
.ThenInclude(c => c.Mechanic.Where(m => m.Enabled && m.Name == request.AllowedMechanic))
.ToListAsync().ConfigureAwait(false);
总而言之,我想知道居住在“伦敦”的“约翰·史密斯”所驾驶的是哪些汽车,这些汽车由“ MechanicsAreUs”提供服务。
对我来说这似乎很漫长,这也许就是我的问题所在。
无论如何,后面的ThenIncludes中的许多.WHERE
子句将无法编译。一步一步地删除它们,直到编译完成为止。
var data = await context.Town.AsNoTracking()
.Where(t => t.TownName == request.TownName)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars)
.ThenInclude(c => c.Manufacturer)
.Include(t => t.Inhabitants.Where(i => i.Name == request.InhabitantName && i.Enabled)
.ThenInclude(i => i.InhabitantCar.Where(ic => ic.Enabled))
.ThenInclude(ic => ic.Cars)
.ThenInclude(c => c.Mechanic)
.ToListAsync().ConfigureAwait(false);
因此,按照书面规定,它将带回禁用的条目,而我没有指定机制。但是,当我运行它时,出现异常:
System.InvalidOperationException:内部使用Lambda表达式 包含无效。
我花了很多时间浏览各种Microsoft示例,但是我没有找到任何看起来如此复杂的示例。这只是少数的内部联接。在几分钟内就可以在存储过程中完成某些事情。只是我想使用实体框架来做到这一点。
答案 0 :(得分:2)
您无法过滤.Include(...)
急切的负载-全部或全部都没有。正如大卫·布朗(David Browne)在对问题的评论中指出的那样,如果要基于记录的Enabled
标志排除记录,则应使用query filters,例如:
modelBuilder.Entity<Car>()
.HasQueryFilter(c => c.Enabled);
我似乎您对Car
实体感兴趣,因此让我们重组查询以使焦点集中:
var query = context.Cars;
您想要与具有特定名称的Inhabitant
关联的汽车,该名称与特定的Town
关联,但也由特定的Mechanic
提供服务,因此让我们根据该条件进行过滤:< / p>
query = query.Where( c =>
c.InhabitantCar.Inhabitant.Name == request.InhabitantName
&& c.InhabitantCar.Inhabitant.Town.TownName == request.TownName
&& c.Mechanic == request.AllowedMechanic );
此查询现在将返回您想要的Car
实体,所以现在让我们配置急切的负载:
query = query.Include( c => c.Manufacturer )
.Include( c => c.Mechanic )
.Include( c => c.InhabitantCar )
.ThenInclude( ic => ic.Inhabitant )
.ThenInclude( i => i.Town );
给个机会。
答案 1 :(得分:0)
实体反映数据状态。您无法过滤所需的相关数据,是全部还是全部。一个城镇不仅有“被启用”的居民,它还具有居民,其中一些人被启用,而其他人则没有。您不想查看禁用或不相关的视图,这是视图的关注点,而不是数据模型。
您可以使用Select
来填充适合您的视图的模型结构。这可以使连接表变平,并仅加载要查看的已启用记录,还可以简化视图所需的字段,而不是公开有关域的所有内容。您可以利用AutoMapper来填充视图模型/ w ProjectTo
。
答案 2 :(得分:0)
一个建议是使用查询过滤器。
这背后的想法很棒-在我的数据库上下文文件中,我可以添加一组通用的过滤器,例如
builder.Entity<Town>()
.HasQueryFilter(a => a.Enabled);
builder.Entity<Car>()
.HasQueryFilter(a => a.Enabled);
builder.Entity<Manufacturer>()
.HasQueryFilter(a => a.Enabled);
这将包含在我的Service文件生成的每个查询中-开发人员无需关心。
但是,当我分析生成的SQL时,我发现我的代码到处都是多个子查询,例如
Inner Join (Select...Where ...Enabled = 1)
在删除这些集中式查询过滤器并将其添加到LINQ语句的WHERE子句中后,查询效率将大大提高。