LINQ 自引用 Table SELECT 查询

LINQ Self Referencing Table SELECT Query

我有一个自引用员工 table,这意味着有些员工的主键 "EmployeeID" 和父级(主管)的外键 "SupervisorID"。经理位于层次结构之上,因此他们的 SupervisorID 为空。还有一个日期列 (CreatedOn),用于过滤特定日期,但只有经理有日期,他们的员工没有。 我正在尝试获取特定日期、经理及其员工的所有结果,但我无法获取 运行。我需要使用 LINQ 来实现这一点,但我认为我可以先进行 SQL 查询,看看我是否能够以某种方式将其迁移到 LINQ 查询(无论哪种语法)。但这是非常错觉的,一旦我意识到它和 LINQ 一样难...... 我认为这可以通过 CTE 实现,但我根本无法获得自我引用 运行。要么我只是得到经理或什么都没有...... 这是一个示例 table:

------------------------------------------------------
|EmployeeID|Name    |SupervisorID|CreatedOn          |
|1         |Sanders |NULL        |2019-01-01 16:20:33|
|2         |Flanders|1           |NULL               |
|3         |Andrews |1           |NULL               |
|4         |Ranger  |NULL        |2019-03-20 18:04:56|
|5         |Hammer  |4           |NULL               |
|6         |Enderson|4           |NULL               |
|7         |Larsson |NULL        |2019-10-03 04:55:16|
|8         |Simpson |8           |NULL               |
------------------------------------------------------

可以看到,Sanders (2019-01-01 16:20:33) 有 2 名员工 (Flanders, Andrews),Ranger 2019-03-20 18:04:56 有 2 名员工 (Hammer, Enderson ) 和 Larsson (2019-10-03 04:55:16) 有 1 名员工 (Simpson)

我对 LINQ 一无所获。如果我过滤日期,我就没有办法过滤员工的初始数据集了。

// This filters by date, but then I can't get the employees without date anymore
employees.Where(e => e.CreatedOn >= '2019-01-01' && e.CreatedOn < '2019-01-02')

// This filters all employees with a parent, but then I can't get the managers anymore and can't filter against a date anymore either
employees.Where(e => e.SupervisorID != null)

我尝试了连接,但效果也不是很好:

from employees m in employees
join e in employees
on m.EmployeeId equals e.SupervisorID
where m.CreatedOn >= '2019-01-01' && m.CreatedOn < '2019-01-02'
select m

由于桑德斯的条目是在 1 月 1 日创建的,并且他的员工在他手下工作,因此适当过滤器的预期结果集将是桑德斯和他的员工:

------------------------------------------------------
|EmployeeID|Name    |SupervisorID|CreatedOn          |
|1         |Sanders |NULL        |2019-01-01 16:20:33|
|2         |Flanders|1           |NULL               |
|3         |Andrews |1           |NULL               |
------------------------------------------------------

有没有人知道我该如何实现?我将无法调整数据库设计,因为我只能读取并且另一个应用程序推送数据,我无法更改。

使用加入,您可以同时拥有经理和他们的下属:

var list = employees.Where(m=> m.CreatedOn >= new DateTime(2019,1,1) && m.CreatedOn < new DateTime(2019,1,2)).Join(employees,sc=> sc.EmployeeId, soc=> soc.SupervisorId, (sc,soc)=> new {left= sc, right=soc}  ).ToList();

我创建了一个 fiddle,查看下方 link:

C# compiler fiddle

注意:我将 employees 与自身结合,考虑左侧列表的日期过滤器,并在右侧列表中加入 supervisorId。它显示经理和 his/her 相应的员工

主管和员工分开来看,以后再合并。

主管

对于主管来说,逻辑很简单,你过滤创建日期。

var fromDate = new DateTime(2019,1,1);
var untilDate = new DateTime(2019,1,2);

var supervisors = employees
                         .Where(e => e.CreatedOn >= fromDate 
                                  && e.CreatedOn < untilDate )
                         .ToList();

注意:在您的数据集中,只有主管有创建日期。如果员工也可以有创建日期,您需要明确过滤掉它们:

var supervisors = employees
                         .Where(e => e.CreatedOn >= fromDate 
                                  && e.CreatedOn < untilDate 
                                  && e.SupervisorID == null)
                         .ToList();

员工

员工的逻辑稍微复杂一些,但您可以依靠 EF 的导航属性来为您处理连接。

简而言之,您想要主管的创建日期在所选范围内的员工。

var fromDate = new DateTime(2019,1,1);
var untilDate = new DateTime(2019,1,2);

var subordinates = employees
                         .Where(e => e.SupervisorID != null
                                  && e.Supervisor.CreatedOn >= fromDate 
                                  && e.Supervisor.CreatedOn < untilDate )
                         .ToList();

合并两者

合并这些相当简单,实际上是在做 supervisorFilter OR subordinateFilter:

var fromDate = new DateTime(2019,1,1);
var untilDate = new DateTime(2019,1,2);

var supervisorsAndSubordinates = employees
                         .Where(e => 
                                  // Supervisor filter
                                  (
                                       e.CreatedOn >= fromDate 
                                    && e.CreatedOn < untilDate
                                  )
                                  ||
                                  // Subordinate filter
                                  (
                                       e.SupervisorID != null
                                    && e.Supervisor.CreatedOn >= fromDate 
                                    && e.Supervisor.CreatedOn < untilDate
                                  ))
                         .ToList();

这将为您提供您期望的结果集。请注意,当此结构是递归的(即多个 "supervisor of supervisor" 级别)时,解决方案变得更加困难,但目前在您的示例中并非如此。


你需要一个left outer join

比如

 var mylist = (from employee in employees
              join r in employees on employee.SupervisorId equals r.EmployeeId  into results                  
              from supervisor in results.DefaultIfEmpty()

               let createdDate = supervisor != null ? supervisor.CreatedOn : employee.CreatedOn                  
               where  createdDate >= new DateTime(2019,1,1) && createdDate < new DateTime(2019,1,2) 
               select employee);

我假设只有两个级别(即没有人可以成为主管并有主管)。