【问题标题】:LINQ Self Referencing Table SELECT QueryLINQ 自引用表 SELECT 查询
【发布时间】:2020-02-10 20:47:31
【问题描述】:

我有一个自引用员工表,这意味着有一个员工的主键“EmployeeID”和一个外键“SupervisorID”作为父级(主管)。经理位于层级之上,因此他们的 SupervisorID 为空。还有一个日期列 (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               |
------------------------------------------------------

可以看到,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

一个合适的过滤器的预期结果集是 Sanders 和他的员工,因为 Sanders 的条目是在 1 月 1 日创建的,而且他的员工在他手下工作:

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

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

【问题讨论】:

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


    【解决方案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();
    

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


    【讨论】:

    • 这个解决方案非常优雅,解决了我的问题 :) 但是,应该是“e.SupervisorID != null”而不是“e.Supervisor!= null”。非常感谢!
    【解决方案2】:

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

    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。它显示了经理和他/她各自的员工

    【讨论】:

    • 不鼓励仅链接的答案。随着链接恶化,答案变得毫无意义。
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-05
      • 2018-04-30
      相关资源
      最近更新 更多