LINQ自引用表SELECT查询

时间:2019-10-14 08:52:31

标签: c# linq filter self-referencing-table

我有一个自我引用的雇员表,这意味着有些雇员的主键“ EmployeeID”和父键(主管)的外键“ SupervisorID”。管理者位于层次结构中,因此其SupervisorID为null。还有一个日期列(CreatedOn),用于过滤特定日期,但只有经理有日期,而没有员工。 我正在尝试从特定日期,经理及其雇员那里获得所有结果,但我只是无法运行它。我需要使用LINQ来实现这一点,但我想我可以首先进行SQL查询,以查看是否能够以某种方式将其迁移到LINQ查询(与哪种语法无关)。但是,一旦我意识到这和LINQ一样困难,这真是错觉。 我以为可以通过CTE来实现,但是我根本无法运行自引用。我只是得到管理者,还是一无所有... 这是一个示例表:

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

您可以看到,桑德斯(2019-01-01 16:20:33)有2名员工(佛兰德斯,安德鲁斯),游侠2019-03-20 18:04:56有2名员工(汉默,恩德森)和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               |
------------------------------------------------------

有人知道吗,我怎么能做到这一点?我将无法调整数据库设计,因为我只能读取,而另一个应用程序可以推送数据,而我无法更改。

4 个答案:

答案 0 :(得分:1)

让我们分别看一下主管和员工,然后再将两者合并。

主管

对于主管来说,逻辑很简单,您可以根据创建日期进行过滤。

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();

这将为您提供期望的结果集。请注意,当此结构是递归结构(即多个“主管的主管”)时,解决方案将变得更加困难,但是您的示例当前并非如此。


答案 1 :(得分:0)

使用加入,您可以同时拥有经理及其下属:

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();

我创建了一个小提琴,请检查以下链接:

C# compiler fiddle

注意:我与员工一起加入了自己,考虑了左侧列表的日期过滤器,并加入了右侧列表的supervisorId。它显示了经理及其各自的员工

答案 2 :(得分:0)

您需要一个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);

我假设只有两个级别(即没有人可以担任主管和拥有主管)。

答案 3 :(得分:-1)

所有Linq方法都是惰性求值的,您应该使用终端方法立即获得结果。

终端方法,例如ToList,ToArray,ForEach ...

employees.Where(e => e.SupervisorID != null).ToList()