Efcore: Rule-based eager load (Include) (load patterns/LoadWith)

Created on 28 Aug 2015  路  18Comments  路  Source: dotnet/efcore

Proposal here: https://github.com/dotnet/efcore/issues/2953#issuecomment-555272371
First part of proposal has been split off in https://github.com/dotnet/efcore/issues/21540 and implemented in 5.0 release.


Currently, in no version of EF, do we have a way of specifying, that whenever we ask for a specific entity, we also want some related entities loaded, too, having to use .Include() in every select. This increases verbosity and the amount of code required, when it could be avoided. I understand that it was never feasible to consider for EF < 7, but I feel should be considered now, to bring the library more in line functionality-wise to another popular ORM.

I've been fiddling with annotations a little to try to see if I can get it to work, but haven't spent a lot of time on it, maybe there's a better way? Either way, for syntax, I propose adding to ReferenceCollectionBuilder method Include(bool lazy = true) (this would follow path where a convention that automatically includes all lazy properties of type, but on individual entities you could re-enable lazy.

area-query needs-design punted-for-2.0 type-enhancement

Most helpful comment

@MHDuke Since we now have a design it's possible this will be done this year.

All 18 comments

This is somewhat covered by https://github.com/aspnet/EntityFramework/issues/1985 but it is also a smaller feature within that larger one. Leaving active and moving to backlog since we won't do this for the initial release but it would be good to enable.

Clearing the milestone as this would be nice for owned entities

@anpete just to do the convention part of this for owned entity types (i.e. those that are known to be part of an aggregate)

Note: when this is implemented, consider whether the API created could be used with ad-hoc Includes as described in #4490, and/or whether the need for a an API like that described in #4490 is something that is still needed.

See also #12238

We needed to do this in order to mimic LINQ to SQL's global LoadWith<> behaviour. We code-gened the includes, including when querying through a base type. Awesome work on including "Include on derived types" team!

Clearing the milestone for triage and linked to two issues that are asking for this in a roundabout way by trying to get the behavior through owned types. See #14145 #14113

I鈥檒l post my workaround to this soon. Note that it requires wrapping the DbSet with a custom queryable.

Reading with all the comments, we mainly need 2 things

  • Ability to set navigation as always eager loaded at model level.

    • OP asked for that to avoid verbosity of include when querying.

    • Most people use Owned entity for this behavior incorrectly.

    • Also opt out behavior to ignore include.

      It should be doable by creating a fluent API to configure INavigation.IsEagerLoaded property.

  • LoadWith API which existed in Linq to SQL

    • It used DataLoadOptions class which collects different lambdas (essentially navigations to be loadded).

    • DataLoadOptions was set to given context. We could just set it to query perhaps. (DbContext level is also fine).

    • We don't need to allow it at model building level. Instead of creating that graph for model, API from first part should be used.

1st of above would provide most value. We can also consider 2nd option alongside.

Some clarification:

  • LoadWith will support all Include features (like navigations on derived types and filters)
  • LoadWith will be additive to the includes already in the query
  • Eager loading opt out and LoadWith will not affect navigations to owned entity types
  • LoadWith only affects the query sent to the database, if we allow configurable fix-up it will be orthogonal

@AndriySvyryd - Clarification on clarification. Does LoadWith need to do special processing about derived types? Since it is taking lambdas only, user can start with lambda parameter of derived type directly rather than using casting.

  • Can you clarify what would be override exactly? Model level eager loading is about loading it eagerly. LoadWith also specify navigations to load. So they both would be additive only rather than overriding.

  • LoadWith won't affect owned entities due to additive nature.

Does LoadWith need to do special processing about derived types? Since it is taking lambdas only, user can start with lambda parameter of derived type directly rather than using casting.

Yes, we can say that's how derived types are supported.

Can you clarify what would be override exactly? Model level eager loading is about loading it eagerly. LoadWith also specify navigations to load. So they both would be additive only rather than overriding.

I meant that you can use the eager loading opt-out to disregard the non-owned eager loaded navigations, then add some of them back using LoadWith.

hello everyone, hope everybody is having 'some' good time. holidays come and go, but spoilers are always there :smile:

so, is there any chance we can have this feature in the very near future? I don't wanna add additional repository tier just to have the necessary Include()s. :cry:

@MHDuke Since we now have a design it's possible this will be done this year.

@AndriySvyryd just to double check, this will allow us to define loading rules at model level i.e. something like this:

protected override void OnModelCreating(ModelBuilder builder)
{
    builder.Entity<Order>().LoadWith(o => o.LineItems);
}

And will also allow us to opt-out at query level i.e.

var orderWithoutLineItems = context.Orders.IgnoreLoadWith().SingleOrDefault(o => o.Id == OrderId);

In this case the IgnoreLoadWith will only opt-out the LineItems navigation property while still eager load all (eventually) configured owned entities

@ilmax Something like that

We're running into this limitation frequently as we upgrade our codebase to use generics. We should be able to delegate the loading behavior to the concrete type at runtime. For example:

```c#
public abstract class PurchaseOrderBase
{
public abstract decimal CalculateTotal();
}

public class FixedPricePurchaseOrder() : PurchaseOrderBase
{
public override decimal CalculateTotal()
{
return 50;
}
}

public class ProratedPurchaseOrder() : PurchaseOrderBase
{
private ProrationData _prorationData;

public override decimal CalculateTotal()
{
    return _prorationData.CalculateProration();
}

}

Currently, we do the following:
```c#

GetPurchaseOrder<IEnumerable<T>>() where T : PurchaseOrderBase
{
    return _context.Set<PurchaseOrderBase>()
    .Include("_prorationData").ToList();
}

But it would be ideal to map it in the EF configuration. We can't use owned types because the proration data isn't a value object but we want it to be eagerly loaded by default.

This really hampers our ability to make use of generics. Is there a better workaround for this than just Include with strings in the meantime?

Splitting off first part of https://github.com/dotnet/efcore/issues/2953#issuecomment-555272371 which is model based configuration. Filed #21540 which is happening in 5.0.
LoadWith API and similar pattern are still in backlog and we consider it in our release planning for next release.

Was this page helpful?
0 / 5 - 0 ratings