从Entity Framework Core中的另一个表有效加入最新记录

时间:2017-07-31 16:06:07

标签: asp.net entity-framework linq asp.net-core entity-framework-core

我从PHP w / MySQL转到ASP .NET Core。

问题:

为了说明,假设以下两个表: T: {ID, Description, FK}States: {ID, ID_T, Time, State}。它们之间存在1:n关系(ID_T引用T.ID)。

我需要来自T的所有记录,其中FK的某些特定值(简称1)以及States中的相关最新记录(如果有)。

就SQL而言,它可以写成:

SELECT T.ID, T.Description, COALESCE(s.State, 0) AS 'State' FROM T
LEFT JOIN (
    SELECT ID_T, MAX(Time) AS 'Time'
    FROM States
    GROUP BY ID_T
) AS sub ON T.ID = sub.ID_T
LEFT JOIN States AS s ON T.ID = s.ID_T AND sub.Time = s.Time
WHERE FK = 1

我正在努力在LINQ(或流畅的API)中编写有效的等效查询。到目前为止,我得到的最佳解决方案是:

from t in _context.T
where t.FK == 1
join s in _context.States on t.ID equals o.ID_T into _s
from s in _s.DefaultIfEmpty()
let x = new
{
    id = t.ID,
    time = s == null ? null : (DateTime?)s.Time,
    state = s == null ? false : s.State
}
group x by x.id into x
select x.OrderByDescending(g => g.time).First();

当我在输出窗口中检查生成的SQL查询时,它就像:

SELECT [t].[ID], [t].[Description], [t].[FK], [s].[ID], [s].[ID_T], [s].[Time], [s].[State]
FROM [T] AS [t]
LEFT JOIN [States] AS [s] ON [T].[ID] = [s].[ID_T]
WHERE [t].[FK] = 1
ORDER BY [t].[ID]

它不仅选择了比我需要的更多的列(在真实的方案中,它们有更多的列)。 查询中没有分组所以我想它会从数据库中选择所有内容(States将会很大)并且分组/过滤发生在数据库之外。

问题:

你会做什么?

  • LINQ / Fluent API中是否有高效的查询?
  • 如果没有,可以使用哪些变通方法?
    • 原始SQL破坏了从特定数据库技术中抽象的概念,它在当前的实体框架核心中使用非常笨重(但可能是最佳解决方案)
    • 对我而言,这看起来是使用数据库视图的一个很好的例子 - 再次,实体框架核心并非真正支持(但可能是最佳解决方案)

2 个答案:

答案 0 :(得分:3)

如果您尝试对LINQ进行更直接的翻译,会发生什么?

var latestState = from s in _context.States
                  group s by s.ID_T into sg
                  select new { ID_T = sg.Key, Time = sg.Time.Max() };

var ans = from t in _context.T
          where t.FK == 1
          join sub in latestState on t.ID equals sub.ID_T into subj
          from sub in subj.DefaultIfEmpty()
          join s in _context.States on new { t.ID, sub.Time } equals new { s.ID, s.Time } into sj
          from s in sj.DefaultIfEmpty()
          select new { t.ID, t.Description, State = (s == null ? 0 : s.State) };

显然,??运算符会转换为COALESCE并可能正确处理空表,因此您可以将select替换为:

          select new { t.ID, t.Description, State = s.State ?? 0 };

答案 1 :(得分:2)

行。阅读this article(现在差不多一岁),Smit对原始问题及其他来源的评论,似乎 EF Core还没有真正准备好生产。它无法将分组转换为SQL,因此它在客户端执行,这可能(并且在我的情况下)可能是一个严重的问题。它对应于观察到的行为(生成的SQL查询不进行分组并选择所有组中的所有内容)。在Linqpad中尝试LINQ查询它总是转换为单个SQL查询。

我在this article之后降级为EF6 。它需要对我的模型代码和一些查询进行一些更改。在原始LINQ查询中将.First()更改为.FirstOrDefault()后,它可以正常工作并转换为仅选择所需列的单个SQL查询。但是,生成的查询比需要的要复杂得多。

使用来自NetMage的答案(在小修复之后),它会导致SQL查询几乎与我自己的原始SQL查询相同(只有比COALESCE更复杂的构造)。

var latestState = from s in _context.States
                  group s by s.ID_T into sg
                  select new { ID = sg.Key, Time = sg.Time.Max() };

var ans = from t in _context.T
          where t.FK == 1
          join sub in latestState on t.ID equals sub.ID into subj
          from sub in subj.DefaultIfEmpty()
          join s in _context.States
              on new { ID_T = t.ID, sub.Time } equals new { s.ID_T, s.Time }
              into sj
          from s in sj.DefaultIfEmpty()
          select new { t.ID, t.Description, State = (s == null ? false : s.State) };

在LINQ中,它并不像我原来的SQL查询那样优雅,但在语义上它是相同的,它在数据库方面或多或少都是相同的。

在EF6中,使用任意原始SQL查询和AFAIK以及数据库视图也更方便。

这种方法的最大缺点是必须针对完整的.NET框架,EF6与.NET Core不兼容。