[英]LINQ and sql query with left join and select max subquery
I have a database table named Engine (EngineId, Make, Model, SerialNumber, etc.) and a table named Test (TestId, EngineId, TestDate, Value1, Value2, etc.). 我有一个名为Engine的数据库表(EngineId,Make,Model,SerialNumber等)和一个名为Test的表(TestId,EngineId,TestDate,Value1,Value2等)。 I want to select each engine record plus the latest test results (if any, might be none). 我想选择每个引擎记录以及最新的测试结果(如果有的话,可能没有)。
I am using ASP.NET Core 1.1 and Entity Framework and I have Engine and Test model entities. 我正在使用ASP.NET Core 1.1和Entity Framework,并且具有Engine和Test模型实体。
I am trying to convert the following SQL to a LINQ query expression: 我正在尝试将以下SQL转换为LINQ查询表达式:
SELECT e.*, t.Value1, t.Value2
FROM Engine e
LEFT JOIN Test t on e.EngineID = t.EngineID
AND t.TestDate= (SELECT MAX(TestDate) FROM Test t2)
I can get started with the left join but I don't see how to do the subquery: 我可以从左联接开始,但看不到如何执行子查询:
var engines = from e in _context.Engine
join t in _context.Test on e.EngineId equals t.EngineId into td
from c in td.DefaultIfEmpty()
select new EngineVM
{
EngineId = e.EngineId,
Unit = e.Unit,
Make = e.Make,
Model = e.Model,
SerialNumber = e.SerialNumber
};
Here is my view model: 这是我的视图模型:
public class EngineVM
{
public long EngineId { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public string SerialNumber { get; set; }
public decimal? LastTestValue1 { get; set; }
public decimal? LastTestValue2 { get; set; }
}
Here was the solution: 解决方法如下:
var lastTestQuery = _context.Test
.FromSql(@"
SELECT t.* FROM Test t
WHERE t.TestDate = (SELECT MAX(t2.TestDate) FROM Test t2
WHERE t2.EngineId = t.EngineId)"
);
// Query to get engine records to display along with last test data
var engines = (from e in _context.Engine
orderby e.SerialNumber
join t in lastTestQuery on e.EngineId equals t.EngineId into lastEngineTest
from t in lastEngineTest.DefaultIfEmpty() // forces left join
let lastTest = (from x in lastEngineTest select x).FirstOrDefault()
select new EngineVM
{
EngineId = e.EngineId,
Unit = e.Unit,
Make = e.Make,
Model = e.Model,
SerialNumber = e.SerialNumber,
Status = CalculateEngineStatus(e.EngineId,
e.PermittedNox,
lastTest.Nox)
}).OrderByDescending(x => x.Status);
I managed to generate a single query by using the FromSql
mechanism in EF Core Looks like this is the way to go for subqueries :( if you want to run as much as possible via the SQL Engine 我设法通过使用EF Core中的FromSql
机制生成了一个查询看起来这是进行子查询的方式:(如果要通过SQL引擎尽可能多地运行
var lastTestQuery = _context.Test
.FromSql(@"
SELECT t.* FROM Test t
WHERE t.TestDate = (SELECT MAX(t2.TestDate) FROM Test t2 WHERE t2.EngineId = t.EngineId)"
);
var enginesWithLastTest = _context.Engine
.GroupJoin(lastTestQuery, engine => engine.EngineId, test => test.EngineId, (engine, tests) => new {
Engine = engine,
LastTests = tests.DefaultIfEmpty()
})
.ToList();
foreach (var item in enginesWithLastTest)
{
var eng = item.Engine;
var test = item.LastTests.FirstOrDefault();
Console.WriteLine($"{eng.EngineId} {eng.Make} {eng.Model} {eng.SerialNumber} {test?.TestValue}");
}
Looking into SQL Server Profiler, this is the generated query: 查看SQL Server Profiler,这是生成的查询:
SELECT [engine].[EngineId],
[engine].[AllowedAmount],
[engine].[Make],
[engine].[Model],
[engine].[SerialNumber],
[test].[TestId],
[test].[EngineId],
[test].[TestDate],
[test].[TestValue]
FROM [Engine] AS [engine]
LEFT JOIN
(SELECT t.*
FROM Test t
WHERE t.TestDate =
(SELECT MAX(t2.TestDate)
FROM Test t2
WHERE t2.EngineId = t.EngineId) ) AS [test]
ON [engine].[EngineId] = [test].[EngineId]
ORDER BY [engine].[EngineId]
A pure LINQ query expressions approach (no lambdas), and without FromSQL 纯LINQ查询表达式方法(无lambda),并且没有FromSQL
Featuring: 特点:
Here's the code: 这是代码:
var query =
from engine in _context.Engine
join test in (
from t in _context.Test
where t.TestDate == (from t2 in _context.Test where t2.EngineId == t.EngineId select t2.TestDate).Max()
select t
) on engine.EngineId equals test.EngineId into td
from latestTest in td.DefaultIfEmpty()
orderby engine.SerialNumber ascending
select new EngineVM {
EngineId = engine.EngineId,
Make = engine.Make,
Model = engine.Model,
SerialNumber = engine.SerialNumber,
LastTestValue1 = latestTest != null ? (decimal?) latestTest.TestValue : null
};
var enginesWithLastTest = query.ToList();
foreach (var eng in enginesWithLastTest)
{
Console.WriteLine($"{eng.EngineId} {eng.Make} {eng.Model} {eng.SerialNumber} {eng.LastTestValue1}");
}
And the generated SQL: 并生成SQL:
SELECT [engine].[EngineId], [engine].[AllowedAmount], [engine].[Make], [engine].[Model], [engine].[SerialNumber], [t1].[TestId], [t1].[EngineId], [t1].[TestDate], [t1].[TestValue]
FROM [Engine] AS [engine]
LEFT JOIN (
SELECT [t0].[TestId], [t0].[EngineId], [t0].[TestDate], [t0].[TestValue]
FROM [Test] AS [t0]
WHERE [t0].[TestDate] = (
SELECT MAX([t20].[TestDate])
FROM [Test] AS [t20]
WHERE [t20].[EngineId] = [t0].[EngineId]
)
) AS [t1] ON [engine].[EngineId] = [t1].[EngineId]
ORDER BY [engine].[SerialNumber], [engine].[EngineId]
Note2: Always check the SQL execution plan when you compose a LINQ monster. 注意2:编写LINQ Monster时,请始终检查SQL执行计划。 You might be better off running two queries and building a dictionary 您可能最好运行两个查询并构建一个字典
Note: I'm eager to hear feedback and improvements on this answer. 注意:我很想听听这个答案的反馈和改进。 I'm just starting with EF Core myself 我本人只是从EF Core开始
var listOfEnginesWithTestData = engineList.Join(testList,
engine => engine.EngineID,
test => test.EngineID,
(engine, test) => new
{
Engine = engine,
Test = test
}).Select(x => new { x.Engine, x.Test.Value1, x.Test.Value2 }).ToList();
This code snippet performs the following: 此代码段执行以下操作:
Join
to combine the two tables on a matching EngineId
. 使用Join
将两个表组合在匹配的EngineId
。 Select
to select only the values you're seeking from the combined data: Engine
, Test.Value1
and Test.Value2
使用“ Select
仅从组合数据中选择要查找的值: Engine
, Test.Value1
和Test.Value2
Here is a sample Console App that uses List
s. 这是一个使用List
的示例控制台应用程序。 You are querying a database, and your code will look slightly different. 您正在查询数据库,并且您的代码看起来会稍有不同。
using System;
using System.Collections.Generic;
using System.Linq;
namespace SQLLeftJoin
{
class MainClass
{
public static void Main(string[] args)
{
var engineList = new List<Engine>
{
new Engine{ EngineID = "1"},
new Engine{ EngineID = "2"},
new Engine{ EngineID = "3"}
};
var testList = new List<Test>
{
new Test{TestID = "1", EngineID = "1", TestDate = DateTime.Now},
new Test{TestID = "2", EngineID = "1", TestDate = DateTime.Now},
new Test{TestID = "3", EngineID = "2", TestDate = DateTime.Now},
new Test{TestID = "4", EngineID = "2", TestDate = DateTime.Now},
new Test{TestID = "5", EngineID = "3", TestDate = DateTime.Now},
new Test{TestID = "6", EngineID = "3", TestDate = DateTime.Now},
new Test{TestID = "7", EngineID = "3", TestDate = DateTime.Now},
};
var listOfEnginesWithTestData = engineList.Join(testList,
engine => engine.EngineID,
test => test.EngineID,
(engine, test) => new
{
Engine = engine,
Test = test
}).Select(x => new { x.Engine, x.Test.Value1, x.Test.Value2 }).ToList();
}
}
class Engine
{
public string EngineID { get; set; }
}
class Test
{
public string TestID { get; set; }
public string EngineID { get; set; }
public DateTime TestDate { get; set; }
public object Value1 { get; set; }
public object Value2 { get; set; }
}
}
try Code: 尝试代码:
DateTime maxTastdate=db.Test.Max(c=>c.TestDate);
var result=( from e in db.Engine
join t in db.Test.where(c=>c.TestDate=maxTastdate)
on e.EngineID equlas t.EngineID into jj from kk in jj.DefaultIfEmpty()
select new {
EngineId=e.EngineId,
Make=e.Make,
Model=e.Model,
SerialNumber=e.SerialNumber
Value1=kk.Value1,
Value2=kk.Value2
}).Tolist()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.