Linq to recurse through a hierarchy - c#

I have a heirarchical data structure as follows.
TABLE 1
id | Groupname | parentId
TABLE 2
id | nodeName | parentId
Table 1 parentId refers to table1 Id, and table2 parentId also refers to table 1 id.
Starting at any ID in table 1, I need to print out all the nodes, and then traverse through the children of all child groups.
Up to now I have this
int id = 1; // replace with argument
repository.Nodes.Where(n => n.ParentId == Id).ToList().ForEach(d =>
{
result.NodeList.Add(GetNodeDetails(n.Id));
});
Can anyone help me get this looping through in nice efficent linq manner?

If I understand you correctly you want to flatten the hierarchy, i.e. the end result should be one flat list with all children of all hierarchy levels.
The simplest way to achieve this is through recursion:
private IEnumerable<Node> GetSelfAndChildren(Node node)
{
yield return GetNodeDetails(n.Id);
foreach(var c in n.Children.SelectMany(GetSelfAndChildren)
yield return c;
};
var result = repository.Nodes.Where(n => n.ParentId == Id)
.AsEnumerable()
.SelectMany(GetSelfAndChildren)
.ToList();
This uses a recursive method to get a flat list of children.
This approach has the potential to exhibit the N+1 problem. Depending on the configuration each access of Children will cause a roundtrip to the database.
If the N+1 problem is happening and causing - well - problems, an alternative approach would be to first fetch all nodes from the database and then perform a Breadth-first search.

Related

Determine if record has children in LINQ to SQL

I am having at hierarchical table with the structure
ID, Name, FK_ID, Sortkey
Fetching the data in LINQ to SQL is straight forward:
var list = from ls in db.myTable
where ls.FK_ID == levelId
orderby ls.sortkey ascending
select ls;
And I can traverse down the tree by linking to the next levelId.
But what I can't figure out, if there is a way in LINQ, to check if there is any children
I could probably build a view, that added a flag to each record, but I would rather do this in LINQ, if possible.
What would even be the best practice for adding such a flag in SQL?
My idea on checking each record, is not the most performance friendly solution.
If you have set up the foreign key correctly, should you not have the 1 to Many mapping properties?
i.e. You could write
var listWithChildren = list.Where(l => l.Children.Any());
or going the other direction
var listWithParent = list.Where(l => l.FK_ID != null);
or using the query expression instead of fluent
var listWithChildren = from item in list
where item.Children.Any()
select item;
as you asked in your comments for a boolean flag, you could do
var updatedList = from item in list
select new
{
Item = item,
HasChildren = item.Children.Any()
};

LINQ search records until parentid = 0

I have a C# List with 3 fields: ID, Name and ParentID. I am binding it to a treeview. Now I am also having a search feature where I want to filter the List and rebind the treeview.
If I search for child-1-1, my linq should be able to get following records: parent-1, child-1-1. So that I have to get records containing my search text and than get the record with ID as ParentID of this. All ParentIDs(roots) have ParentID value 0 so I have to keep on getting records until ParentID is 0.
Example of Data:
ID Name ParentID
1 parent-1 0
2 parent-2 0
3 child-1-1 1
4 child-1-2 1
5 child-2-1 2
So my question is how can I get a LINQ expression to get records like I described above?
I mean something like var mydata = from p in this.mylist where...???
assuming you have List<Node> myList where Node class with properties (Id, ParentId, ...) based on https://stackoverflow.com/a/7063002/1594178 variant for you
static IEnumerable<Node> Parents(this IEnumerable<Node> nodes, int startId)
{
Node node = nodes.Where(n => n.Id = startId).Single();
yield return node;
while (node.ParentId != 0)
{
node = nodes.Where(n => n.Id = node.ParentId).Single();
yield return node;
}
}
to populate mydata with parents of your child with id = 3 along with that child use
var mydata = mylist.Parents(3)

Fetch max records of parent in one to many relationship

Suppose two class are there a ParentClass and a ChildClass. Parent is having a bag to have childs.
I have tried .SetResultTransformer(new DistinctRootEntityResultTransformer()) and distinct() which filters out the repeatitions and when fetching .SetMaxResults() I am not getting it in ParentClass level.
Is there anything which can be used to get make the .SetMaxResults() to work on ParentClass level and not on ChildClass. I need to enforce the maxresults in Parent level.
Example ParentClass having 6 childs and setmaxresults(6) and distinct() would result me to a single ParentClass while I am looking for more 5 ParentClass records in my query. And my criteria includes 3 parameters to match with Parent record and 2 to match with Child record
One solution could be to use the subquery. The documentation 14.11. Subqueries.
It will work like an inner select. The subquery will contain WHERE clause with 2 params to match Child, and a projection to return Parent.ID. The master query will then contain 3 params to filter Parent and also a call to subquery to match the Parent ID.
The subquery:
var sub = DetachedCriteria
.For<Child>()
.Add(Restrictions.In("FirsChildProperty", new int[] {1, 2 })) // WHERE
.Add(Restrictions.... // Second
.SetProjection(Projections.Property("Parent.ID")); // Parent ID as a SELECT clause
The master query:
var criteria = session.CreateCriteria<Parent>()
.Add(Restrictions.In("FirsParentProperty", new int[] {1, 2 })) // WHERE
.Add(Restrictions.... // the second
.Add(Restrictions.... // the third
// no filter to match children
.Add(Subqueries.PropertyIn("ID", sub)); // Parent.ID in (select
// now paging just over Parent table....
.SetFirstResult(100) // skip some rows
.SetMaxResults(20) // take 20
var result = criteria.List<Parent>();

LinqToSql Filter EntitySet

I am working on a WP7 application using Linq To Sql. I have used Linq, but this is the first I have used Linq to Sql. I am having an issue with filtering data in an EntitySet. I maybe doing it wrong I have no clue. What I have right now works, but I need to get one of the EntitySets filtered.
I have 4 tables. The Parent, Child, Grandchild, and a ParentChild linking table. When I query ParentChild, I get back the ParentChild entity and I can iterate through the Parent, Child and Grandchild entities just fine. What I want to be able to do is filter the Grandchild entity.
Lets say I have a father and mother in the Parent table. Then I have a son and daughter in the Child table. Then a grandson and granddaughter in the Grandchild table. Of course there are normal associations, etc.
I want to return the father, which also gets me all the associated tables just fine. The problem that I have is with filtering on the Grandchild. Let's say I want just the grandson and have a field for sex. How can I do this? I just can't seem to figure it out.
Here is the code I am using which works fine, but it pulls all the grandchildren.
IQueryable<ParentChild> parentChild = from ParentChild c in DataContext.ParentChild
where c.ParentId == this.parentId
select c;
foreach (Grandchild grandchild in parentChild.SelectMany(parent => parent.Child.Grandchild))
{
Console.WriteLine(grandchild.Name);
}
So if I do this:
IQueryable<ParentChild> parentChild = from ParentChild c in DataContext.ParentChild
where c.ParentId == this.parentId && c.Child.Grandchild.Any(a => a.Sex == "F")
select c;
foreach (Grandchild grandchild in parentChild.SelectMany(parent => parent.Child.Grandchild))
{
Console.WriteLine(grandchild.Name);
}
I get the parent, but I only get the children that have female grandchildren. I want the parent, all the children (even if they don't have female grandchildren or don't have any grandchildren) and only the female grandchildren.
After much trial and error and searching, I found the answer. I have to use the AssociateWith option.
DataLoadOptions dataLoadOptions = new DataLoadOptions();
dataLoadOptions.AssociateWith<Child>(c => c.Grandchild.Where(p => p.Sex == "F"));
this.DataContext.LoadOptions = dataLoadOptions;
As long as you've got your foreign keys set up correctly in SQL; LINQ to SQL will be able to give you association properties that match your foreign key relationships.
If your foreign keys are set up you'll be able to do the following...
var query = from p in DataContext.Parent
//make sure they have at least 1 female grandchild
where p.GrandChilds.Any(gc => gc.IsFemale)
select p;
I've made some assumptions about the names in your datamodel, but you get the idea. :-)

Recursive function to show hierarchial display using c#

I am having a table like this
ID Title Parentid
1 Level1 0
2 Level2 1
3 Level3 2
4 Level4 1
I want output in hierarchy model according to the parentid ,Id relationship as
Level1
->Level2->Level 3
-> Level4.
I am able to achieve like
level1
/\
level2 level4.
Here I am not getting level 3.
But i want the ouptut as shown in the first example using c#.
(Untested) Try:
;with RCTE as
(select id, title full_path from MyTable where ParentID = 0
union all
select m.id, r.full_path & '->' & m.title full_path
from MyTable m, RCTE r
where m.parentid = r.id)
select full_path from RCTE
Are all the parents defined before the children?
If so, you can use a Dictionary(int, List(Item)) (sorry about the parentheses, can't seem to get the angle brackets to work) where, say,
public class Item {
public int Id { get; set;}
public int ParentId { get; set;}
public string Title {get; set;}
}
IDictionary<int, List<Item>> CreateTree(IEnumerable<Item> nodeList){
var ret = new Dictionary<int, List<Item>>();
foreach (var item in items) {
if (!ret.ContainsKey(item.ParentId)) {
ret.Add(item.ParentId, new List<Item>());
}
ret[item.ParentId].Add(item);
}
return ret;
}
This will give (for the above data)
0 => level1
1 => level2, level4
2 => level3
If the parent ids are not guaranteed to be before the child ids, then you need to add in some tweaking to allow for orphans and then add then process them at the end.
Hope this helps,
Alan.
The recursion should be done inside of SQL Server using a Common Table Expression query (CTE). One query should be able to give the results and the "levels" which can then be parsed in C# without the need for recursion in code.
Here's a link with examples: (Mark's example also applies)
http://msdn.microsoft.com/en-us/library/ms186243.aspx

Categories

Resources