yesman
yesman

Reputation: 7829

How do you eager load all child collections an entity may have?

I have this model:

public class Person
{
    public string Name { get; set; }
    public Person Parent { get; set; }
    public virtual ICollection<Person> Children { get; set; }
} 

My question: how do I return all children, no matter how deep they are? For example, I could have a Person Alice, who has a child Bob, who in turn has a child Charlie. It's possible that this chain of parents and children goes on for 10 more Persons. Without knowing beforehand how many children Alice has, how do I return all of them in a query? I'm currently doing this:

 using (var ctx = new PersonContext())
{
    var result = ctx.People
          .Include(x => x.Parent)
          .Include(x => x.Children)
          .FirstOrDefault();
    return result;
}

But this doesn't seem to load Charlie. Using ctx.Configuration.ProxyCreationEnabled = false; also does not seem to make a difference.

Upvotes: 1

Views: 1393

Answers (3)

Jcl
Jcl

Reputation: 28272

Loading grand-children is not possible unless you know exactly the amount of children. So you either lazy-load them, or you can explicitly load them recursively... something like:

private void LoadChildren(DbContext context, Person person)
{
  var childCollection = context.Entry(person).Collection(x => x.Children);
  if(!childCollection.IsLoaded)
  {
    childCollection.Load();
    foreach(var child in person.Children)
      LoadChildren(context, child);
  }
}
/* ... */
using (var ctx = new PersonContext())
{
    var result = ctx.People
          .Include(x => x.Parent)
          .FirstOrDefault();
    LoadChildren(ctx, result);
    return result;
}

If you are ok with an approximation (having a "maximum" amount of grand children), I think you should be able to do this using the string format of Include (totally untested though, just posted this for you to experiment):

/* ... */
var numOfGrandChildren = 30; // 30 maximum grandchildren
string childInclude = "";
for(var i = 0; i < numOfChildren; i++)
{
   if(childInclude != "") childInclude+=".";
   childInclude+="Children";
}
using (var ctx = new PersonContext())
{
    var result = ctx.People
          .Include(x => x.Parent)
          .Include(childInclude)
          .FirstOrDefault();
    return result;
}

Upvotes: 3

timothyclifford
timothyclifford

Reputation: 6959

You can disable lazy loading for a navigation property by making it non-virtual: https://msdn.microsoft.com/en-au/data/jj574232.aspx#lazyOffProperty

So your class would be:

public class Person
{
    public string Name { get; set; }
    public Person Parent { get; set; }
    public ICollection<Person> Children { get; set; }
} 

You can disable lazy loading for an entire context through configuration: https://msdn.microsoft.com/en-au/data/jj574232.aspx#lazyOff

public class YourContext : DbContext 
{ 
    public YourContext() 
    { 
        this.Configuration.LazyLoadingEnabled = false; 
    } 
}

Upvotes: 1

Kahbazi
Kahbazi

Reputation: 15005

try this code

first get all people then get the one you want.

using (var ctx = new PersonContext())
{
    var people = ctx.People.ToList();
    var result = People.Where(P=>P.Name == "Alice").FirstOrDefault();
    return result;
}

Upvotes: 0

Related Questions