LINQ query through several object "layers" in an IList? - c#

I have a List<Category> where Category is:
public class Category {
public List<Category> Categories { get; set; } // this holds sub-categories
public string Name { get; set; }
public string Icon { get; set; }
public string Id { get; set; }
}
Since Categories is itself another List<Category> it could contain sub-categories, and those sub-categories could contain sub-categories, and so forth...
I know I can query past the first "layer" like so:
Categories.Where(x => x.Categories.Any(c => c.Id == id)).FirstOrDefault();
How can I effectively query for a specific Category by Id, perhaps 3,4, or 5 layers deep in the object tree (there are at most 3, but for future reference I'd like to know)?
Edit
In addition, how could I get the entire object tree, all the way up to the top level Category, if I only had an Id of a sub-category 3 layers deep?

This will recursively traverse categories until category matching passed id will be found (if any). Full path to found category will be returned (i.e. like breadcrumbs menu does):
static IEnumerable<Category> GetById(IEnumerable<Category> categories, string id)
{
if (categories == null || !categories.Any())
yield break;
Category result = categories.FirstOrDefault(c => c.Id == id);
if (result != null)
{
yield return result;
yield break;
}
foreach (var category in categories)
{
var subCategories = GetById(category.Categories, id);
if (subCategories.Any()) // we have found the category
{
yield return category; // return current category first
foreach (var subCategory in subCategories)
yield return subCategory;
yield break; // don't search in other categories
}
}
}
Usage:
IEnumerable<Category> result = GetById(categories, id);
// Food > Beer > Kilkenny
string breadcrumbs = String.Join(" > ", result.Select(c => c.Name).ToArray());
You can convert this method to extension if you wish.

You could write an extension method like the following to flatten Category as an IEnumerable<Category>:
public static IEnumerable<Category> Flatten(this Category category)
{
if (category.Categories != null)
{
foreach (var sub in category.Categories)
{
foreach (var subSub in sub.Flatten())
yield return subSub;
}
}
yield return category;
}
Then you use Linq on the IEnumerable<Category> as you like:
var filtered = categoryList.SelectMany(x => x.Flatten())
.Where(x => x.Id == id);

You would need to recurse through the Categories if you had an indeterminate level of nesting, and even if you had a fixed level of nesting, for any level of nesting more than 2-3 levels down, it would be worth recursing.
Linq doesn't really have a way of expressing recursion, although this post talks about using Linq2Xml features to achieve it: Expressing recursion in LINQ
If you are able to modify the class itself, you could implement a GetChildById-style method to recursively scan through the child Categories.

Related

Search value in List<T> inside another List<T>

i have class strucur something like this
List<MainCat> AllCat;
public class MainCat
{
public string id { get; set; }
public string name { get; set; }
public List<subcat> subcat { get; set; }
}
public class subcat
{
public string id { get; set; }
public string name { get; set; }
public List<subsubcat> subsubcat { get; set; }
}
public class subsubcat
{
public string id { get; set; }
public string name { get; set; }
}
i want to get name by id,
for example i know the id is 69
i want get output like this
MainCat.name > subcat.name > subsubcat.name (if 69 found in subsubcat)
MainCat.name > subcat.name (if 69 found in subcat)
MainCat.name (if 69 found in MainCat)
If I've understood your requirement properly, this is a case where the query syntax can work wonders:
IEnumerable<string> MyFunc(IEnumerable<MainCat> mainCategories, string idToMatch)
{
return (from main in mainCategories
where main.id == idToMatch
select main.name)
.Concat(from main in mainCategories
from sub in main.subcat
where sub.id == idToMatch
select string.Format("{0} > {1}", main.name, sub.name))
.Concat(from main in mainCategories
from sub in main.subcat
from subsub in sub.subsubcat
where subsub.id == idToMatch
select string.Format("{0} > {1} > {2}", main.name, sub.name, subsub.name));
}
If you're only interested in the first match, this can be called like
string resultName = MyFunc(AllCat, "69").FirstOrDefault();
Because the query uses deferred execution, this will avoid calling the more complex queries if a match is found in the main category.
It is also possible to use the SelectMany function with the function call syntax, however, it gets much harder to follow e.g. the following is how I re-wrote the contents of the second .Concat(...) call in order to illustrate:
mainCategories.SelectMany(main => main.subcat, (main, sub) => new { Main = main, Sub = sub })
.SelectMany(pair => pair.Sub.subsubcat, (pair, subsub) => new { Main = pair.Main, Sub = pair.Sub, SubSub = subsub})
.Where(triplet => triplet.SubSub.id == idToMatch)
.Select(triplet => string.Format("{0} > {1} > {2}", triplet.Main, triplet.Sub, triplet.SubSub));
As I understand it, the query syntax compiles to something very similar to this behind the scenes.
Update after answer accepted, and I came back to look at my code again:
Another possibility would be to add an interface to all 3 classes (or unify them into a single class or derive from a common base class depending on real use case).
This allows a recursive implementation that can search to arbitrary depth (below are 2 different Linq-based implementations depending on whether you have a preference for one or other syntax):
public interface ITreeCat
{
string id { get; }
string name { get; }
IEnumerable<ITreeCat> subcat { get; }
}
// add explicit interface implemetantion to existing 3 classes
// e.g.
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return subsubcat; } }
// IEnumerable<ITreeCat> ITreeCat.subcat { get { return Enumerable.Empty<ITreeCat>(); } }
IEnumerable<string> MyFunc(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
return (from cat in categories
where cat.id == idToMatch
select prefix + cat.name)
.Concat(from cat in categories
from recursiveResult in MyFunc(cat.subcat, idToMatch, prefix + cat.name + " > ")
select recursiveResult);
}
IEnumerable<string> MyFunc2(IEnumerable<ITreeCat> categories, string idToMatch, string prefix = "")
{
return categories.Where(cat => cat.id == idToMatch)
.Select(cat => prefix + cat.name)
.Concat(categories.SelectMany(cat => MyFunc2(cat.subcat, idToMatch, prefix + cat.name + " > ")));
}
This has the advantage that it continues to work if you later add a subsubsubcat etc.
All of the above code examples use a breadth-first search, and repeatedly enumerate the "parent" categories each time they go one level deeper.
In some applications a depth-first search may be a better choice, as each list is only enumerated once, in which case it's much easier to use foreach rather than Linq. Again, a recursive version is more concise than 3 nested loops with different classes:
IEnumerable<string> MyFuncDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
foreach(var cat in categories)
{
if (cat.id == idToMatch)
yield return cat.name;
foreach (var subResult in MyFuncDepthFirst(cat.subcat, idToMatch))
yield return string.Format("{0} > {1}", cat.name, subResult);
}
}
This still assumes that multiple matches can occur. If we're just after the first match, then there's no need to use an iterator block at all, and the above function can be modified to return a simple string:
string FirstMatchingIdDepthFirst(IEnumerable<ITreeCat> categories, string idToMatch)
{
foreach(var cat in categories)
{
if (cat.id == idToMatch)
return cat.name;
string subResult = FirstMatchingIdDepthFirst(cat.subcat, idToMatch);
if(subResult != null)
return string.Format("{0} > {1}", cat.name, subResult);
}
return null;
}
var list = this.AllCat.Where(t=>t.subcat.Any(s=> subsubcat.contains(s));
You can go for a method like below
public static Type Find(string id, MainCat m)
{
if (m.id.Equals(id))
{
return m.GetType();
}
if (m.subcat.Any(a => a.id.Equals(id)))
{
return typeof(subcat);
}
if (m.subcat.Any(a => a.subsubcat.Any(b => b.id.Equals(id))))
{
return typeof(subsubcat);
}
return null;
}
and perform the search. Find the gist, https://gist.github.com/IshamMohamed/33d75064789d77d88404b8ffc9a17e94
In this way you can increase the number of inner lists (eg: subsubsubcat)

Get parent department node in Entity Framework

I have a SQL table like this:
DepartmentID is parent of department. I've build a tree by this table(in ASP.net (C#) project):
Records in tree above is:
I need to get parents in this tree.
I can do it in SQL Server like this(for Example id=2, id is input argument):
with cte1
as
(
select id,name,DepartmentID, 0 AS level
from Department
where id =2
union all
select Department.ID,Department.name,Department.DepartmentID, level+1
from Department
inner join cte1 on Department.ID=cte1.DepartmentID
)
select * from cte1
Output(id=2 (A))
Output(id=4 (A1))
I know EF does not support cte, but I need to get this result in EF.
It would be very helpful if someone could explain solution for this problem.
These posts are similar to your question.please see these:
writing-recursive-cte-using-entity-framework-fluent-syntax-or-inline-syntax
converting-sql-statement-that-contains-with-cte-to-linq
I think there is no way to write a single LINQ to SQL query that could get all However, LINQ supports a method to execute a query (strangly enough called DataContext.ExecuteQuery). Looks like you can use that to call a arbitrary piece of SQL and map it back to LINQ.
See this post:
common-table-expression-in-entityframework
The easiest way I can think of is to map the relationship in EF and then retrieve all departments and then get the root parent from that list. All of them should be loaded in memory and EF will take care of the tree structure with the mapping. Alternatively you can enable lazy loading and just get the parent but then with each child or childset a query will be executed by EF during retrieval.
Model
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int? DepartmentId { get; set; }
public Department ParentDepartment { get; set; }
public virtual ICollection<Department> ChildDepartments { get; set; }
}
Mapping (using fluent)
public DbSet<Department> Departments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// other mapping code
modelBuilder.Entity<Department>()
.HasOptional(x => x.ParentDepartment)
.WithMany(x => x.ChildDepartments)
.HasForeignKey(x => x.DepartmentId);
// other mapping code
}
Eager retrieval of root parent
using (var context = new YourDbContext())
{
var allDepartments = context.Departments.ToList(); // eagerly return everything
var rootDepartment = allDepartments.Single(x => x.DepartmentId == null);
}
Retrieval of only root parent and then use lazy loading, note that the DbContext needs to be available for Lazy Loading to work and it must also be enabled on the DbContext
using (var context = new YourDbContext())
{
var rootDepartment = context.Departments.Single(x => x.DepartmentId == null);
// do other stuff, as soon as context is disposed you cant lazy load anymore
}
Try one of these,
1-
int _ID = 2; // ID criteria
List<object> result = new List<object>(); // we will use this to split parent at child, it is object type because we need Level
var departments = entites.Departments.Where(x => x.ID == _ID).SelectMany(t => entites.Departments.Where(f => f.ID == t.DepartmentID),
(child, parent) => new { departmentID = child.DepartmentID, Name = child.Name, ID = child.ID, level = 0,
Parent = new { DepartmentID = parent.DepartmentID, Name = parent.Name, ID = parent.ID, level = 1 }});
// first we check our ID (we take A from where criteria), then with selectmany T represents the Department A, we need
// department A's departmentID to find its parent, so another where criteria that checks ID == DepartmentID, so we got T and the new list
// basically child from first where parent from second where, and object created.
// for showing the results
foreach (var item in departments)
{
result.Add(new { DepartmentID = item.departmentID,ID = item.ID, level= item.level,Name = item.Name}); // child added to list
result.Add(new { DepartmentID = item.Parent.DepartmentID, ID = item.Parent.ID, level = item.Parent.level, Name = item.Parent.Name }); // parent added to list
}
Result;
2-
List<object> childParent = new List<object>();
// basically get the child first
Departments child1 = entites.Departments.Where(x => x.ID == _ID).FirstOrDefault();
// find parent with child object
Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
// create child object with level
childParent.Add(new { child1.DepartmentID, child1.ID,child1.Name , level = 0});
// create parent object with level
childParent.Add(new { parent1.DepartmentID,parent1.ID,parent1.Name, level = 1 });
Result (not the same image, check column Header Text);
Edit 1:
3-
Another way, by giving ID as input and assuming that ID column is unique, so there will be always 2 values at the array and by returning list, the index of items actually represent their levels. (won't add results because they are same :)).Btw you can also use Union instead of Concat.
var ress = list.Where(x=> x.ID ==2)
.SelectMany(x=> list.Where(c=> c.ID == x.ID).Concat(list.Where(s => s.ID == x.DepartmentID))).ToList();
DataTable dt = new DataTable();
dt.Columns.Add("DepartmentID");
dt.Columns.Add("ID");
dt.Columns.Add("Name");
dt.Columns.Add("Level");
for (int i = 0; i < ress.Count(); i++)
{
dt.Rows.Add(ress[i].DepartmentID, ress[i].ID, ress[i].Name, i);
}
dataGridView1.DataSource = dt;
Edit 2
There is not cte in linq, basically using view,sp is the first choise but here is a solution, it might be a little push. Anyway it gives the result.
List<Departments> childParent = new List<Departments>();
// or basically get the child first
Departments child1 = entites.Departments.Where(x => x.ID == 7).FirstOrDefault();
// find parent with child object
Departments parent1 = entites.Departments.Where(x => x.ID == child1.DepartmentID).FirstOrDefault();
// create child object with level
Departments dep = new Departments(); // I add to department class a string level field
dep.DepartmentID = child1.DepartmentID;
dep.ID = child1.ID;
dep.Name = child1.Name;
dep.level = 0; // first item
childParent.Add(dep);
// create parent object with level
dep = new Departments();
dep.DepartmentID = parent1.DepartmentID;
dep.ID = parent1.ID;
dep.Name = parent1.Name;
dep.level = 1; // parent one
childParent.Add(dep);
while (childParent.Select(t => t.DepartmentID).Last() != null) // after added to list now we always check the last one if it's departmentID is null, if null we need to stop searching list for another parent
{
int? lastDepID = childParent.Last().DepartmentID; // get last departmentID
Departments tempDep = entites.Departments.Single(x => x.ID == lastDepID); // find as object
tempDep.level = childParent.Last().level + 1; // increase last level
childParent.Add(tempDep); // add to list
}
(Added another C1 to check 4th level)
Hope helps,
Below is the simple console project Program class code.
You can check with different IDs for the input parameter of the GetParentSet method.
class Program
{
static void Main(string[] args)
{
Program p = new Program();
var result= p.GetParentSet(6);
foreach(var a in result)
{
Console.WriteLine(string.Format("{0} {1} {2}",a.ID,a.Name,a.DepartmentId));
}
Console.Read();
}
private List<Department> GetParentSet(int id)
{
List<Department> result = new List<Department>(); //Result set
using (RamzDBEntities context = new RamzDBEntities())
{
var nodeList = context.Departments.Where(t=>t.ID<=id).ToList(); //Get All the the entries where ID is below or greater than the given to the list
var item = nodeList.Where(a => a.ID == id).SingleOrDefault(); //Get the default item for the given ID
result.Add(item); //Add it to the list. This will be the leaf of the tree
int size = nodeList.Count(); //Get the nodes count
for (int i = size; i >= 1;i--)
{
var newItem= nodeList.Where(j => j.ID == item.DepartmentId).SingleOrDefault(); //Get the immediate parent. This can be done by matching the leaf Department ID against the parent ID
if (item!=null && !result.Contains(newItem)) //If the selcted immediate parent item is not null and it is not alreday in the list
{
result.Add(newItem); //Add immediate parent item to the list
}
if (newItem.ID == 1) //If the immediate parent item ID is 1 that means we have reached the root of the tree and no need to iterate any more.
break;
item = newItem; //If the immediate parent item ID is not 1 that means there are more iterations. Se the immediate parent as the leaf and continue the loop to find its parent
}
}
return result; //return the result set
}
}
Code itself is self-explanatory. However below is the explanation. Hope this will help!
First all the entries with ID below or equal to the given ID is
assigned to a List
Then get the leaf of the tree and add it to the list named result. This is the first element of our result set
We iterate through the retrieved entries descending order. Get the immediate parent of the leaf by equating parent's ID to leaf's department ID
If this immediate parent is not null and its not already in the list add it to the list.
Make the immediate parent item as the leaf and continue the loop so that we can get the parent of the immediate parent.
continue this until we reach the root of the tree.
If the immediate parent ID is=1 that means we have reached the root of the tree and we can break the loop.
Since you generated the edmx, you have the code generated for your DbContext and for your Model Classes including Departments like on this screenshot.
You shouldn't modify them because they might (will) get overwritten by EF tools anyway on any model manipulation. Fortunately both classes are generated as partial so the creators thought about people wanting to customize it safely.
Example below is made for simplicity of implementation not for top performance. I assumed that the table containing Departments is not enormously big and the levels of nesting in hierarchy are not enormously deep.
Create a new Class (*.cs file) in your project and extend your auto-generated Departments class by your custom method or property:
using System;
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class Departments
{
public List<Departments> Hierarchy {
get {
List<Departments> retVal = new List<Departments>();
retVal.Add(this);
using (YourAutoGeneratedContext ctx = new YourAutoGeneratedContext())
{
Departments tmp = this;
while(tmp.DepartmentID != null)
{
tmp = ctx.Departments.First(d => d.ID == tmp.DepartmentID);
retVal.Add(tmp);
}
}
return retVal;
}
private set { }
}
}
}
When you extend the partial class, make sure that you put it in the same namespace. In my case I named my project CustomEF and I've placed the edmx file in the EFStuff subfolder so the generator placed the auto generated class in the CustomEF.EFStuff namespace.
The example above will allow you to get the hierarchy for any Departments object e.g.
int level = 0;
foreach(Departments d in someDepartmentObject.Hierarchy)
{
Console.WriteLine(d.ID.ToString() + ", " + d.DepartmentID.ToString() + ", " + d.Name +", " +(level++).ToString());
}
If you also need to get the hierarchy from some code where you have an ID but not the object, you can additionally create another class (*.cs file) where you'll extend the auto-generated context.
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class YourAutoGeneratedContext
{
public List<Departments> GetDepartmentHierarchy(int departmentId)
{
Departments mydep = this.Departments.FirstOrDefault(d => d.ID == departmentId);
if (mydep == null)
{
throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString());
}
return mydep.Hierarchy;
}
}
}
Or in this case you might want to move the implementation to the Context class entirely, without extending the Departments class at all (and you wouldn't have to create an additional instance of your context, you'll have the this to use).
using System.Collections.Generic;
using System.Linq;
namespace CustomEF.EFStuff
{
public partial class YourAutoGeneratedContext
{
public List<Departments> GetDepartmentHierarchy(int departmentId)
{
Departments tmp = this.Departments.FirstOrDefault(d => d.ID == departmentId);
if (tmp == null)
{
throw new System.Data.Entity.Core.ObjectNotFoundException("There is no department with ID = " + departmentId.ToString());
}
List<Departments> retVal = new List<Departments>();
retVal.Add(tmp);
while (tmp.DepartmentID != null)
{
tmp = this.Departments.First(d => d.ID == tmp.DepartmentID);
retVal.Add(tmp);
}
return retVal;
}
}
}
As another unsophisticated use example:
YourAutoGeneratedContext ctx = new YourAutoGeneratedContext();
level = 0;
foreach (Departments currentHier in ctx.GetDepartmentHierarchy(10))
{
Console.WriteLine(currentHier.ID.ToString() + ", " + currentHier.DepartmentID.ToString() + ", " + currentHier.Name + ", " + (level++).ToString());
}
I don't know how much you can trust the data in the database. You might want to implement some checks including cross-referencing departments to prevent infinite loop.
Note that formally the term 'to extend a class' may apply to extension methods rather then to partial classes. I used this word from lack of better one. Extension methods would be something that you might want to use if, for some reason, you'd need your method/property returning EF native DbSet<> instead of the List<>. In such case you might want to take look at: https://shelakel.co.za/entity-framework-repository-pattern/
Example in EF6 to get all parents up to root node.
public class Department
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Department Parent { get; set; }
public virtual ICollection<Department> Children { get; set; }
private IList<Department> allParentsList = new List<Department>();
public IEnumerable<Department> AllParents()
{
var parent = Parent;
while (!(parent is null))
{
allParentsList.Add(parent);
parent = parent.Parent;
}
return allParentsList;
}
}
use include keyword.
_context.Invoices.Include(x => x.Users).Include(x => x.Food).ToList();

How to count total # of items in nested categories (self-referencing category table) [duplicate]

This question already has answers here:
Most efficient method of self referencing tree using Entity Framework
(6 answers)
Closed 7 years ago.
I am using C#, ASP.Net, Entity Framework, LINQ, Microsoft SQLServer
I have two tables
tblCategories
Id
Name
ParentId (parentId is the Id of another category in this table that is the parent of this one)
tblQuestion
Id
CategoryId
Etc.
So say my categories table contains
Programming (Id 1 ParentId null) - directly contains 10 questions
C# (Id 25 ParentId 1) – directly contains 5 questions
Interview Questions (Id 99 ParentId 25) - directly contains 2 questions
Variables (Id 100 ParentId 25) – directly contains 1 question
Networking (Id 2 Parent Id null)
I want to know how many questions exist for a each category, including its child categories.
In other words in this example 18 questions are in programming, 8 are in C#
Is it possible to do this in a query or do I need some kind of iterative loop called on each category?
such as db.categories.Where(something).count()
Here is my category class:
public class Category
{
[Key]
public int Id { get; set; }
[StringLength(40)]
public string Name { get; set; }
public int? ParentId { get; set; }
[JsonIgnore]
public virtual ICollection<Category> Children { get; set; }
[JsonIgnore]
public virtual Category Parent { get; set; }
[JsonIgnore]
public virtual ICollection<Question> Questions { get; set; }
}
then in OnModelCreating:
modelBuilder.Entity<Category>()
.HasMany(x => x.Children)
.WithOptional(y => y.Parent)
.HasForeignKey(z => z.ParentId)
.WillCascadeOnDelete(false);
there are several similar questions on StackOverflow, like this: Help with generating a report from data in a parent-children model and this linq aggregated nested count but I can't find one that is a good enough match and I can understand
To clarify, I want to know the number of questions in the child categories, not the number of child categories.
I am unsure as to what you want to accomplish, but as others pointed out it can't be accomplished by simple Linq.
If what you want is to get a total count of questions given a category: you need to:
var count = TotalQuestions(category1);
TotalQuestions method
public int TotalQuestions(Category category)
{
var totalQuestions = category.Questions.Count;
foreach (var innerCategory in category.Categories)
{
totalQuestions += TotalQuestions(innerCategory);
}
return totalQuestions;
//OR
//return category.Questions.Count + category.Categories.Sum(innerCategory => TotalQuestions(innerCategory));
}
If you want to have the total count of questions for each Category (including the count of its child categories) you need:
var counts = new Dictionary<Category, int>();
TotalQuestions(category1, counts);
2nd TotalQuestions method
private int TotalQuestions(Category category, Dictionary<Category, int> counts)
{
if (!counts.ContainsKey(category))
{
counts.Add(category, category.Questions.Count);
}
foreach (var innerCategory in category.Categories)
{
counts[category] += TotalQuestions(innerCategory, counts);
}
return counts[category];
}
Sample
For this sample data:
var category1 = new Category(1, "Cat1", null);
var category2 = new Category(1, "Cat2", category1);
var category3 = new Category(1, "Cat3", category2);
var category4 = new Category(1, "Cat4", category2);
var category5 = new Category(1, "Cat5", category3);
category1.Questions.Add(new Question("q1"));
category1.Questions.Add(new Question("q2"));
category1.Questions.Add(new Question("q3"));
category1.Questions.Add(new Question("q4"));
category2.Questions.Add(new Question("q1"));
category2.Questions.Add(new Question("q2"));
category2.Questions.Add(new Question("q3"));
category3.Questions.Add(new Question("q1"));
category3.Questions.Add(new Question("q2"));
category4.Questions.Add(new Question("q1"));
category5.Questions.Add(new Question("q1"));
category5.Questions.Add(new Question("q2"));
var count = TotalQuestions(category1);
MessageBox.Show(count.ToString());
var counts = new Dictionary<Category, int>();
TotalQuestions(category1, counts);
You Get:
count = 12
And in counts:
category1 => 12
category2 => 8
category3 => 4
category4 => 1
category5 => 2
Yes, you need a recursive method.
// Recursively counts the number of children of this parent
static int CountChildren(Category parent)
{
if (parent == null)
return 0;
if (parent.Children == null || !parent.Children.Any())
return 0;
return parent.Children.Count()
+ parent.Children.Sum(ch => CountChildren(ch));
}
You can't do such counting using only a simple linq query or only with fluent callings for the simple fact that you have a variable number of levels of iterations (the number of generations: parents, children, grandchildren, etc varies). Therefore, I would recommend creating a recursive method to count children:
static void RecursiveQuestionCount(Category cat, ref int count)
{
count += Questions.Count(); // this cat's questions.
foreach(var child in cat.Children)
{
RecursiveQuestionCount(child, ref count); // will add each child and so it goes...
}
}
The count result will be stored in the ref int count:
int catCount;
Foo.RecursiveQuestionCount(existingCategoryObj, catCount);
Console.WriteLine(catCount);

How do I count the number of child collection's items using LINQ Method Syntax?

Let's say I have a schema, representing Question entities. Each question can be voted up, voted down or, of course, not voted at all - just like here in StackOverflow. I want to get the number of voteups for a given user.
int number = (from q in userDbContext.Questions
from qv in q.QuestionVotes
where qv.IsVoteUp
select qv).Count();
I want to write the same query, but using Method Syntax. How do I do this with the same example?
You can use SelectMany:
userDbContext.Questions.SelectMany(x => x.QuestionVotes).Count(x => x.IsVoteUp);
This LINQ query demonstrates how to do that using 3 level structure tree > branch > leaf as an example.
So the code below gives you the number of the leaves from all branches of all trees (all or only colored with the given color):
public class Calculator
{
public int CountAllLeafsOn(List<Tree> trees, string сolor = null)
{
// Count the leafs (all from all branches of all trees, or only if they are colored with the provided color)
return сolor == null
? trees.Sum(tree => tree.Branches.Sum(branch => branch.Leaves.Count))
: trees.Sum(tree => tree.Branches.Sum(branch => branch.Leaves.Count(leaf => leaf.Color.Equals(сolor))));
}
}
public class Tree
{
public List<Branch> Branches { get; set; }
}
public class Branch
{
public List<Leaf> Leaves { get; set; }
}
public class Leaf
{
public string Color { get; set; }
}
Hope that helps.
It must work:
int number = userDbContext.Questions
.Select(x => x.QuestionVotes.Count(y => y.IsVoteUp))
.Sum();
It will get the count of filtered child items for each parent. Then Sum() will compute the sum of these values.
You can count children using Where like this:
foreach (YourCollectionType item in datagrid.Items)
{
var children = datagrid.ItemsSource.OfType<YourCollectionType>().Where(x => x.Item1 == item.Item1 && x.Item2 == item.Item2 && x.Item3 == item.Item3 && x.Item4 == item.Item4);
item.Results = children.Count();
Trace.TraceInformation(item.Results.ToString());
}

How to filter a recursive object?

In my current project, a method I don't control sends me an object of this type:
public class SampleClass
{
public SampleClass();
public int ID { get; set; }
public List<SampleClass> Items { get; set; }
public string Name { get; set; }
public SampleType Type { get; set; }
}
public enum SampleType
{
type1,
type2,
type3
}
I display those data in a TreeView, but I would like to display only the path ending with SampleClass objects having their Type property set to type3, no matter the depth of this leaf.
I have absolutely no clue on how to do that, can someone help me ?
Thanks in advance !
Edit
To explain the problem I meet with the solutions proposed by Shahrooz Jefri and dasblinkenlight, here is a picture. The left column is the original data, without filtering, and the right one is the data filtered. Both methods provide the same result.
In red is the problem.
Use this Filter method:
public void Filter(List<SampleClass> items)
{
if (items != null)
{
List<SampleClass> itemsToRemove = new List<SampleClass>();
foreach (SampleClass item in items)
{
Filter(item.Items);
if (item.Items == null || item.Items.Count == 0)
if (item.Type != SampleType.type3)
itemsToRemove.Add(item);
}
foreach (SampleClass item in itemsToRemove)
{
items.Remove(item);
}
}
}
In addition to initially determining which items to show, if the datasize is substantial and you expect users to frequently collapse and expand sections then filtering after every click my result in slow ui response.
Consider the Decorator pattern or some other way of tagging each node with relevant info so that the filtering is not required after every click.
Try this approach:
static bool ShouldKeep(SampleClass item) {
return (item.Type == SampleType.type3 && item.Items.Count == 0)
|| item.Items.Any(ShouldKeep);
}
static SampleClass Filter(SampleClass item) {
if (!ShouldKeep(item)) return null;
return new SampleClass {
Id = item.Id
, Name = item.Name
, Type = item.Type
, Items = item.Items.Where(ShouldKeep).Select(x=>Filter(x)).ToList()
};
}
The above code assumes that Items of leaves are empty lists, rather than nulls.

Categories

Resources