获取相邻实体

Get Neighboring Entities

我正在编写一个函数,它根据日期从数据库中获取相邻(上一个和下一个)实体。我已经想出如何在 2 个查询中 return 邻居,但我更愿意同时拉出两个实体。

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);
}

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

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

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

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

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

把前后的去掉中间的怎么样?

我相信这仍会生成两个单独的 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();

这与在 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 core 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]