【问题标题】:Filtering by max and grouping by id with joins to other entities in LINQ to Entity Framework (C#)在 LINQ to Entity Framework (C#) 中按最大值过滤并按 id 分组并连接到其他实体
【发布时间】:2010-11-10 21:11:47
【问题描述】:

以下 sn-p 确实可以满足我的需要。我相信虽然必须有更好的做法?执行此查询的更优化方法?

需要的是获得一个员工对象列表,这些对象是员工/经理 x 的直接下属。直接下属列在历史表中,每个员工都有多条记录,因此每个直接下属(员工)应该从该表返回一条(最近的)记录,然后应该使用 Employee 表来获取员工 id 等于此过滤结果集中每个历史记录中的员工 id 的员工对象。我可以使用两个单独的 LINQ to EF 查询来获得两半。

尝试从第一个结果集中加入employeeHistory 对象时出现问题。根据 MSDN:不支持引用非标量闭包 [不支持在查询中引用非标量闭包,例如实体。执行此类查询时,会引发 NotSupportedException 异常,并显示一条消息,指出“无法创建‘闭包类型’类型的常量值。在此上下文中仅支持原始类型(‘例如 Int32、String 和 Guid’) ."]

所以我运行了两个查询,第一个是 int 类型的列表,而不是一个复杂的对象。这确实有效,但似乎是做作的。关于更好方法的任何建议(我想做一个查询)。

private List<BO.Employee> ListDirectReports(int mgrId)
{
    IQueryable<BO.Employee> directRpts;
    using(var ctx = new Entities())
    {
        //to get a list of direct rpts we perform two separate queries. linq to ef with linq to objects
        //first one gets a list of emp ids for a direct mgr emp id from the history table
        //this first qry uses grouping and a filter by empid and a filter by max(date)
        //the second qry joins to the resultset from the first and goes to the employee table 
        //to get whole employee objects for everyone in the int emp id list from qry #1

        //qry #1: just a list of integers (emp ids for those reporting to emp id of mgrId)
        IEnumerable<int> directRptIDList = 
            from employeeHistory in ctx.EmployeeHistory
            .Where(h => h.DirectManagerEmployeeID == mgrId).ToList()
                group employeeHistory by employeeHistory.EmployeeID into grp 
                    let maxDt = grp.Max(g => g.DateLastUpdated) from history in grp
                    where history.DateLastUpdated == maxDt
                    select history.EmployeeID;

        //qry #2: a list of Employee objects from the Employee entity. filtered by results from qry #1:
        directRpts = from emp in ctx.Employee
            join directRptHist in directRptIDList.ToList()
            on emp.EmployeeID equals directRptHist
            select emp;
    }
    return directRpts.ToList();
}

谢谢。

【问题讨论】:

    标签: c# linq entity-framework


    【解决方案1】:

    我可以想到 2 件事来改进您的查询:

    ToList 是非延迟的。在您的 Queryable 集合上调用它会导致大量额外的数据库访问。我也相信这个调用以及IEnumerable&lt;int&gt; 的显式声明导致了关闭错误。

    使用ObjectContex 中EmployeeHistory 和Employee 之间的关系来加入查询。这将使框架产生更高效的SQL。当directRpts 在您的ToList 调用中进行评估时,它应该只访问数据库一次。

    如果这有帮助,请告诉我。

    private List<BO.Employee> ListDirectReports(int mgrId)
    {
        using(var ctx = new Entities())
        {
            var directRptIDList = 
                from employeeHistory in ctx.EmployeeHistory
                                           .Where(h => h.DirectManagerEmployeeID == mgrId)
                group employeeHistory by employeeHistory.EmployeeID into grp 
                let maxDt = grp.Max(g => g.DateLastUpdated) from history in grp
                where history.DateLastUpdated == maxDt
                select history;
    
            var directRpts = 
                from emp in ctx.Employee
                join directRptHist in directRptIDList
                on emp equals directRptHist.Employee
                select emp;
        }
        return directRpts.ToList();
    }
    

    【讨论】:

      【解决方案2】:

      这里有很多问题,其中最重要的是,通过执行 Where在获得最新的历史记录项,您将获得不再有效的记录.以下是我的做法:

      private List<BO.Employee> ListDirectReports(int mgrId)
      {
          using(var ctx = new Entities())
          {
              // First make sure we're only looking at the current employee information
              var currentEntries = 
                  from eh in ctx.EmployeeHistory
                  group employeeHistory by employeeHistory.EmployeeID into grp 
                  select grp.OrderBy(eh => eh.DateLastUpdated).FirstOrDefault();
              // Now filter by the manager's ID
              var directRpts = currentEntries
                  .Where(eh => eh.DirectManagerEmployeeID == mgrId);
      
              // This would be ideal, assuming your entity associations are set up right
              var employees = directRpts.Select(eh => eh.Employee).Distinct();
      
              // If the above won't work, this is the next-best thing
              var employees2 = ctx.Employee.Where(
                                  emp => directRpts.Any(
                                      eh => eh.EmployeeId == emp.EmployeeId));
      
              return employees.ToList();
          }
      }
      

      【讨论】:

        【解决方案3】:

        谢谢索拉克斯。我发布的代码没有错误,并且确实给了我需要的结果,但是正如您所指出的,在包含 ToList() 方法时合并两个查询会出错。使用您的提示,我成功地合并了两者(对其进行了测试),并在下面发布了改进的单一查询方法。 StriplingWarrior 我也试过你的,也许我可以多按摩一下。函数评估在第一次查询时超时,所以我现在会坚持 Sorax 的建议。感谢您的帮助,并将重新考虑。

            private static List<BO.Employee> ListDirectReports(int mgrId)
            {
                IQueryable<BO.Employee> directRpts;
                using(var ctx = new Entities())
                {
                    directRpts = 
                        from emp in ctx.Employee
                        join directRptHist in 
                        (from employeeHistory in ctx.EmployeeHistory
                            .Where(h => h.DirectManagerEmployeeID == mgrId)
                        group employeeHistory by employeeHistory.EmployeeID into grp 
                        let maxDt = grp.Max(g => g.DateLastUpdated) from history in grp
                        where history.DateLastUpdated == maxDt
                        select history)
                        on emp equals directRptHist.Employee
                        select emp;
                }
                return directRpts.ToList();
        
                //IQueryable<BO.Employee> employees;
                //using(var ctx = new Entities())
                //{
        
                //        //function evaluation times out on this qry:
        
                //    // First make sure we're only looking at the current employee information
                //    IQueryable<BO.EmployeeHistory> currentEntries = 
                //        from eh in ctx.EmployeeHistory
                //        group eh by eh.EmployeeID into grp 
                //        select grp.OrderBy(eh => eh.DateLastUpdated).FirstOrDefault();
        
                //    // Now filter by the manager's ID
                //    var dirRpts = currentEntries
                //        .Where(eh => eh.DirectManagerEmployeeID == mgrId);
        
                //    // This would be ideal, assuming your entity associations are set up right
                //    employees = dirRpts.Select(eh => eh.Employee).Distinct();
        
                //    //// If the above won't work, this is the next-best thing
                //    //var employees2 = ctx.Employee.Where(
                //    //                    emp => directRpts.Any(
                //    //                        eh => eh.EmployeeId == emp.EmployeeId));
                //}
                //return employees.ToList();
            }
        

        【讨论】:

          猜你喜欢
          • 2016-10-09
          • 2010-11-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多