Bangyou
Bangyou

Reputation: 9816

Entity Framework - check whether has grandchild records

I am using asp net core 2.0 and entity framework to access a MySQL database. My model structure looks like

public class Parent 
{
   [Key]
   public int Id { get; set; }
   public virtual ICollection<Child> Child { get; set; }
   [NotMapped]
   public virtual bool HasGrandchild  { get; set; }
}

public class Child
{
   [Key]
   public int Id { get; set; }
   public int ParentId { get; set; }
   public virtual ICollection<Grandchild> Grandchild { get; set; }
}

public class Grandchild
{
   [Key]
   public int Id { get; set; }
   public int ChildId { get; set; }
}

I want to check whether there are any records of grandchild in the class Parent. I know I can use Include and ThenInclude to retrieve the child and grandchild. However, the Grandchild is really big. I don't want to return them. Alternatively, I could use for loop to count the number Grandchild. For example,

List<Parent> p_list = _context.Parent.ToList();
foreach(Parent p in p_list) 
{
    List<Child> c_list = _context.Child
        .Where(c => c.ParentId == p.Id)
        .ToList();
    int NumberOfGrandchild = 0;
    foreach (Child c in c_list) 
    {
        List<Grandchild> gc_list = _context.Grandchild
           .Where(gc =>gc.ChildId == c.Id)
           .ToList();
        NumberOfGrandchild  += gc_list.Count();
    }
    p.HasGrandchild = false;
    if (NumberOfGrandchild > 0) {
        p.HasGrandchild = true;
    }
}

The performance of this method is very slow, especially for the big table.

Is there any better method to check whether there are any grandchild records.

Upvotes: 0

Views: 474

Answers (1)

krillgar
krillgar

Reputation: 12815

var parents = _context.Parents
                      .Where(p => p.Children.SelectMany(c => c.Grandchildren).Any());

This should give you what you're looking for. You want to avoid Include as much as possible, because it adds the entire table's columns to the query. (At least it did with <= EF5.)

What's happening here is that you're generating subqueries, the lowest of which will get all Children for the parent, and see if any Child has any Grandchild.

If you want to determine if a Parent has any Grandhildren then you can either move that portion of the Where clause, or just have true in an anonymous object.

var parents = _context.Parents
                      .Select(p => new { // Can be other DTO if you have one
                          Parent = p,
                          HasGrandchildren = p.Children
                                              .SelectMany(c => c.Grandchildren)
                                              .Any()
                      });

or

var parents = _context.Parents
                      .Where(p => p.Children.SelectMany(c => c.Grandchildren).Any())
                      .Select(p => new {
                          Parent = p,
                          HasGrandchildren = true
                      });

The second one probably be a little more performant because it would be able to perform that within a single query. The first might be slightly slower of a query, but it would also allow you to get everything.

There are also other ways you could do that with a GroupBy if you want to split them between those that do have grandchildren, and those that don't.

Upvotes: 2

Related Questions