Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# LINQ查询以包含子表一行中的字段_C#_.net_Entity Framework_Linq - Fatal编程技术网

C# LINQ查询以包含子表一行中的字段

C# LINQ查询以包含子表一行中的字段,c#,.net,entity-framework,linq,C#,.net,Entity Framework,Linq,我正在尝试解决如何使用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

我正在尝试解决如何使用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, 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实现这一点,我很乐意看到它

感谢您的帮助!
谢谢

通过为每个实体框架查询调用
ToString()
,您可以在每种情况下获得生成的SQL。你可以检查它的效率

如果您在某个地方错误地使用了
可枚举
扩展而不是EF和
可查询
扩展(转换为单个SQL查询),您将看到这一点。

试试这个

var results = from u in Users
    select new {u.Id, u.Name, u.Email, u.Department,     
    Logins.Where(l=>l.User.Id==u.Id).LastOrDefault()}

我猜登录Id是自动递增的。如果是这样,LastOrDefault()将返回用户的最后登录名

您可以使用
let

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

为我自己的问题提供另外两个解决方案

这两种方法都是反向的,因为查询是从登录的上下文来处理问题的,而不是像前面两个答案那样从用户的角度来处理问题。 它们非常相似,但彼此略有不同

方法1:

from l in Logins
// where clause here
group l by l.User.Id into grp
let LastLogin = grp.Max(ul => ul.LoginTimestamp)
from l in grp
where l.LoginTimestamp == LastLogin
select new { l.User.Id, l.User.Name, l.User.Email, l.User.Department, LastLogin }
方法2:

from l in Logins
// where clause here
group l by l.User.Id into grp
let ul = grp.OrderByDescending(ul => ul.LoginTimestamp).FirstOrDefault()
select new { ul.User.Id, ul.User.Name, ul.User.Email, ul.User.Department, LastLogin = ul.LoginTimestamp }
方法1生成的SQL看起来比方法2更干净,优化效果更好,但仍然与前面两个答案不太匹配。出于这个原因,我仍然更喜欢Jamiec的答案


在任何情况下,这两种方法都可能对某些人有用。

这没有错,sql将优化到正确的连接,从而将所有子部分限制为易于管理的块。我想您的登录表可能有一百万条记录,但这更多的是管理数据保留,而不是微观优化查询。依我看,带点盐吃吧,YMMV等等。同意@PaulSwetz。您应该考虑到<代码> LastLogin <代码>属性到您的代码>用户< /代码>。在登录表中的每次插入上,最后一次登录开始填充(由登录表的FK填充)。@PaulSwetz-这种方法确实有效,但在我的特定应用程序中通过LinqPad分析SQL之后,它似乎为从子实体引入的每个字段构建了一个子查询,这正是我所期望的。直接使用SQL,您将使用来自同一查询的所有必需字段,因此它似乎没有对其进行充分优化。这让我回到问题上来——有没有更好/更有效的方法?(当我得到5分时,我将基于此示例构建一个应用程序,以确认我的发现和结论!)@tschmit077-我非常喜欢这种方法,并将在适当的地方使用它。谢谢但是,它解决了这个特定示例中的场景。如果我需要更灵活一点的东西怎么办?也许我需要在特定时间范围内的最后一次登录,或者从特定IP地址的最后一次登录,等等?虽然我包括了这个例子,但问题更具概念性——我对一种更动态的方法感兴趣,在这种方法中,感兴趣的子行可能依赖于运行时定义的许多因素,因此不能存储为父实体中的指针。Entitiy框架适用于不需要担心速度的人(因为您无法直接控制正在生成的SQL)如果你需要速度,考虑写一个SQL存储过程,然后从EF调用它。谢谢@停止Crand。我不能做到这一点-也许是因为我正在运行的具体查询。但是,它确实促使我下载LinqPad为同一个网络结果。谢谢Mehmet。我意识到我与原来的查询有多么接近。我会做一个。小改动只是为了删除Where子句的要求,并删除对自动增量ID的依赖:LastLogin=u.Logins.orderbydecenting(l=>l.LoginTimestamp).FirstOrDefault()。否则,它生成的SQL与我想要的完全一样,与@jamice的答案相同。在可读性方面,我发现jamice的答案更容易一些,另外还可以指定要绘制的列,但显然每个人都会有自己的偏好。谢谢!谢谢。我喜欢这种方法。清晰、灵活,不是吗他完成了工作。(LINQPad确认生成的SQL中的优化)。+1来自我-谢谢!你能帮我完成这个linq查询吗:
from l in Logins
// where clause here
group l by l.User.Id into grp
let ul = grp.OrderByDescending(ul => ul.LoginTimestamp).FirstOrDefault()
select new { ul.User.Id, ul.User.Name, ul.User.Email, ul.User.Department, LastLogin = ul.LoginTimestamp }