【问题标题】:LINQ and sql query with left join and select max subquery带有左连接的 LINQ 和 sql 查询并选择最大子查询
【发布时间】:2017-08-07 00:07:38
【问题描述】:

我有一个名为 Engine(EngineId、Make、Model、SerialNumber 等)的数据库表和一个名为 Test(TestId、EngineId、TestDate、Value1、Value2 等)的表。我想选择每个引擎记录以及最新的测试结果(如果有,可能没有)。

我正在使用 ASP.NET Core 1.1 和 Entity Framework,并且我有 Engine 和 Test 模型实体。

我正在尝试将以下 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)

我可以开始使用左连接,但我不知道如何执行子查询:

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
              };

这是我的视图模型:

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; }
}

更新

解决方案如下:

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

【问题讨论】:

  • 我查看了样本,但它们并没有真正帮助我
  • 你是否有从引擎到测试的导航属性,即engine.Tests。实际上...如果您至少为 EngineTest 实体显示我们的部分对象模型会更好

标签: c# entity-framework linq linq-to-entities


【解决方案1】:

我设法通过使用 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}");
}

查看 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]

纯 LINQ 查询表达式方法(无 lambda),无 FromSQL

特色:

  • 单个 SQL 查询(检查下面...我不是最好的 SQL,但你是)
  • 单个 LINQ 表达式(很长,但是……又来了)

代码如下:

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}");
}

以及生成的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]

注意 2: 编写 LINQ 怪物时,请务必检查 SQL 执行计划。运行两个查询并构建字典可能会更好

注意:我渴望听到对此答案的反馈和改进。我自己刚刚开始使用 EF Core

【讨论】:

  • 谢谢。我能够使用.FromSql 和另一个示例来获得解决方案。我认为你是对的,这是在 EF Core 中进行子查询的方式。
  • 现在你可以提供反馈了 :)
【解决方案2】:

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

说明

这段代码 sn-p 执行以下操作:

  1. 使用Join 在匹配的EngineId 上组合两个表。
  2. 使用 Select 从组合数据中仅选择您正在寻找的值:EngineTest.Value1Test.Value2

示例控制台应用

这是一个使用Lists 的示例控制台应用程序。您正在查询数据库,您的代码看起来会略有不同。

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; }
    }
}

【讨论】:

  • 这是一个 INNER JOIN 我认为 OP 正在寻找所有引擎,即使它们没有关联 Test。您可以尝试GroupJoin,但 EF Core 在翻译它时遇到问题。请参阅EF Core Roadmap 2.0 预览版 1 的大部分内容已经完成,主要例外是 GroupBy 翻译,您将排序需要获取最新的 @987654334 @对于给定的Engine
  • 在 EF Core 中使用GroupJoin,您可以运行单个查询,其中包括所有引擎的左外连接和所有测试(按最大日期选择测试的部分在内存中完成)
【解决方案3】:

尝试代码:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-17
    • 1970-01-01
    • 2014-12-01
    • 1970-01-01
    • 2016-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多