Get nested elements in hierarchical manner C# linq - c#

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.

Related

Getting a list of objects of 2 different lists

I have a list of action type objects. Each object has a list of departments.
I have a user and a user object has n number of deparments.
I would like to get a list of user actions that the user can take. Here is what I came up with:
List<Item> userActions = new List<Item>();
foreach (var actionType in actionTypelist)
{
foreach (var dept in actionType.availableDepts)
{
if (data.currentUser.secondaryServDepts.Where(x => x.Id == dept.servDeptId).Count() > 0)
{
userActions.Add(actionType);
}
}
}
Can you please suggest a better way to get a quicker result? Thanks in advance.
One possible optimisation instead of:
if (data.currentUser.secondaryServDepts.Where(x => x.Id == dept.servDeptId).Count() > 0)
{
userActions.Add(actionType);
}
Use:
if (data.currentUser.secondaryServDepts.Any(x => x.Id == dept.servDeptId))
{
userActions.Add(actionType);
}
Count will enumerate the entire collection while Any will stop at the first match (since all you need is at least one item)

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

Comparing and insert/update/delete children in Entity Framework

I have a multiple selection form that needs to be saved into the database. Users can edit their selections on their subsequent visits. The items are defined in a table with unique IDs so the form only pass back a list of IDs.
It is something like:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user", fruits=apple,orange }
In DB there is a link table linking the id of people and id of fruits.
Now when I receive a request to edit, I'll need to compare with existing fruits so I know whether I need to add or delete an entry.
foreach (var fruit in existing_fruits)
{
if (post_fruits.Where(e => e.id == fruit.id).Count() == 0) user.Fruits.Remove(fruit);
}
foreach (var fruit in post_fruits)
{
if (existing_fruits.Where(e => e.id == fruit.id).Count() == 0)
{
var entity = context.Fruit.Where(e => e.id == fruit.id);
user.Fruits.Add(entity);
}
}
As you can see there are multiple loops and multiple which call on the lists, which makes me wonder if there is a cleaner way in doing this?
There are a lot of useful functions in EntityFramework.Extended. It contains Batch Update and Delete feature which can be useful in your situation, it also eliminates the need to retrieve and load an entity before modifying it, so it can increase your performance in future.
What if you use .Any method here (although it still uses two loops but it is more efficient) :
foreach (var fruit in existing_fruits)
if (!post_fruits.Any(e => e.id == fruit.id)) user.Fruits.Remove(fruit);
foreach (var fruit in post_fruits)
{
if (existing_fruits.Any(e => e.id == fruit.id)) continue;
var entity = context.FirstOrDefault(e => e.id == fruit.id);
if(entity != null) user.Fruits.Add(entity);
}
But it's better to change DB architecture to:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user" }
PeopleFruits
{ id=1, fruitId = 1, personId = 1, isSelected = 0}
All you need to update records now is to get this PeopleFruits entities of some person.
PeopleFruits[] personPplFruits = cont.PeopleFruits.Where(pf => pf.personId == 1).ToArray();
And update .isSelected properties according to what user has selected.
Check this article please : https://www.simple-talk.com/sql/database-administration/how-to-get-database-design-horribly-wrong/

Recursive Lambda Expression Query

I am trying to write a recursive lambda expression for going through this following structure.
NodeID ParentNodeID IsChecked
1 null false
2 1 false
3 1 false
4 1 false
5 1 false
6 2 false
7 2 false
8 2 false
9 2 false
10 6 false
11 6 false
12 6 false
13 3 false
14 3 false
15 13 false
16 13 false
//Now i have a List<Int32> checkedNodes Which has 2,3 in it.
List<Int32> checkedNodes = new List<Int32>({2,3});
//I can write a lambda expression which will set these nodes checked value to true.
myList.Where(x => checkedNodes.Contains(x.NodeID)).ToList().ForEach(x => x.Checked = true);
I want to write my lambda expression to set checked for all children and sub children. please Help.
I am open to alternative solution as well if this is not possible.
You cannot build a recursive process with LINQ only.
You will need a recursive function.
That's, in my opinion, is an appropriate and elegant solution:
private static void Traverse(Node node)
{
node.Checked = true;
_nodes.Where(x => x.ParentId == node.Id).ToList().ForEach(Traverse);
}
public static void Check(params int[] values)
{
values.Select(item => _nodes.Single(x => x.Id == item)).ToList().ForEach(Traverse);
}
Traverse is a recursive function which checks the node and calls itself for all its child nodes.
Check is just a function which calls Traverse for every node by given IDs list.
For example, you can wrap it in a public static helper class and it will be convenient to use it:
public static class NodeRecursiveChecker
{
private static List<Node> _nodes;
private static void Traverse(Node node)
{
node.Checked = true;
_nodes.Where(x => x.ParentId == node.Id).ToList().ForEach(Traverse);
}
public static void CheckNodes(this List<Node> nodes, params int[] values)
{
_nodes = nodes;
values.Select(item => _nodes.Single(x => x.Id == item)).ToList().ForEach(Traverse);
}
}
Then, you can use it this way:
list.CheckNodes(6, 13); // Mark 6, 13 and their children
list.CheckNodes(1); // Mark everything (as 1 is a root in your case)
Here is the DotNetFiddle Demo.
P.S. Note, that I am using LINQ Single function which will throw an exception if there will be no such element in a collection. For example, if you call nodes.CheckNodes(11) and there will be no Node with Id = 11 - it will throw exception. Of course, you can add a check for existence in CheckNodes but it is redundant if you are sure that you will pass only existing Ids. That's why I haven't used it.
Maybe this is what you're looking for:
Func<Node, bool> predicate = null;
predicate = x => checkedNodes.Contains(x.NodeId) || (x.Parent != null && predicate(x.Parent));
myList.Where(predicate).ToList().ForEach(x => x.IsChecked = true);
Sorry I don't know the declaration of your type containing NodeId, Parent and IsChecked. So I was guessing a little bit.
Maybe you can add an extension method for IEnumerable<T> to have ForEach and remove the ToList call that creates a copy of your collection.
You can write where clause something like this:
nodes.Where(node =>
checkedNodes.Contains(node.Id) ||
(node.ParentId != null && checkedNodes.Contains((int)node.ParentId))
).ToList().ForEach(each => { each.IsChecked = true; });
Have a look at complete demo example Here
This is not Linq , but one way of doing this. With the way LInq is designed, your question should be impossible
List<int> bag = new List<int>();
checkedNodes.ForEach(x=> bag.Add(x));
data.OrderBy(x=>x.ParentNodeId).ToList().ForEach(item=>{
if(bag.Contains(item.ParentNodeId)){
bag.Add(item.NodeId);
item.IsChecked = true;
}
});
This solution uses a bag to keep list of all parents that are already checked. It is not very efficient, but will work for small collections.
Note that I am sorting based on parent node id first, to reach parents before children. Also note that I am adding newly found parents to the bag.

Linq query for referal manager

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.

Categories