简体   繁体   English

具有左联接的LINQ和sql查询并选择max子查询

[英]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; }
}

Update 更新

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: 特点:

  • Single SQL Query (check below... my be not the best SQL but there you are) 单个SQL查询(请在下面检查...我不是最好的SQL,但是您在那)
  • Single LINQ expression (long but... there you go again) 单个LINQ表达式(很长,但是...您又来了)

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开始

LINQ Join LINQ加入

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();

Explaination

This code snippet performs the following: 此代码段执行以下操作:

  1. Use Join to combine the two tables on a matching EngineId . 使用Join将两个表组合在匹配的EngineId
  2. Use Select to select only the values you're seeking from the combined data: Engine , Test.Value1 and Test.Value2 使用“ Select仅从组合数据中选择要查找的值: EngineTest.Value1Test.Value2

Sample Console App 样例控制台应用

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM