LINQ下一个/最后一个日期相对于今天的EF6

时间:2017-09-15 09:22:29

标签: c# entity-framework linq entity-framework-6

我有以下型号:

a

我将这个概括为一个视图模型,使用LINQ with Entity Framework 6查询数据库:

public class User
{
    public int UserId { get; set; }
    public string FirstName { get; set; }
    public string Surname { get; set; }
    public string FirstNameSurname { get; set; }
    public ICollection<RotaUser> RotaUser { get; set; }
}

public class RotaUser
{
    [Key]
    [Column(Order = 1)]
    public int RotaId { get; set; }

    [Key]
    [Column(Order = 2)]
    public int UserId { get; set; }
    public bool Clinical { get; set; }
    public Rota Rota { get; set; }
    public User User { get; set; }
}

public class Rota
{
    public int RotaId { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public int Days { get; set; }
    public int BankHolidays { get; set; }
    public ICollection<RotaUser> Users { get; set; }
}

虽然下面的工作正如我所做的那样,public class OnCallSummaryViewModel { public string Name { get; set; } public DateTime? Next { get; set; } public DateTime? Last { get; set; } public int? Days { get; set; } public int? BankHolidays { get; set; } } Next值感觉不对,并且当LastMinDate时返回01/01/0001我宁愿返回NULL

public ActionResult Summary()
{
    DateTime today = DateTime.Now.Date;
    var model = db.User
        .Include(u => u.RotaUser.Select(r => r.Rota))
        .Where(u => u.OnCall.Value)
        .GroupBy(u => new { u.UserId, u.FirstNameSurname })
        .Select(u => new OnCallSummaryViewModel
        {
            Name = u.Key.FirstNameSurname,
            Days = u.Sum(r => r.RotaUser.Sum(x => x.Rota.Days)),
            BankHolidays = u.Sum(r => r.RotaUser.Sum(x => x.Rota.BankHolidays)),
            Next = u.Min(ru => ru.RotaUser
                .Where(r => r.Rota.StartDate >= today)
                .Select(d => d.Rota.StartDate)
                .OrderBy(d => d)
                .FirstOrDefault()),
            Last = u.Max(ru => ru.RotaUser
                .Where(r => r.Rota.StartDate <= today)
                .Select(d => d.Rota.StartDate)
                .OrderBy(d => d)
                .FirstOrDefault()),
        })
        .ToList();

    return PartialView("Summary", model);
}

如果我在TSQL中执行此操作,我希望复制的查询如下:

SELECT
    [User].UserId
,   [User].FirstNameSurname
,   SUM(Rota.[Days]) AS Days
,   SUM(Rota.BankHolidays) AS BankHolidays
,   MIN(CASE
        WHEN Rota.StartDate >= CAST(GETDATE() AS DATE)
            THEN Rota.StartDate
    END) AS NEXT
,   MIN(CASE
        WHEN Rota.StartDate <= CAST(GETDATE() AS DATE)
            THEN Rota.StartDate
    END) AS Last

FROM
    dbo.[User]
    LEFT JOIN OnCall.RotaUser
        ON  [User].UserId = RotaUser.UserId
    LEFT JOIN OnCall.Rota
        ON  RotaUser.RotaId = Rota.RotaId

WHERE
    [User].OnCall = 'TRUE'

GROUP BY
    [User].UserId
,   [User].FirstNameSurname

根据使用LINQ和EF6的条件检索下一个/最后一个值的正确方法是什么?

2 个答案:

答案 0 :(得分:2)

据我所知,您的查询结构有点奇怪。作为快速解决方法,请尝试将所选日期转换为可为空,因此FirstOrDefault将开始返回null而不是默认日期:

.Select(d => (DateTime?)d.Rota.StartDate)

仔细看看,我认为您根本不需要对数据进行分组,因为查询DbSet<User>将返回用户分组的RotaUser

所以我认为以下是您想要的查询:

var model = db.User
    .Where(u => u.OnCall.Value)
    .Select(u => new OnCallSummaryViewModel
    {
        Name = u.FirstNameSurname,
        Days = u.RotaUser.Sum(x => (int?)x.Rota.Days),
        BankHolidays = u.RotaUser.Sum(x => (int?)x.Rota.BankHolidays),
        Next = u.RotaUser
            .Where(r => r.Rota.StartDate >= today)
            .Min(d => (DateTime?)d.Rota.StartDate),
        Last = u.RotaUser
            .Where(r => r.Rota.StartDate <= today)
            .Max(d => (DateTime?)d.Rota.StartDate),
    })
    .ToList();

请注意,据我所知,您当前的查询(在问题中)会为Last返回错误的值,因为您在应用Max之前已经选择了最短的日期:

ru.RotaUser
    .Where(r => r.Rota.StartDate <= today)
    .Select(d => d.Rota.StartDate)
    .OrderBy(d => d) // first date will be minimum, not maximum
    .FirstOrDefault()

答案 1 :(得分:1)

通常,为了获得与SQL查询类似的结果(即使对于不可空的列,自然也支持NULL),您应该使用EF查询来提升非可空类型(例如StartDate },EndDateDaysBankHolidays)到相应的可空类型。

但LINQ查询的SQL转换很糟糕。您可以尝试使用与SQL查询等效的直接EF查询:

var query =
    from u in db.User
    from ru in u.RotaUser.DefaultIfEmpty() // left outer join
    let r = ru.Rota
    where u.OnCall == true
    group r by new { u.UserId, u.FirstNameSurname } into g
    select new OnCallSummaryViewModel
    {
        Name = g.Key.FirstNameSurname,
        Days = g.Sum(r => (int?)r.Days),
        BankHolidays = g.Sum(r => (int?)r.BankHolidays),
        Next = g.Min(r => r.StartDate >= today ? (DateTime?)r.StartDate : null),
        Last = g.Min(r => r.StartDate <= today ? (DateTime?)r.StartDate : null),
    };

不幸的是,这也无法获得最佳的SQL转换。您可以通过使用中间let语句帮助EF查询转换器来获得所需的结果:

var query =
    from u in db.User
    from ru in u.RotaUser.DefaultIfEmpty()
    let r = ru.Rota
    where u.OnCall == true
    let ri = new // the expressions needed by group aggregates
    {
        Days = (int?)r.Days,
        BankHolidays = (int?)r.BankHolidays,
        Next = r.StartDate >= today ? r.StartDate : (DateTime?)null,
        Last = r.StartDate <= today ? r.StartDate : (DateTime?)null
    }
    group ri by new { u.UserId, u.FirstNameSurname } into g
    select new OnCallSummaryViewModel
    {
        Name = g.Key.FirstNameSurname,
        Days = g.Sum(r => r.Days),
        BankHolidays = g.Sum(r => r.BankHolidays),
        Next = g.Min(r => r.Next),
        Last = g.Max(r => r.Last),
    };

使用以下SQL生成所需的结果:

SELECT
    [GroupBy1].[K1] AS [UserId],
    [GroupBy1].[K2] AS [FirstNameSurname],
    [GroupBy1].[A1] AS [C1],
    [GroupBy1].[A2] AS [C2],
    [GroupBy1].[A3] AS [C3],
    [GroupBy1].[A4] AS [C4]
    FROM ( SELECT
        [Filter1].[K1] AS [K1],
        [Filter1].[K2] AS [K2],
        SUM([Filter1].[A1]) AS [A1],
        SUM([Filter1].[A2]) AS [A2],
        MIN([Filter1].[A3]) AS [A3],
        MAX([Filter1].[A4]) AS [A4]
        FROM ( SELECT
            [Extent1].[UserId] AS [K1],
            [Extent1].[FirstNameSurname] AS [K2],
            [Extent3].[Days] AS [A1],
            [Extent3].[BankHolidays] AS [A2],
            CASE WHEN ([Extent3].[StartDate] >= @p__linq__0) THEN  CAST( [Extent3].[StartDate] AS datetime2) END AS [A3],
            CASE WHEN ([Extent3].[StartDate] <= @p__linq__1) THEN  CAST( [Extent3].[StartDate] AS datetime2) END AS [A4]
            FROM   [dbo].[User] AS [Extent1]
            LEFT OUTER JOIN [dbo].[RotaUser] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[UserId]
            LEFT OUTER JOIN [dbo].[Rota] AS [Extent3] ON [Extent2].[RotaId] = [Extent3].[RotaId]
            WHERE 1 = [Extent1].[OnCall]
        )  AS [Filter1]
        GROUP BY [K1], [K2]
    )  AS [GroupBy1]
相关问题