I have a self referencing employee table, meaning that there are employees with a primary key "EmployeeID" and a foreign key "SupervisorID" for the parent (supervisor). Managers are on top of the hirarchy, so their SupervisorID is null. There's also a date column (CreatedOn), to filter for specific dates, but only managers have a date, not their employees. I'm trying to get all results from a specific date, managers and their employees, but I just can't get it running. I need to achieve this with LINQ, but I thought I could an SQL query first, to see if I'm somehow able to migrate this to an LINQ query (doesn't matter which syntax). But this was very dellusional, once I realized that it's as hard as in LINQ... I thought that this might be achieved with a CTE, but I can't get the self-reference running at all. Either I just get managers or nothing... Here's a sample 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 |
------------------------------------------------------
You can see, that Sanders (2019-01-01 16:20:33) has 2 employees (Flanders, Andrews), Ranger 2019-03-20 18:04:56 has 2 employees (Hammer, Enderson) and Larsson (2019-10-03 04:55:16) has 1 employee (Simpson)
I didn't get anywhere at all with LINQ. If I filter on the date, I don't have any way anymore, to filter on the initial dataset for the employees.
// 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)
I tried a join, but that didn't work very well either:
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
An expected resultset for an appropriate filter would be Sanders and his employees, since Sanders' entry was created on January 1 and since his employees work under him:
------------------------------------------------------
|EmployeeID|Name |SupervisorID|CreatedOn |
|1 |Sanders |NULL |2019-01-01 16:20:33|
|2 |Flanders|1 |NULL |
|3 |Andrews |1 |NULL |
------------------------------------------------------
Does anyone have an idea, how I could achieve this? I won't be able to adjust the database design, since I can only read and another application pushes the data, which I can't change.
Let's look at supervisors and employees separately, and then merge the two later on.
Supervisors
For a supervisor, the logic is fairly simple, you filter on the creation date.
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();
Note: in your dataset, only supervisors have a creation date. If employees can have a creation date as well, you need to explicitly filter them out:
var supervisors = employees
.Where(e => e.CreatedOn >= fromDate
&& e.CreatedOn < untilDate
&& e.SupervisorID == null)
.ToList();
Employees
The logic for employees is slightly more complicated, but you can rely on EF's navigational properties to handle the joins for you.
In short, you want the employees whose supervisor has a creation date in the selected range.
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();
Merging the two
Merging these is fairly simple, it's effectively doing 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();
This will give you the result set you're expecting. Note that the solution becomes significantly harder when this structure is recursive (ie multiple "supervisor of supervisor" levels), but this is currently not the case in your example.
using join, you can have both managers and their subordinates:
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();
I've created a fiddle, check below link:
note: I joined employees with itself, considering the date filter for left list, and joining on supervisorId on right list. It shows manager, and his/her respective employee
You need a left outer join
Ie something like
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);
I'm assuming that there are only two levels (ie no-one can be a supervisor and have a supervisor).
All Linq methods are lazy-evaluated, you should use an terminal method to get result immediately.
Terminal Methods like ToList, ToArray, ForEach...
employees.Where(e => e.SupervisorID != null).ToList()
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.