LINQ to SQL:使用LINQ查询从每个组的顶行选择多个属性

LINQ to SQL:使用LINQ查询从每个组的顶行选择多个属性,sql,linq,linq-to-sql,linqpad,Sql,Linq,Linq To Sql,Linqpad,我有两个表,Apps和AppGooglePlayMetadatas[AGPM],一行有许多AGPM。我想选择一组应用程序,对于每一个应用程序,还将从最近的AGPM行返回每个应用程序的一些属性。我可以通过以下代码实现: Apps .Select(a => new { a.Name, DatePublished = a.AppGooglePlayMetadatas.OrderByDescending(agpm => agpm.DatePublished).Select(a

我有两个表,Apps和AppGooglePlayMetadatas[AGPM],一行有许多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,但希望一些天才能帮助我,谢谢

您可以尝试以下方法:

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语句?

您可以尝试以下方法:

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语句?

在实体框架中,要获取“项目及其子项目”,如“学校及其学生”、“客户及其订单”、“应用程序及其AGPM”,通常最好使用位于一对多关系一侧的
虚拟ICollection
。实体框架将在正确的GroupJoin中对此进行转换

如果您不想使用<代码>虚拟ICORSECTION/CODE >,例如,因为您认为您可以做得更高效,请考虑自己做GooppIng:< /P>

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(),
})
如果应用程序没有AGPM,则根本没有LatestAGPM,属性KatestAGPM值将为空

IMHO的“应用程序及其最新的AGPM”的概念是明确和直截了当的。使用方法如下:

textBoxAppName.Text = result.AppName,
testBoxAgpmLastPublished.Text =
   result.LatestAgpm.DatePublished;
textBoxAppName.Text = result.AppName,
testBoxAgpmLastPublished.Text = result.DatePublished;
如果您真的想混合应用程序和AGPM的属性,您需要添加一个额外的选择:

.Select(appWithItsLatestAgpm => new
{
    Name = appWithItsLatestAgpm.Name,
    CollectionDate = appWithItsLatestAgpm?.CollectionDate,
    DatePublished = appWithItsLatestAgpm?.DatePublished,
})
我不确定这是否会让结果的用户更清楚。DatePublished是应用程序的发布日期吗

使用方法如下:

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

这是否证明在实体框架中额外选择是合理的?

以获取“项目及其子项目”,如“学校及其学生”、“客户及其订单”、“应用程序及其AGPM”,通常最好使用位于一对多关系一侧的
虚拟ICollection
。实体框架将在正确的GroupJoin中对此进行转换

如果您不想使用<代码>虚拟ICORSECTION/CODE >,例如,因为您认为您可以做得更高效,请考虑自己做GooppIng:< /P>

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(),
})
如果应用程序没有AGPM,则根本没有LatestAGPM,属性KatestAGPM值将为空

IMHO的“应用程序及其最新的AGPM”的概念是明确和直截了当的。使用方法如下:

textBoxAppName.Text = result.AppName,
testBoxAgpmLastPublished.Text =
   result.LatestAgpm.DatePublished;
textBoxAppName.Text = result.AppName,
testBoxAgpmLastPublished.Text = result.DatePublished;
如果您真的想混合应用程序和AGPM的属性,您需要添加一个额外的选择:

.Select(appWithItsLatestAgpm => new
{
    Name = appWithItsLatestAgpm.Name,
    CollectionDate = appWithItsLatestAgpm?.CollectionDate,
    DatePublished = appWithItsLatestAgpm?.DatePublished,
})
我不确定这是否会让结果的用户更清楚。DatePublished是应用程序的发布日期吗

使用方法如下:

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

这是否证明了额外选择的合理性?

可能是您需要DatePublished列上的索引吗?哎呀,修复注释:谢谢您的回答!我喜欢这里的想法,但它似乎没有很快恢复。以下是它生成的SQL:选择[t0].[AppId],[t4].[DatePublished]作为[DatePublished2],[t4].[CollectionDate]作为[Apps]中的[CollectionDate]作为[t0]内部联接((从[AppGooglePlayMetadata]中选择[t1].[AppId]作为[t1]分组,由[t1].[AppId])作为[t2]外部应用(选择顶部(1)[t3].[DatePublished],[t3].[CollectionDate],[t3].[AppId]来自[AppGooglePlayMetadata]作为[t3],其中[t2].[AppId]=[t3].[AppId]顺序由[t3].[DatePublished]DESC作为[t4]在[t0].[AppId]=[t4].[DatePublished]描述您是否需要在DatePublished列上建立索引?哎哟,修正意见:谢谢你的回答!我喜欢这里的想法,但它似乎没有很快恢复。以下是它生成的SQL:选择[t0].[AppId],[t4].[DatePublished]作为[DatePublished2],[t4].[CollectionDate]作为[Apps]中的[CollectionDate]作为[t0]内部联接((从[AppGooglePlayMetadata]中选择[t1].[AppId]作为[t1]分组,由[t1].[AppId])作为[t2]外部应用(选择顶部(1)[t3].[DatePublished],[t3].[CollectionDate],[t3].[AppId]来自[AppGooglePlayMetadata]作为[t3],其中[t2].[AppId]=[t3].[AppId]由[t3].[DatePublished]DESC作为[t4]在[t0].[AppId]=[t4].[DatePublished]DESCHi Harold订购,谢谢您的回答!我以前从未使用过GroupJoin,非常整洁!不幸的是,这两种建议都会导致代码出现问题。第一段代码实际上为每一行运行一条SQL语句。如果我在末尾添加额外的.Select,它会生成一条语句,但不幸的是仍然有两个ORDER BY子句,与我问题中的第一个SQL语句块大致相同:/Hi Harold,谢谢你的回答!我以前从未使用过GroupJoin,非常整洁!不幸的是,这两种建议都会导致代码出现问题。第一段代码实际上为每一行运行一条SQL语句。如果我在末尾添加额外的.Select,它会生成一条语句,但不幸的是仍然有两个ORDER BY子句,与我问题中的第一个SQL语句块大致相同:/