我有一个对象的导航属性,例如Items,比如Order。如果我想获得订单列表,包括项目,我可以做类似的事情:
var orders = dbContext.Order.Include(o => i.Items);
这很好用,但现在我想为每个订单只获得3个项目,我想知道实现这一目标的最佳方法。
一种方法是执行以下操作:
var orders =
(from o in dbContext.Order
join i in dbContext.Items on o.Id equals i.OrderId
select new { o.Id, i })
.GroupBy(o => o.Id)
.SelectMany(i => i.Take(3))
这很好用,虽然生成的SQL有点复杂,但我想知道是否有更直接(或高性能)的方式。
谢谢, 埃里克
答案 0 :(得分:3)
表现有多糟糕?如果它是可以忍受的那么我就不管它了。在SQL中也没有直接的方法来做到这一点。通常,您最终会得到一个子查询,用于计算分组所分区的ROW_NUMBER
,然后返回行号小于n
的行。
由于没有将该机制直接转换为Linq,因此我保持Linq可以理解并且不担心生成的SQL的复杂性,除非它是一个非常重要的性能问题。
您还可以将其与返回所有项目的性能进行比较,然后使用Linq-to-Objects进行过滤。
另一种选择是将其编码为存储过程,而不是尝试在Linq中进行编码。
答案 1 :(得分:3)
这应该以{{1}}的形式生成简单的SQL,其中包含对项目的前3个语句。然后我们必须使用linq-to-objects进行一些分组,但只有我们需要的数据来自服务器。
OUTER APPLY
如果您只想要每个订单的前三个项目而没有订单实体。将SQL中的top语句生成var orders =
(from o in dbContext.Order
from i in (from x in dbContext.Items
where o.Id == x.OrderId
select x).Take(3).DefaultIfEmpty()
select new
{
Order = o,
Item = i
}).AsEnumerable()
.GroupBy(x => x.Order)
.Select(x => new { Order = x.Key, Items = x.Select (y => y.Item ) });
项。
CROSS APPLY
答案 2 :(得分:3)
var orders = dbContext.Order
.Select(o => new
{
Order = o,
Items = o.Items.Take(3)
})
.AsEnumerable()
.Select(a => a.Order)
.ToList();
如果
,这将自动填充Order.Items
集合中的前三项
Order
和Item
之间的关系不是多对多(可能不是这种情况,因为订单和商品通常有一对多的关系)修改强>
生成的SQL查询是:
SELECT
[Project2].[Id] AS [Id],
[Project2].[C1] AS [C1],
[Project2].[Id1] AS [Id1],
[Project2].[OrderId] AS [OrderId],
FROM (SELECT
[Extent1].[Id] AS [Id],
[Limit1].[Id] AS [Id1],
[Limit1].[OrderId] AS [OrderId],
CASE WHEN ([Limit1].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Orders] AS [Extent1]
OUTER APPLY (SELECT TOP (3)
[Extent2].[Id] AS [Id],
[Extent2].[OrderId] AS [OrderId],
FROM [dbo].[Items] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[OrderId] ) AS [Limit1]
) AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC