通过最大值进行过滤并使用连接将其分组,并将其连接到LINQ to Entity Framework(C#)中的其他实体

时间:2010-11-10 21:11:48

标签: c# linq entity-framework

以下代码段可以满足我的需求。我相信虽然必须有更好的做法?执行此查询的更佳方式是什么?

需要获取员工对象列表,这些员工对象是employee / mgr x的直接报告。直接报告列在历史记录表中,每个员工都有多条记录,因此每个直接报告(员工)只应从该表返回一条(最新的)记录,然后应使用Employee表获取员工对象,其中员工ID等于此筛选结果集中每个历史记录的员工ID。我可以通过两个单独的LINQ到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();
}

谢谢。

3 个答案:

答案 0 :(得分:2)

我可以考虑改善您的查询的两件事:

ToList没有被拒绝。在Queryable集合上调用它会导致大量额外的数据库访问。我也相信这个调用以及IEnumerable<int>的明确声明导致了关闭错误。

使用EmployeeHistoryEmployeeObjectContex之间的关系加入查询。这将使框架产生更高效的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();
}

答案 1 :(得分:0)

这里有很多问题,其中最重要的是通过在之前执行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();
    }
}

答案 2 :(得分:0)

谢谢Sorax。我发布的代码没有错误,并且确实给了我需要的结果,但正如您所指出的,在包含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();
    }