4

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.

Community
  • 1
  • 1
AnneMz
  • 484
  • 1
  • 6
  • 16
  • Is this for reporting? Anyway, I am sure it cannot be done just by LINQ. – tia May 07 '15 at 15:43
  • @AnneMz, just a reminder since you seem to be new here: select or upvote correct answers. Not doing so can get bad for your future reputation. – Alexandre Severino May 07 '15 at 16:36
  • Thanks for the reminder. Yes I am new. – AnneMz May 07 '15 at 18:24
  • So AnneMz, clarifying your question, I think you meant to say you have 17 questions in programming and 7 are in C#, right? What is that you want to get? You have an Object Category and you want to know how many questions it has inside (including its childs questions), is that exactly what you want? – Dzyann May 07 '15 at 18:51
  • Or you want it to return how many items it has of each Category, like in your Example, Programming 17, C# category 7, Interview 2? – Dzyann May 07 '15 at 18:57

3 Answers3

4

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
Dzyann
  • 5,062
  • 11
  • 63
  • 95
  • Does this work if there are three levels of categories or only two? i.e. something can indirectly be a child category, as Interview Questions is two layers below Programming in my example but still a child of programming. Maybe there's a better way to structure my categories table to make this easier? Top level categories have parentId of null – AnneMz May 07 '15 at 19:46
  • ahh now I see that. This looks pretty good. Let me try it out before I mark it. – AnneMz May 07 '15 at 19:52
  • @AnneMz - It is recursive, so it works for any number of levels, I updated my sample to have 1 more level, now you can see category 5 is a child of category 3. – Dzyann May 07 '15 at 19:54
  • @AnneMz - no problem. As you pointed out you are new to SO, I will let you know that the custom here is to upvote the posts that were useful for you. For instance if kilouco or buffjape posts were helpful you can upvote them also even if they are not exactly what you needed. – Dzyann May 07 '15 at 20:40
  • Thanks Dyzann, I am so new I can't even vote yet LOL Been reading a long time but this is my first post. – AnneMz May 07 '15 at 22:44
  • @AnneMz - haha, np :) – Dzyann May 08 '15 at 13:23
  • This can be achieved heaps easier with one block of linq query: `(from c in Categories from q in c.Questions select q).Count();` – Rhys Stephens Aug 04 '16 at 07:51
0

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);
Alexandre Severino
  • 1,563
  • 1
  • 16
  • 38
  • Thanks I have added a clarification to my question - it seems it wasn't clear that I want to know the total number of questions in a category and it's child categories, not the number of child categories, though that is good to know as well – AnneMz May 07 '15 at 18:26
  • I fixed it so it will count the `questions` now. – Alexandre Severino May 07 '15 at 20:08
0

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));
}
  • I want to know the total number of questions in a category and it's child categories, not the number of child categories, though that is good to know as well. – AnneMz May 07 '15 at 18:25