【问题标题】:LINQ-to-SQL: Select mulitple properties from top row of each GROUP using LINQ queryLINQ-to-SQL:使用 LINQ 查询从每个 GROUP 的顶行中选择多个属性
【发布时间】:2020-11-05 18:47:58
【问题描述】:

我有两张表,Apps 和 AppGooglePlayMetadatas [AGPM],一个 Apps 行有很多 AGPM。我想选择一组应用程序,并为每个应用程序返回最近 AGPM 行中的一些属性。我可以用这段代码做到这一点:

Apps
.Select(a => new
{
    a.Name,
    DatePublished = a.AppGooglePlayMetadatas.OrderByDescending(agpm => agpm.DatePublished).Select(agpm => agpm.DatePublished).First(),
    CollectionDate = a.AppGooglePlayMetadatas.OrderByDescending(agpm => agpm.DatePublished).Select(agpm => agpm.CollectionDate).First(),
})

但这很丑陋,不能很好地扩展,并且会导致(可能)低效的 SQL 并执行两次排序:

SELECT [t0].[Name], (
    SELECT TOP (1) [t1].[DatePublished]
    FROM [AppGooglePlayMetadata] AS [t1]
    WHERE [t1].[AppId] = [t0].[AppId]
    ORDER BY [t1].[DatePublished] DESC
    ) AS [DatePublished], (
    SELECT TOP (1) [t2].[CollectionDate]
    FROM [AppGooglePlayMetadata] AS [t2]
    WHERE [t2].[AppId] = [t0].[AppId]
    ORDER BY [t2].[DatePublished] DESC
    ) AS [CollectionDate]
FROM [Apps] AS [t0]

这样可以写得更简洁一些,但仍然会产生两个 ORDER BY 语句:

Apps
.Select(a => new
{
    App = a,
    Latest = a.AppGooglePlayMetadatas.OrderByDescending(agpm => agpm.DatePublished).First()
})
.Select(a => new {
    a.App.Name,
    a.Latest.CollectionDate,
    a.Latest.DatePublished,
})

我正在寻找一种方法来查询连接表中特定行的多个属性,从而生成高效的 SQL 查询。如果我打算用 SQL 写这个,我相信我可以像这样最有效地做到这一点:

SELECT TOP 100 a.Name, agpm.CollectionDate, agpm.DatePublished
FROM Apps AS a
CROSS APPLY (
    SELECT TOP 1 DatePublished, CollectionDate
    FROM AppGooglePlayMetadata AS agpm
    WHERE a.AppId = agpm.AppId
    ORDER BY DatePublished DESC
) AS agpm

不确定在这种情况下是否有可能让 LINQ 生成如此高效的 SQL,但希望有大神能帮助我,谢谢!

【问题讨论】:

    标签: sql linq linq-to-sql linqpad


    【解决方案1】:

    您可以尝试以下方法:

    var result = 
        Apps.Join(
            AppGooglePlayMetadata
                .GroupBy(x => x.AppId)
                .Select(grp => grp
                    .OrderByDescending(x => x.DatePublished)
                    .First()),
            x => x.AppId,
            x => x.AppId,
            (app, meta) => new { app.AppId, meta.DatePublished, meta.CollectionDate });
    

    这会让你得到一个高效的 SQL 语句吗?

    【讨论】:

    • 您是否需要在 DatePublished 列上建立索引?
    • 糟糕,正在修复评论:感谢您的回答!我喜欢这里的想法,但它似乎并没有很快恢复。这是它生成的 SQL:
    • SELECT [t0].[AppId], [t4].[DatePublished] AS [DatePublished2], [t4].[CollectionDate] AS [CollectionDate] FROM [Apps] AS [t0] INNER JOIN (( SELECT [t1].[AppId] FROM [AppGooglePlayMetadata] AS [t1] GROUP BY [t1].[AppId] ) AS [t2] 外部应用 (SELECT TOP (1) [t3].[DatePublished], [t3 ].[CollectionDate], [t3].[AppId] FROM [AppGooglePlayMetadata] AS [t3] WHERE [t2].[AppId] = [t3].[AppId] ORDER BY [t3].[DatePublished] DESC ) AS [ t4]) ON [t0].[AppId] = [t4].[AppId] ORDER BY [t4].[DatePublished] DESC
    【解决方案2】:

    在实体框架中获取“项目及其子项目”,例如“学校及其学生”、“客户及其订单”、“应用程序及其 AGPM”,通常使用@987654321 是个好主意@ 那是一对多关系的一方面。 Entity Framework 将把它翻译成正确的 GroupJoin。

    如果您不想使用virtual ICollection<...>,例如因为您认为可以更高效地使用,请考虑自己进行 GroupJoin:

    var result = Apps.GroupJoin(         // GroupJoin Apps
    AppGooglePlayMetadatas,              // with AGPMs
    
    app => app.Id,                       // from every App take the primary key
    apgm => apgm.AppId,                  // from every Agpm take the foreign key to App
    
    (app, AgpmsOfThisApp) => new         // for every App with its zero or more AGPMs
    {                                    // make one new
        AppName = app.Name,
        LatestAgpm = AgpmsOfThisApp.OrderByDescending(agpm => agpm.DatePublished)
            .Select(agpm => new
            {
                CollectionDate = agpm.CollectionDate,
                DatePublished = agpm.DatePublished,
            }}
            .FirstOrDefault(),
    })
    

    如果 App 没有 AGPM,则根本没有 LatestAGPM,属性 KatestAGPM 值为 null

    恕我直言,“带有最新 AGPM 的应用程序”的概念清晰明了。用法将是:

    textBoxAppName.Text = result.AppName,
    testBoxAgpmLastPublished.Text =
       result.LatestAgpm.DatePublished;
    

    如果真想混用App和AGPM的属性,需要多加一个select:

    .Select(appWithItsLatestAgpm => new
    {
        Name = appWithItsLatestAgpm.Name,
        CollectionDate = appWithItsLatestAgpm?.CollectionDate,
        DatePublished = appWithItsLatestAgpm?.DatePublished,
    })
    

    我不确定这是否会让结果用户更清楚地看到结果。 DatePublished 是 App 的发布日期吗?

    用法如下:

    textBoxAppName.Text = result.AppName,
    testBoxAgpmLastPublished.Text = result.DatePublished;
    

    这是否证明额外的 Select 是合理的?

    【讨论】:

    • 嗨哈罗德,感谢您的回答!我以前从未使用过 GroupJoin,非常整洁!不幸的是,这两个建议都会导致有问题的代码。第一个代码块实际上为每一行运行一条 SQL 语句。如果我在最后添加额外的 .Select ,它会生成一个语句,但不幸的是仍然有两个 ORDER BY 子句,或多或少与我的问题中的第一个 SQL 块相同:/
    猜你喜欢
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    相关资源
    最近更新 更多