获取相邻实体

时间:2020-04-15 19:17:36

标签: c# entity-framework linq asp.net-web-api expression

我正在编写一个函数,该函数根据日期从数据库中获取相邻的(上一个和下一个)实体。我已经找到了如何在2个查询中返回邻居的方法,但是我希望一次可以同时拉两个实体。

public interface IHasDateRange
{
    DateTime StartDate { get; set; }
    DateTime EndDate { get; set; }
}

public static (TEntity Previous, TEntity Next) GetNeighborsOrDefault<TEntity>(
    this IQueryable<TEntity> query, JustDate startDate)
        where TEntity : class, IHasDateRange
{
    var previous = query.Where(x => x.StartDate < startDate)
            .OrderByDescending(x => x.StartDate)
            .FirstOrDefault();

    var next = query.Where(x => x.StartDate > startDate)
        .OrderBy(x => x.StartDate)
        .FirstOrDefault();

    return (previous, next);
}

我想在单个查询中提取上一个和下一个,最好采用一种不会因翻译过于复杂的Expression而产生sql庞然大物的方式。

编辑 我在想如果我删除开始日期过滤器的位置并计算距离的话,有一种方法可以解决,但是我仍然卡住了,但是我觉得应该可以工作。

var previous = query
    .Select(x => new { 
        Entity = x,  
        Distance = DbFunctions.DiffDays(x.StartDate, startDate)
   })
    .Where(x => x.Distance != 0); 

注意:假定每个实体都有一个唯一的起始日期。

有没有一种简单的方法可以在单个查询中提取上一个和下一个实体?

2 个答案:

答案 0 :(得分:1)

如何去除前后的并消除中间的东西?

我相信,这仍然会生成两个单独的SQL查询-一个用于获取Count(),另一个用于获取结果,但是除非您想为EF添加ROW_NUMBER支持(您可以扩展EF Core,为此),我认为没有更好的方法:

var previousAndNext = query.OrderBy(x => x.StartDate)
        .Skip(query.Where(x => x.StartDate < startDate).Count()-1)
        .Take(3)
        .Where(x => x.StartDate != startDate)
        .Take(2) // if startDate not in DB, just get previous and next
        .ToList();

答案 1 :(得分:1)

这与从startDate之前的第一个日期删除(包括)这两个实体相同。

query.Where(e => e.StartDate != startDate
    && e.StartDate >= query.OrderByDescending(e1 => e1.StartDate)
        .Where(e1 => e1.StartDate < startDate).Select(e1 => e1.StartDate).FirstOrDefault())
    .OrderBy(e => e.StartDate)
    .Take(2)

如您所见,尽管第二个查询现在是一个主查询中的子查询,但您无法避免运行两个查询。

在EF6中,这会生成一个中等复杂的查询,如下所示:

SELECT TOP (2)
    ...
    FROM ( SELECT 
        ...
        FROM  [dbo].[Entity] AS [Extent1]
        INNER JOIN  (SELECT TOP (1) [Project1].[StartDate] AS [StartDate]
            FROM ( SELECT 
                [Extent2].[StartDate] AS [StartDate]
                FROM [dbo].[Entity] AS [Extent2]
                WHERE [Extent2].[StartDate] < @p__linq__1
            )  AS [Project1]
            ORDER BY [Project1].[StartDate] DESC ) AS [Limit1] ON 1 = 1
        WHERE ( NOT (([Extent1].[StartDate] = @p__linq__0) AND ((CASE WHEN ([Extent1].[StartDate] IS NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END) = 0))) AND ([Extent1].[StartDate] >= [Limit1].[StartDate])
    )  AS [Project2]
    ORDER BY [Project2].[StartDate] ASC

看到EF核心3.1.3生成这样的简单查询,我感到很惊讶:

SELECT TOP(@__p_2) ...
FROM [Entity] AS [e]
WHERE (([e].[StartDate] <> @__startDate_0) OR [e].[StartDate] IS NULL) AND ([e].[StartDate] >= (
    SELECT TOP(1) [e0].[StartDate]
    FROM [Entity] AS [e0]
    WHERE [e0].[StartDate] < @__startDate_1
    ORDER BY [e0].[StartDate] DESC))
ORDER BY [e].[StartDate]