如何在 .NET Core 3.0 Entity Framework 中执行组加入?

Posted

技术标签:

【中文标题】如何在 .NET Core 3.0 Entity Framework 中执行组加入?【英文标题】:How to perform a group join in .NET Core 3.0 Entity Framework? 【发布时间】:2020-02-25 14:05:20 【问题描述】:

随着 .NET Core 3.0 的变化,我得到了

... NavigationExpandingExpressionVisitor' 失败。这可能表明 EF Core 中的错误或限制。看 https://go.microsoft.com/fwlink/?linkid=2101433了解更多详情 信息。)---> System.InvalidOperationException:处理 LINQ 表达式 'GroupJoin, ...

这是一个非常简单的查询,因此必须有一种方法可以在 .NET CORE 3.0 中执行它:

 var queryResults1 = await patients
            .GroupJoin(
                _context.Studies,
                p => p.Id,
                s => s.Patient.Id,
                (p, studies) => new 
                
                    p.DateOfBirth,
                    p.Id,
                    p.Name,
                    p.Sex,
                   Studies =studies.Select(s1=>s1)
                
            )
            .AsNoTracking().ToListAsync();

我基本上是在寻找一个 Linq 查询(或上面的方法语法),它将研究连接到患者,如果给定患者没有研究,则将研究设置为空列表或 null。

有什么想法吗?这在 .NET Core 2.2 中有效。上面的 MSFT 链接还提到,关键的重大更改与客户端评估有关,并避免生成的查询读取整个表,然后必须加入或过滤客户端。然而,通过这个简单的查询,连接应该很容易在服务器端实现。

【问题讨论】:

【参考方案1】:

正如here 所讨论的,您正在尝试数据库不支持的查询。 EF Core 2 使用客户端评估来使您的代码工作,但 EF Core 3 拒绝,因为随着数据集的增加,客户端的便利性是以难以调试的性能问题为代价的。

您可以使用DefaultIfEmpty 离开加入患者的研究,然后使用ToLookup 手动分组。

var query =
    from p in db.Patients
    join s in db.Studies on p.Id equals s.PatientId into studies
    from s in studies.DefaultIfEmpty()
    select new  Patient = p, Study = s ;

var grouping = query.ToLookup(e => e.Patient); // Grouping done client side

上面的示例获取了完整的 Patient 和 Study 实体,但您可以选择选择列。如果您需要的 Patient 数据太大而无法为每个 Study 重复,请在连接查询中仅选择 Patient ID,在单独的非连接查询中查询其余 Patient 数据。

【讨论】:

答案有效!我想查询翻译器还有一些工作要做。像这样的简单查询应该是可翻译的。假设 FK/索引正确,随着数据集的增加,2 个表的简单组连接不应该存在性能问题。我怀疑很多人都会遇到这个问题,2 表组连接是一个非常标准且经常使用的查询。 @she72 我同意。看起来问题源于 LINQ 和 SQL 使用“组”关键字的方式不同。 EF Core 应该将 LINQ groupby 转换为左连接,这样做不会拉回比预期更多的行。我相应地发布了comment。 我有一个后续问题,我仍在尝试理解为什么需要在客户端完成此类查询的分组,这似乎是新 LINQ 框架的限制。对于上述情况,我认为它不会以意想不到的方式减慢客户端执行速度。你能澄清一下吗? 作为进一步的后续行动,主要关注的是:在您重新制定的查询中,如果我对每位患者进行 1000 项研究,则将客户端分组,我将从数据库中加载每位患者 1000 次?是否有任何替代方法可以强制在数据库中完成这项工作并返回分组结果? @shev72 数据库理解的唯一分组涉及聚合,例如对每个患者的研究计数的患者查询。数据库总是返回一个矩形数据集。分层分组必须由客户端组成。您可以将其视为客户端评估或part of the ORM。在分层分组中,父实体数据会重复,但不会重新查询。【参考方案2】:

遇到了完全相同的问题,并且为此付出了巨大的努力。事实证明,.net Core 3.0 不支持方法语法中的 Join 或 Groupjoin(还没有?)。 有趣的是,它确实适用于查询语法。

试试这个,它是带有一点方法语法的查询语法。 这很好地转换为具有良好左外连接的正确 SQL 查询,并在数据库上进行处理。 我没有你的模型,所以你需要自己检查语法......

var queryResults1 = 
    (from p in _context.patients
    from s in _context.Studies.Where(st => st.PatientId == p.Id).DefaultIfEmpty()
    select new
    
        p.DateOfBirth,
        p.Id,
        p.Name,
        p.Sex,
        Studies = studies.Select(s1 => s1)
    ).ToListAsync();

【讨论】:

顺便说一句,Join 和 GroupJoin with Method syntac DO 可以与非核心框架和 EF 一起使用。并转换为服务器端处理的正确查询 什么是studies.Select(s1 => s1) 问题中不包含模型,所以我不知道研究模型。我最好的猜测是,这是模型中的一个虚拟集合。 All 查询语法被翻译成 fluent / lambda / "method" 语法,所以如果它翻译成查询语法,equivalent fluent 查询也将翻译。

以上是关于如何在 .NET Core 3.0 Entity Framework 中执行组加入?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .NET Core 3.0 SDK 上构建多目标 .NET 5 和 .NET Core 3.1

请问在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句?

如何将 .NET Core 2.2 Web API 迁移到 .NET Core 3.0?

如何在 .NET Core 3.0 中替换 AddJwtBearer 扩展

在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句

使用 ASP.NET Core 和 Entity Framework Core 进行集成测试 - 如何在每次测试时恢复数据库中的测试数据?