grokky
grokky

Reputation: 9295

Eager loading related entities in EF Core using EF6 syntax

In EF6 we used to load related entities like this:

query                                                   // (A)
  .Include(q => q.Employee.Supervisor.Office.Address)   
  .Include(q => q.Orders);

That would eager load all the entities in that chain.

In EF Core, you're supposed to do this:

query                                                   // (B)
  .Include(q => q.Employee)
    .ThenInclude(q => q.Supervisor)
      .ThenInclude(q => q.Office)
        .ThenInclude(q => q.Address)
  .Include(q => q.Orders);

which is much (much!) less friendly, but works.

Our codebase has tons of (A) calls, which we must upgrade to (B) so we can use EF Core.

I've found that in many cases, EF Core works perfectly fine with (A) even though it shouldn't! ...and sometimes it fails completely.

Is that a fluke? Is it supposed to work with (A) under certain circumstances? Because unless I have to, I don't feel like making many hundreds of changes, and then testing them.

Upvotes: 4

Views: 698

Answers (1)

Ivan Stoev
Ivan Stoev

Reputation: 205889

Actually both syntaxes work in EF Core as far as the include path contains simple reference type (i.e. non collection type) navigation properties. It's because the "fluent" version of Include in both EF6 and EF Core is modeling a type safe way of describing a navigation property path like Root -> Employee -> Supervisor -> Office -> Address, which unsafe version is represented as string (also supported by both EF6 and EF Core) "Employee.Supervisor.Office.Address". EF Core examples seem to prefer always using the Include / ThenInclude pattern because it's more universal and works well for both reference and collection type properties as we will see later.

The real difference comes when describing related properties of a collection type navigation properties. Let assume the Order class from your example has a navigation property ICollction<OrderDetail> OrderDetails and OrderDetail class contains Vendor Vendor property. The string navigation path for including root + orders + order details + order detail vendors is "Orders.OrderDetails.Vendor", but it cannot be simply represented as Include(q => q.Orders.OrderDetails.Vendor) expression (compile error). Here the EF6 and EF Core take a different approach. EF6 is resolving it using standard LINQ Select operator:

.Include(q => q.Orders.Select(o => o.OrderDetails.Select(d => d.Vendor)))

and EF Core - with ThenInclude custom extension method:

.Include(q => q.Orders).ThenInclude(o => o.OrderDetails).ThenInclude(d => d.Vendor)

I can't say which one is better - both have a pros and cons. EF6 produces nesting, but allows external code to provide include expressions (of form Expression<Func<T, object>>) without referencing the EF related assemblies. From the other side EF Core syntax is "flat" and allows easy chaining additional properties w/o carrying if they are collection or reference, but there is no easy way to provide includes externally.

But the main point is - better or not, the syntax for including a collection element related property is different and has to be considered when porting EF6 code to EF Core. Actually in some initial EF Core versions the old syntax was supported (additionally to the new syntax), but for some unknown reason it has been removed. The only good thing is that you don't need to modify all your Includes - just find the ones using Select inside and convert them to ThenInclude syntax.

Upvotes: 3

Related Questions