Linq query for referal manager - c#

I have a requirement in my LINQ in MVC application i.e. I need to populate the list of employeeId in a dropdown, to assign mgr to employee's, whose are not subordinate of his or his subordinates subordinate. To make my point more clear below is the scenario-
Id MgrId
1
2 1
3
4 2
5
6 3
I try
empBO.GetAllEmpList().Where(s => s.Id != model.ID && s.MgrId != model.ID);
this works for only one level, from the above when I select empid 1 for edit to assign the mgr the drop down should only contain empid of 3,5 and 6. I haven't had much expertise in LINQ and hopt this could be done using LINQ any suggestion/help would be appreciated.

You can use recursive method to find all subordinates of given employee (note, if hierarchy depth is big, you can use query/stack implementation instead of recursion):
public static IEnumerable<Employee> GetSubordinates(
IEnumerable<Employee> employees, int employeeId)
{
foreach (var employee in employees)
{
if (employee.MgrId != employeeId)
continue;
yield return employee;
foreach (var subordinate in GetSubordinates(employees, employee.Id))
yield return subordinate;
}
}
Getting available employees will be simple:
var employees = GetAllEmpList();
var available = employees.Except(GetSubordinates(employees, selectedId));
This method will return employees with ids { 2, 4 }. Then calling Enumerable.Except will give you all employees besides those who are direct or indirect subordinated of selected one.

Related

C# Linq or querying IEnumerable

I have an Employee table which also has Department Manager information. I need to populate two dropdowns - one with Employees and other with Managers. Instead of using two queries to pull employees and another query to pull managers, I am querying table once and storing all info in cache in an IEnumerable EmployeeList.
I need some query to pull managers from that query - either using LINQ or loop within C# code. I have written loop but it is very inefficient.
Here is the SQL query to populate HCache:
SELECT [Dept_Mgr_ID] As MgrId,
EmployeeId,
EmpLastName,
EmpFirstName
FROM Employee_tbl
Here I am trying to loop through the cache and join EmployeeId and MgrId
List<DTO.Employee> Mgrs = new List<DTO.Employee>(0);
for (int i = 0; i < HCache.EmployeeList.Count(); i++)
{
foreach(var e in HCache.EmployeeList)
{
if (HCache.EmployeeList.ElementAt(i).EmployeeId == e.MgrId)
{
Mgrs.Add(new DTO.Employee() { MgrID = e.MgrId,
ManagerLastName = e.EmpLastName,
ManagerFirstName = e.EmpFirstName
});
}
}
}
I am not using this query, however, this is how I can get the results using 2nd query to get managers:
WITH CTE_Manager_ID
AS
(
SELECT DISTINCT [Dept_Mgr_ID]
FROM Employee_tbl
)
SELECT EmployeeId,
EmpLastName,
EmpFirstName
FROM Employee_tbl Emp
INNER JOIN CTE_Manager_ID cteMgr
ON cteMgr.Dept_Mgr_ID = Emp.EmployeeId
I'd say you should use your second SQL query to get the managers, but I'll try to speed up your code.
Problems:
Assuming EmployeeList is an IEnumerable, EmployeeList.ElementAt(i) is an O(n) operation, i.e. slow. It's a nested loop behind the scenes.
EmployeeList.Count() is an O(n) operation, i.e. slow.
The resulting complexity of your code is O(n^3), i.e. very slow.
How to improve:
Do one pass to build a map from EmployeeId to Employee (or whatever you store in HCache.EmployeeList). This will enable you to find them quickly by id (in O(1)).
Do another pass through EmployeeList to collect the managers.
The overall complexity is O(n), i.e. proportional to the size of the EmployeeList collection.
Here is some code to illustrate the idea:
class Emp {
public int EmployeeId {get;set;}
public int MgrId {get;set;}
public string EmpLastName {get;set;}
}
IEnumerable<Emp> EmployeeList = new List<Emp> {
new Emp { EmployeeId = 1, MgrId = 0, EmpLastName = "boss" },
new Emp { EmployeeId = 2, MgrId = 1, EmpLastName = "dude" } };
IDictionary<int, Emp> dict = EmployeeList.ToDictionary(e => e.EmployeeId);
var managers = EmployeeList
.Select(e => dict.TryGetValue(e.MgrId, out Emp mgr) ? mgr : null)
.OfType<Emp>()
.ToList()
// List<Emp>(1) { Emp { EmpLastName="boss", EmployeeId=1, MgrId=0 } }
Note that this code potentially produces duplicates in the managers list, which may or may not be what you want, but your code behaves this way so I preserved the behavior.

Get nested elements in hierarchical manner C# linq

I have list of objects which is map as Parent- child relationship.
so parent have many children, grand children and great grandchildren and so on.
I have structured table as below
UserId UserNme
1 Jack
2 William
3 asha
4 winston
ParentId ChildId
2 3
3 4
So I want to list the users in following hierarchical manner:
->User not as child (Parent/Non-parent)
----> Child Users
-------->Grand child users
-----------> Great Grand child users
I have tried below code, but not completed it:
public List<MarketingUserDto> GetChildAgents(List<MarketingUserDto> agents,List<MarketingUserDto> resultAgents)
{
if (agents.Count == 0)
{
var parentagents = _userRegistrationRepository.GetMany(x => ((x.IsParentAgent ?? false) == true && x.UserTypeId == (short)Enums.UserTypes.Agent) || (x.UserTypeId == (short)Enums.UserTypes.Super_Manager && (x.IsParentAgent ?? false) == false));
this.GetChildAgents(Mapper.Map<List<UserRegistration>, List<MarketingUserDto>>(parentagents.ToList()), resultAgents);
}
else
{
foreach (var agent in agents)
{
var childagents = _agentMappingRepository.GetMany(x => x.ParentId == agent.UserId, y => y.UserRegistration);
}
}
return resultAgents;
}
Could you please provide any suggestion how to achieve this?
If you don't want to reinvent the flattening wheel, take a look at MoreLinq's Flatten() extension method.
If you grab the NuGet package, you get that and a lot more useful LINQ extensions.

Select Multiple Values from a Collection using Lambda Expression

How do I select two or more values from a collection into a list using a single lambda expression? Here is what I am trying:
List<Prodcut> pds=GetProducts();
List<Product> pdl = new List<Product>();
foreach (Product item in pds)
{
pdl.Add(new Product
{
desc = item.Description,
prodId = Convert.ToInt16(item.pId)
});
}
GetProducts() returns a list of Products that have many (about 21) attributes. The above code does the job but I am trying to create a subset of the product list by extracting just two product attributes (description and productId) using a single lambda expression. How do I accomplish this?
What you want to do is called projection, you want to project each item and turn them into something else.
So you can use a Select:
var pdl = pds.Select(p => new Product
{
desc = p.Description,
prodId = Convert.ToInt16(p.pId)
}).ToList();

Select top N elements and remember occurence's order

I have a requirement to select top N elements of related products from a big list of products.
So far, I have below code and it works perfectly.
class Product
{
public string Name;
public double Rating;
public List<Product> RelatedProducts;
public List<Product> GetTopRelatedProducts(int N)
{
var relatedSet = new HashSet<Product>();
var relatedListQueue = new Queue<List<Product>>();
if (RelatedProducts != null && RelatedProducts.Count > 0)
relatedListQueue.Enqueue(RelatedProducts);
while (relatedListQueue.Count > 0)
{
var relatedList = relatedListQueue.Dequeue();
foreach (var product in relatedList)
{
if (product != this && relatedSet.Add(product) && product.RelatedProducts != null && product.RelatedProducts.Count > 0)
relatedListQueue.Enqueue(product.RelatedProducts);
}
}
return relatedSet.OrderByDescending(x => x.Rating).Take(N).OrderBy(/*How to order by occurrence here? */).ToList();
}
}
Now, I want GetTopRelatedProducts method to remember the occurrence order of top N products. First added product to the HashSet will be at the begining of the returned List.
For example, if I have this scenario:
//...
relatedSet.Add(new Product(){Name="A", Rating=3});
relatedSet.Add(new Product(){Name="B", Rating=4});
relatedSet.Add(new Product(){Name="C", Rating=5});
//...
and if N = 2, the method should return : B,C instead of C,B because B was added first to the HashSet.
So I changed the return statement in the method to:
var relatedSetCopy = relatedSet.ToList();
return (from p in relatedSet.OrderByDescending(x => x.Rate).Take(N)
join c in relatedSetCopy on p.Name equals c.Name
let index = relatedSetCopy.IndexOf(c)
orderby index
select p).ToList();
Basically, I use LINQ Join to re-order the list in the same way it was before the ordering on Rating.
I want to do it this way because first added product has more similarity with selected product than others.
I have two questions here:
Is there a better way to re-order the returned list?
Is there a better design to handle relation between products? (I was thinking about implementing a tree structure. So object navigation and retrieval will be faster)
Is there a better way to re-order the returned list?
You can simply Intersect the relatedSet with the top N related reordered set because Intersect yields the items based on their order in the first sequence.
So instead of
return relatedSet.OrderByDescending(x => x.Rating).Take(N).ToList();
you would use
return relatedSet.Intersect(relatedSet.OrderByDescending(x => x.Rating).Take(N)).ToList();

LINQ All not working as expected when used with Contains

I have a many-to-many relationship like this:
Organisations (OrganisationID, Name)
Categories (CategoryID, Name)
Organisations_Categories (OrganisationID, CategoryID)
I'm trying to get a list of Organisations that belong to all the Categories that get passed in as a parameter
e.g. If 10 Organisations belong to CategoryID=1, and 0 Organisations belong to CategoryID=2, and I pass in [1,2] as the CategoryID parameter, 0 Organisations should be returned, because 0 Organisations belong to both CategoryID=1 and CategoryID=2
Here is the code so far:
int[] catIdsSelected = ((catIds.Length > 0) ? Array.ConvertAll(catIds.Split(','), int.Parse) : new int[0]);
if (catIdsSelected.Length > 0)
{
orgs = orgs.Where(l => l.Categories.Any(m => catIdsSelected.AsQueryable().Contains(m.CategoryID)));
}
However this returns a list of Organisations that belong to any of the Categories passed in
I've tried replacing 'Any' with 'All' without success
You can do a little trick:
if (catIdsSelected.Length > 0)
{
foreach (var id in carIdsSelected)
{
var localId = id;
orgs = orgs.Where(o => o.Categories.Any(cat => cat.CategoryId == localId));
}
}
It will work for sure, but I am not sure how efficient query is generated by EF provider.

Categories