【问题标题】:Get Neighboring Entities获取相邻实体
【发布时间】:2020-07-28 21:41:00
【问题描述】:

我正在编写一个函数,它根据日期从数据库中获取相邻(上一个和下一个)实体。我已经想出了如何在 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);
}

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

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

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

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

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

【问题讨论】:

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


    【解决方案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 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]
    

    【讨论】:

    • 谢谢,我会在稍后回到我的计算机时验证这一点
    • 什么Trials?
    【解决方案2】:

    取前后,去掉中间怎么样?

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

    【讨论】:

    • 我喜欢这个,Idea,我现在想不出办法,但我认为使用 select/selectMany 可能有一种巧妙的方法来做到这一点,加入会起作用,但我认为这可能会使生成的查询比单独选择它们需要更长的时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    相关资源
    最近更新 更多