我正在尝试研究如何使用LINQ查询返回主实体的详细信息,以及从1对多(或0..1到多)子实体的一行中的详细信息.
这里的细节只是一个简化的例子.这更像是我正在寻找的概念.实体框架类可能定义为:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public ICollection<Login> Logins { get; set; }
}
public class Login
{
public int Id { get; set; }
public DateTime LoginTimestamp { get; set; }
public string IpAddress { get; set; }
public string UserAgent { get; set; }
public virtual User User { get; set; }
}
所以开始查询,我想要的东西像:
var results = from u in Users
select new {u.Id, u.Name, u.Email, u.Department}
到目前为止都很好.说我想添加上次登录日期和时间:
var results = from u in Users
select new {u.Id, u.Name, u.Email, u.Department, u.Logins.Max(LoginTimestamp)}
还好.但现在我想从最新的登录记录中添加更多细节.假设我也想包括上次登录时的IP地址.我知道查询将不得不大幅改变并使用除“Max”之外的其他内容,但我不确定如何.加入?子查询?
我已经看到过做这样的事情:
var results = from u in Users
select new {u.Id, u.Name, u.Email, u.Department,
u.Logins.Max(LoginTimestamp),
u.Logins.OrderByDescending(LoginTimestamp).FirstOrDefault().IPAddress}
…但我怀疑效率非常低,并且对于从子实体添加的每个其他列越来越多.
什么是最好/最有效的方法?
为了清楚起见,我只希望每个用户在结果集中有一行,只包含上次登录的详细信息.
谢谢
2016年6月16日编辑:
我已经构建了这个示例,并使用LINQPad来测试查询.
对于此LINQ查询:
from u in Users
select new {u.Id, u.Name, u.Email, u.Department,
LastLogin = u.Logins.OrderByDescending(l => l.LoginTimestamp).FirstOrDefault().LoginTimestamp,
LastIP = u.Logins.OrderByDescending(l => l.LoginTimestamp).FirstOrDefault().IpAddress}
…它产生的SQL是……
SELECT
[Project2].[Id] AS [Id],
[Project2].[Name] AS [Name],
[Project2].[Email] AS [Email],
[Project2].[Department] AS [Department],
[Project2].[C1] AS [C1],
[Limit2].[IpAddress] AS [IpAddress]
FROM (SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Email] AS [Email],
[Extent1].[Department] AS [Department],
(SELECT TOP (1) [Project1].[LoginTimestamp] AS [LoginTimestamp]
FROM ( SELECT
[Extent2].[LoginTimestamp] AS [LoginTimestamp]
FROM [dbo].[Logins] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[User_Id]
) AS [Project1]
ORDER BY [Project1].[LoginTimestamp] DESC) AS [C1]
FROM [dbo].[Users] AS [Extent1] ) AS [Project2]
OUTER APPLY (SELECT TOP (1) [Project3].[IpAddress] AS [IpAddress]
FROM ( SELECT
[Extent3].[LoginTimestamp] AS [LoginTimestamp],
[Extent3].[IpAddress] AS [IpAddress]
FROM [dbo].[Logins] AS [Extent3]
WHERE [Project2].[Id] = [Extent3].[User_Id]
) AS [Project3]
ORDER BY [Project3].[LoginTimestamp] DESC ) AS [Limit2]
…这不是特别有效,并且它只会变得更糟,因为它似乎为(相同的)子实体的每个字段输出添加子查询.
如果我在SQL中这样做,我会使用:
SELECT Users.Id, Users.Name, Users.Email, Users.Department, Logins.LoginTimestamp, Logins.IpAddress, Logins.UserAgent
FROM (SELECT MAX(LoginTimestamp) AS LastLoginTimestamp, User_Id
FROM Logins AS Logins_1
GROUP BY User_Id) AS LastLogins INNER JOIN
Logins ON LastLogins.LastLoginTimestamp = Logins.LoginTimestamp AND LastLogins.User_Id = Logins.User_Id INNER JOIN
Users ON Logins.User_Id = Users.Id
一个子查询,您可以根据需要从子表中引入尽可能多的字段.
所以我想我的问题是如何在LINQ中复制这个SQL查询?
或者,如果通过LINQ有更有效的方法,我很乐意看到它.
任何帮助赞赏!
谢谢
最佳答案 您可以使用let来避免使用double子查询
var result = from u in Users
let lastLogin = u.Logins.OrderByDescending(l => l.LoginTimestamp).FirstOrDefault()
select new {u.Id, u.Name, u.Email, u.Department,
LastLogin = lastLogin.LoginTimestamp,
LastIP = lastLogin.IpAddress};