当您还在 EFCore 3.1.11 中选择其他对象列表时,左连接不会带来所有结果
Posted
技术标签:
【中文标题】当您还在 EFCore 3.1.11 中选择其他对象列表时,左连接不会带来所有结果【英文标题】:Left join not bringing all results when you also select a list of other objects in EFCore 3.1.11 【发布时间】:2021-05-02 21:57:09 【问题描述】:让我先承认这个错误不再出现在较新版本的 EFCore (5+) 中,但不幸的是,我们现在无法更新我们的项目。
我创建了一个示例代码,以便更好地展示这个错误是如何发生的。在这个示例代码中,我有一个包含三个表的 DbContext:Company、Locations 和Employees。一家公司可以有 1 名或更多员工,以及 0 个或更多地点。我想创建一个返回如下公司和位置的查询:
Company | Location | Employees
-----------------------------------------------
Company 1 | Location 1 | Employee 1, Employee 2
Company 1 | Location 2 | Employee 1, Employee 2
Company 2 | | Employee 3, Employee 4
同一公司的不同位置应该在不同的行中,如果公司没有位置,它应该仍然显示。
我使用 Linq 提出了以下查询,它执行我上面在 EFCore 5+ 中描述的功能,但在 3.1.11 中没有
(from c in ctx.Companies
join _l in ctx.Locations on c.Id equals _l.CompanyId into lGroup
from l in lGroup.DefaultIfEmpty()
where
c.Id == new Guid("FFF461F3-9E38-4DA0-8B31-AC4D0C90A4D0") // Id of Company 1 in the code block above
select new
CompanyName = c.Name,
LocationName = l == null ? string.Empty : l.Name,
Employees = c.Employees.Select(e => e.Name)
).AsNoTracking().ToList();
在 EFCore 5+ 中,这会在结果列表中返回两个项目,这与我想要的完全一样。但在 3.1.11 中,它只返回一行,即第一个位置。缺少第二个位置的第二行。
问题是,如果您将查询更改为不使用 DefaultIfEmpty()(基本上将其从左连接更改为内连接),它会完全按照预期工作,但在这种情况下,如果您尝试查询一家没有位置,它不会显示(因为它是与位置的内部连接)。
有谁知道我是否可以通过有效的方式实现这一目标?
提前谢谢你。
编辑:这是生成的查询:
SELECT [c].[Name], CASE
WHEN [l].[Id] IS NULL THEN N''
ELSE [l].[Name]
END, [c].[Id], [e].[Name], [e].[Id]
FROM [Companies] AS [c]
LEFT JOIN [Locations] AS [l] ON [c].[Id] = [l].[CompanyId]
LEFT JOIN [Employees] AS [e] ON [c].[Id] = [e].[CompanyId]
WHERE [c].[Id] = 'f2c44d70-d713-42dc-b92a-e93b175fc351'
ORDER BY [c].[Id], [e].[Id]
【问题讨论】:
我看到你有很多关系。你有第三张桌子吗? net core 3.1 ef 需要第三个表进行多对多。 在这种情况下,没有多对多关系。公司有很多地方,每个地方有一个公司,有很多员工,每个地方有一个公司。员工和地点之间没有关系。这只是我想出的一个示例,但在原来的情况下,这两个表之间没有任何关系。 能否添加生成的SQL? 将生成的查询添加到帖子@GuruStron。 【参考方案1】:您可以使用Employees
添加连接并在内存中处理结果:
(from c in ctx.Companies
join _l in ctx.Locations on c.Id equals _l.CompanyId into lGroup
from l in lGroup.DefaultIfEmpty()
join _e in ctx.Employees on c.Id equals _e.CompanyId into eGroup
from e in eGroup.DefaultIfEmpty()
where
c.Id == new Guid("FFF461F3-9E38-4DA0-8B31-AC4D0C90A4D0")
select new
CId = c.Id,
CompanyName = c.Name,
LId = l.Id,
LocationName = l == null ? string.Empty : l.Name,
EmployeeName = e.Name
)
.AsNoTracking()
.AsEnumerable()
.GroupBy(r => new CId, LId)
.Select(g = > new
CompanyName = g.First().CompanyName ,
LocationName = g.First().LocationName ,
Employees = g.Select(e => e.EmployeeName)
)
.ToList();
【讨论】:
【参考方案2】:试试这个:
var q= ctx.Locations
.Include(i=>i.Company)
.Select (i=> new
CompanyName = i.Company.Name,
LocationName =i.Name,
Employees = i.Company.Employees.Select(e => e.Name)
).AsNoTracking().ToList()
.Union(
ctx.Companies
.Where(i=>i.Locations==null)
.Select (i=> new
CompanyName = i..Name,
LocationName =stringEmpty,
Employees = i.Employees.Select(e => e.Name)
).AsNoTracking().ToList());
【讨论】:
【参考方案3】:EFC 3.1 的解决方法是将查询拆分为两部分:服务器端(LINQ to Entities)查询仅选择所需的数据,但具有相关子集合的“正常”集合类型投影,以及客户端(LINQ to对象)查询进行所需的展平/乘法。
类似的东西
(from c in ctx.Companies
select new
CompanyName = c.Name,
Locations = c.Locations.Select(e => new e.Name ), // <-- the data needed
Employees = c.Employees.Select(e => e.Name),
)
.AsEnumerable() // <-- switch to client side context
.SelectMany(c => c.Locations.DefaultIfEmpty(), (c, l) => new
c.CompanyName,
LocationName = l == null ? string.Empty : l.Name,
c.Employees
).ToList();
如果您没有 c.Locations
集合导航属性(应该如此),请将其替换为等效的
ctx.Locations.Where(e => e.CompantyId == c.Id)
这是 EFC 对 L2E 查询中的集合导航属性自动执行的操作。
【讨论】:
以上是关于当您还在 EFCore 3.1.11 中选择其他对象列表时,左连接不会带来所有结果的主要内容,如果未能解决你的问题,请参考以下文章
当您向上或向下滚动时,选择一个 UITableViewCell 会选择同一位置的其他单元格