我有以下型号:
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
值感觉不对,并且当Last
值MinDate
时返回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的条件检索下一个/最后一个值的正确方法是什么?
答案 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
},EndDate
,Days
和BankHolidays
)到相应的可空类型。
但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]