使用左连接的LINQ和sql查询并选择最大子查询

时间:2017-08-07 00:07:39

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

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

我正在使用ASP.NET Core 1.1和Entity Framework,我有引擎和测试模型实体。

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

3 个答案:

答案 0 :(得分: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查询表达式方法(没有lambdas),没有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

答案 1 :(得分:0)

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

此代码段执行以下操作:

  1. 使用Join将两个表合并到匹配的EngineId上。
  2. 使用Select仅从组合数据中选择您要搜索的值:EngineTest.Value1Test.Value2
  3. 示例控制台应用程序

    以下是使用List的示例控制台应用。您正在查询数据库,您的代码看起来会略有不同。

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

答案 2 :(得分:0)

尝试代码:

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