Linq/Entity Frameworkselect一组最新记录

Linq / Entity Framework select latest recorded for a group

我正在尝试弄清楚如何编写 LINQ/Entity Framework 查询以 return table.

中每个交易品种的最新可用数据

我的数据库 table 如下所示:

ID    symbol    price_date    price
------------------------------------
1     AAPL      2022-02-28    174.50
2     MSFT      2022-02-28    307.20
3     AAPL      2021-03-01    172.23
4     MSFT      2021-03-01    304.15

虽然不是每个交易品种每天都有记录。 ID 密钥是连续的,可以安全使用,因为给定符号的最高 ID 将包含最新数据。

如果我正在编写一个 SQL 查询,下面的内容将 return 我正在寻找的内容:

select prices.*
from prices 
where id in (select max(id) from prices group by symbol)

在 Linq 中,我无法将其纳入单个查询。 到目前为止,我将其分为两个查询:

var maxIds = from pp in ctx.Prices
             group pp by pp.Symbol
                 into maxIdBySymbol
             select maxIdBySymbol.Max(pp => pp.Id);

var latestPrices = ctx.Prices.Where(it => maxIds.Contains(it.Id)).ToList();

有没有办法在 LINQ 中将其设为单个查询?

谢谢

您可以将 Where 结合使用 Any:

ctx.Prices.Where(prices1 => !ctx.Prices.Any(prices2 => (prices2.Id > prices1.Id) && (prices1.symbol.Equals(prices2.symbol))))

补充:建议的解决方案可行,但效率较低
更多信息见文末补充。

原解

所以您首先要创建记录组,其中每组仅包含一个特定交易品种的记录。因此,您将有一组包含代码 AAPL 的记录,一组包含代码 MSFT 的记录,等等。

I am trying ... query ... the latest data available for each symbol in a table.

所以,一旦你有了组,你就 select 成为组中的一个元素。根据您的要求,您 select 最新的元素,即 PriceDate 具有最高值的元素。如您所说,您还可以取 属性 ID 中具有最高值的元素。就我个人而言,我不会这样做,因为如果在很远的将来你的 ID 不再处于升序日期,例如因为你添加了在输入错误后编辑 PriceDate 的功能。

为此,我会使用 overload of Queryable.GroupBy that has a parameter resultSelector。使用 resultSelector select 每个组中您想要的一个元素。

var newestRecordPerSymbol = dbContext.PriceRecords

// make groups of priceRecords with same value for property Symbol
.GroupBy( priceRecord => priceRecord.Symbol,

// parameter resultSelector: for every symbol and all priceRecords
// that have this symbol, take the newest one
// = order by descending PriceDate and take the first one
(symbol, priceRecordsWithThisSymbol) => priceRecordsWithThisSymbol
    .OrderByDescending(priceRecord => priceRecord.PriceDate)
    .FirstOrDefault();

换句话说:从 PriceRecords 的 table 中,创建具有相同值的 PriceRecords 组 属性 Symbol。从 Symbol 和具有此 symbol 的 PriceRecords 的每个组合中,按 属性 PriceDate 的降序排列所有 PriceRecords,并仅保留第一个。

每个组至少有一个元素,因此您可以使用 First 以及 FirstOrDefault。 EntityFramework 或 DBMS 的某些版本在使用 First 时会出现问题。如果遇到此问题,请使用 FirstOrDefault。

如果你还想拿ID最高的那个:

  .OrderByDescending(priceRecord => priceRecord.ID)
  .FirstOrDefault(),

为什么这个解决方案效率较低。

原方案中,对一组中的所有记录进行排序,只取第一条。如果你只取第一个,那么对第二个、第三个等元素进行排序有点浪费。

在原始 SQL 中,您会看到如下代码:

select maxIdBySymbol.Max(pp => pp.Id);

因此,并非所有元素都已排序。该序列只枚举一次,返回最大的一个。这比对您无论如何都不会使用的元素进行排序更有效。

要创建这样的代码,我们需要更改 GroupBy 的参数 resultSelector。让我们使用像 Max(propertySelector), or one of the overloads of Queryable.Aggregate 这样的方法。像这样:

// parameter resultSelector: keep the record with the largest ID
(symbol, priceRecordsWithThisSymbol) => priceRecordsWithThisSymbol
    .Max(record => record.Id);

唉,虽然 entity framework 的人做了大量工作,但不支持 Max 方法的这种重载,聚合方法的 none 也不支持。参见 List of Supported and Unsupported Linq methods