首页 > 解决方案 > 具有多个连接和包含的 LINQ 查询不起作用

问题描述

我有一个运行正常的现有 LINQ 查询:

public partial class UserFacilityAccess
{
    public int UserFacilityAccessId {get; set;}
    public int FacilityId {get; set;}
    public Guid AppUserId {get; set;}
}

appUsers = (from a in _context.AppUsers
           join uf in _context.UserFacilityAccesses on new { p1 = a.AppUserId, p2 = FacilityID } equals new { p1 = uf.AppUserId, p2 = uf.FacilityId }
           into jn
           from c in jn.DefaultIfEmpty()
           where c.UserFacilityAccessId == null 
           select a)
           .OrderBy(o => o.AppUserDisplayName).ToList();

现在我有一个额外的要求,要检查一个 FacilityIds 列表。我所做的如下:

List<int> facilities = (from f in _context.Facilities
                       join c in _context.Clients on f.ClientID equals c.ID
                       join g in _context.Groups on c.GroupID equals g.ID
                       where ((GroupID == 0 || g.ID == GroupID) && (ClientID == 0 || c.ID == ClientID))
                       select f.ID).ToList();

该列表成功填充,包含所有有效的设施 ID。我尝试对原始查询进行以下修改,现在无论设置如何,它都不会返回任何行,所以很明显我做错了。我可以将创建的设施加入到 UserFacilityAccesses 表中,但我不知道该怎么做。我想将 UserFacilityAccesses 限制为我创建的设施列表中具有 FacilityId 的记录。这是我尝试的:

appUsers = (from a in _context.AppUsers
           join uf in _context.UserFacilityAccesses on new { p1 = a.AppUserId, p2 = FacilityID } equals new { p1 = uf.AppUserId, p2 = uf.FacilityId }
           into jn
           from c in jn.DefaultIfEmpty()
           where c.UserFacilityAccessId == null && facilities.Contains(c.FacilityId)
           select a)
           .OrderBy(o => o.AppUserDisplayName).ToList();

这运行没有错误,但不返回任何行,因此显然 Contains 子句无法正常工作。我很困惑如何让它发挥作用。作为一项实验,我尝试更改 contains 子句,改为硬编码一个我知道将由 UserFacilityAccesses 表返回的 ID,并且只添加了 c.FacilityId == 5 并且也没有返回任何内容。提前致谢。

标签: c#linq

解决方案


您的两个查询都使用反连接(左外连接不包括与连接条件匹配的记录),但前者在连接前正确应用右侧过滤器,而后者在连接尝试应用它,其中为时已晚,因为它已经被过滤以包含到记录中。

这就是我的意思。在第一次查询中

join uf in _context.UserFacilityAccesses
on new { p1 = a.AppUserId, p2 = FacilityID }
equals new { p1 = uf.AppUserId, p2 = uf.FacilityId }
into jn from c in jn.DefaultIfEmpty()

FacilityId条件隐藏在连接条件(p2键的)内。以上是等价于

(1)

join uf in _context.UserFacilityAccesses
    .Where(x => FacilityId == x.FacilityID) // <-- pre filter
on a.AppUserId equals uf.AppUserId // <-- inner join
into jn from c in jn.DefaultIfEmpty() // <-- convert the inner join to left outer join

甚至更好理解的子查询语法

(2)

from c in _context.UserFacilityAccesses
    .Where(x => FacilityId == x.FacilityID
        && a.AppUserId == x.AppUserId)
    .DefaultIfEmpty()
where c == null

甚至更好(不要与反连接和什么是空的以及在哪里应用过滤器混淆),“没有匹配记录”的正常条件

(3)

where !_context.UserFacilityAccesses
    .Any(x => FacilityId == x.FacilityID
        && a.AppUserId == x.AppUserId)

最好是使用导航属性(_context变量表示您使用的是 EF6 或 EF Core)

(4)

where !a.UserFacilityAccesses
    .Any(x => FacilityId == x.FacilityID)

所有这些(包括原件)都是等效的,仅在可读性上有所不同。但是最后 4 种语法允许您轻松更改FacilityId原始语法无法实现的条件,并导致您出现不正确的位置/错误。

话虽如此,有问题的问题的解决方案是选择这 4 个查询中的任何一个并简单地替换

FacilityId == x.FacilityID

facilities.Contains(x.FacilityID)

推荐阅读