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?
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.
This is not possible, because "restarting" the include path this way is by design, and in general is the way
Include->ThenInclude->ThenInclude... pattern worksand
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.
ThenIncludeis specifically for solving C# syntax problem with including property of an element of a collection, i.e. while you can writeInclude(a => a.B.C)in caseBis reference type navigation, you can't writeInclude(a => a.Bs.C)whereBsis collection ofBhaving propertyC. In that case you could use thestringoverloadInclude("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 LINQSelect, 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 toThenIncludeanother property.(3) Including a path automatically includes all the levels, i.e.
and
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),
ThenIncludeotherwise, and avoidstringoverload due to the obvious maintenance (not functional or efficiency) drawbacks, even though the later can eventually be solved with interpolated strings andnameofoperators.