Using the result of ThenInclude() rather than starting at Include() again. Entity Framework Core 5

2.1k Views Asked by At

I am pretty new to EF Core, so if this question seems obvious, go easy on me :)

As the title mentions, I would like a way to simplify queries in EF core, so that I don't have to reset at the top of my query with the Include() method.

Here is a sample of what I'm dealing with/trying to get rid of.

var result = await _dbContext.Zoo
                    .AsNoTracking()
                    .Include(zoo => zoo.Animal)
                        .ThenInclude(animal => animal.Cow)
                    .Include(zoo => zoo.Animal)
                        .ThenInclude(animal.Dog)
                    .Include(zoo => zoo.Animal)
                        .ThenInclude(animal.Goat)
                    .FirstOrDefaultAsync(zoo => zoo.Name = "Cool Pets");

This works, and based on the question answer provided by Poke (where I got the inspiration for the example above), this is the way that this type of query needs to be handled.

From another question answered by Serge, a way to counter this is to use strings and other ways like so:

dbcontext.a.Include(x => x.b)
            .ThenInclude(x => x.c)
             .Include("b.d")

-------------------------
OR
-------------------------

dbcontext.a.Include(x => x.b)
            .ThenInclude(x => x.c)
             .Include(x => x.b.d)

So my question is, which approach to this type of query, if either, is best for readability and efficiency?

1

There are 1 best solutions below

1
Ivan Stoev On BEST ANSWER

First of all, you don't need to be inspired by SO answers since all this is very well documented in the Eager Loading of Related Data section of the official EF Core documentation, and in particular with lot of examples in Including multiple levels subsection (even though the string overload which was added later is not included there).

But to answer your concrete questions.

Using the result of ThenInclude() rather than starting at Include() again

This is not possible, because "restarting" the include path this way is by design, and in general is the way Include -> ThenInclude -> ThenInclude ... pattern works

As the title mentions, I would like a way to simplify queries in EF core, so that I don't have to reset at the top of my query with the Include() method

and

So my question is, which approach to this type of query, if either, is best for readability and efficiency?

Readability is subjective, so it's up to you. Efficiency - it doesn't matter (thus no need of "simplification" or "optimization") because all these methods are equivalent and produce one and the same query and result(if something needs optimization, it is how many things you include, not how you include them in the query result).

All you need to realize is that

(1) all these methods are alternative ways of expressing include paths. ThenInclude is specifically for solving C# syntax problem with including property of an element of a collection, i.e. while you can write Include(a => a.B.C) in case B is reference type navigation, you can't write Include(a => a.Bs.C) where Bs is collection of B having property C. In that case you could use the string overload Include("Bs.C"), but the problem with strings in general is that they are error prone and you loose type safety provided by expressions. "Classic" Entity Framework (EF6) solves the collection include syntax problem with standard LINQ Select, i.e. Include(a => a.Bs.Select(b => b.C)), however it looks a bit unnatural, hence EF Core introduced the "more intuitive" ThenInclude.

(2) Only unique paths are included in the generated query. In other words, repeating Include(zoo => zoo.Animal) doesn't mean it will be included many times. No, it will be included only once. Same for sub paths you have to repeat in order to ThenInclude another property.

(3) Including a path automatically includes all the levels, i.e.

Include(a => a.B).ThenInclude(b => b.C).ThenInclude(c => c.D)

and

Include(a => a.B.C.D)

do one and the same.


With all that being said, which one method to use for particular scenario is simply a matter of personal taste. I personally use the compact expression form where possible (basically until you hit a collection), ThenInclude otherwise, and avoid string overload due to the obvious maintenance (not functional or efficiency) drawbacks, even though the later can eventually be solved with interpolated strings and nameof operators.