我从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
将会很大)并且分组/过滤发生在数据库之外。
问题:
你会做什么?
答案 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不兼容。