This an "epic" issue for the theme of automatic aggregate behaviors in the model based on ownership. Specific pieces of work will be tracked by linked issues.
AsNoTracking (#17836)When you get an entity from the database and you also want to get a "child entity", you need to pass that "child object" as an expression in order to also get it from the database (when not using lazy loading).
public class Parent
{
public Child Child { get; set;}
}
public class Child
{
}
var dataContext = new DataContext();
var parents = dataContext.Parents.Include(p => p.Child).ToList();
Would it be possible to add an new attribute that will be read by the datacontext and add the include statements automatic?
public class Parent
{
[Include]
public Child Child { get; set;}
}
public class Child
{
}
var dataContext = new DataContext();
var parents = dataContext.Parents.ToList(); // Child entity is also read from the database
@kennytordeur this is a very dangerous feature. In a large application someone would "think" they always want to load B & C together with A. However in other parts of the code this creates issues due to the fact that it creates overly complex queries (perf) or loads more than necessary (attach/detach scenarios over the wire/web services) or breaks some logic IE: someone checks if B is not loaded (== null) then loads B and B1+B2 etc (an entire graph) after this change application is broken because B is loaded but not B1+B2.
Also there are alternative solutions to this, like:
A repository pattern:
List<Parent> GetParents(){return datacontext.Parents.Include(p => p.Child);}
Which is also more flexible because it allows you to customize the query with more than just includes.
@kennytordeur Your request maps to some of our thinking around being able to declaratively define the shapes of aggregates in an EF model. We don't know exactly how this feature would look like or the timeframe in which we will try to tackle it. In the meantime I will move your suggestion to our backlog, since it seems to provide one way to define an aggregate that we could consider.
While I agree with @popcatalin81 that in many applications it is dangerous to assume that aggregates are fixed and always need to be loaded together, I also believe that many applications can take advantage of this behavior and be constrained to it. After all, loading full aggregates only is the primary behavior you get when you use a document database. Of course if the database is relational, the overhead of loading from multiple tables is higher, but at least in EF Core the queries that we would generate are much simpler than in EF6.
As @popcatalin81 mentioned, writing repository methods that execute queries with calls to Include can address the requirement of loading fixed shapes of graphs without sacrificing the fine grained control. However a more declarative way of specifying the shape of the aggregate would allow for other automatic behavior, e.g.:
cc @rowanmiller in case he knows if we already have something covering aggregates in the backlog (I couldn't find it) and to suggest whether we should have it as a separate item to this.
I thought we had something... but I can't find it... so we should just keep this one open to track it :smile:
Thanks. I moved it to backlog and changed the title to make it more general about aggregates.
Thanks. It would be nice to define fetch strategies for aggregates.
I have worked around this issue using extension methods that include a defined subset of the children I want to load.
public static IQueryable<Adult> IncludeAll (this IQueryable<Adult> query)
{
return query.Include(f => f.Relationships).ThenInclude(f => f.Child)
.Include(f => f.CreatedBy)
.Include(f => f.ModifiedBy);
}
I really, really liked the idea implemented in this package for EF6. Basically it's using expressions to define what is part of the aggregate and what is referencing other aggregates of the model, e.g.:
context.UpdateGraph(order, map => map
.OwnedCollection(ord => ord.LineItems)
.AssociatedItem(ord => ord.Customer));
Unfortunately the project is a) abandoned and b) not ported to EF Core. A package inspired by this exists, but it seems to be in an early unstable phase.
Just because an entity's property is an aggregate part of the entity, doesn't mean you _always_ want to include it. But _sometimes_ you do. Instead of [Include], consider introducing [Part] to indicate a part-whole relationship. After appropriately dispersing [Part] throughout your model, you query like this:
var parents = dataContext.Parents.IncludeParts().ToList();
This recursively loads parts and parts of parts, etc..
Also consider adding a RequiresFilter property to [Part] like this:
public class PartAttribute : Attribute
{
public bool RequiresFilter { get; set; }
}
Setting this property to true indicates that a filter must be specified for the property as part of a query with IncludeParts, or else an exception is thrown upon loading.
Note for triage: @bricelam called out that most of what is described in this issue is either implemented already (owned entities, [Owned] attributes) or captured elsewhere:
I would like to propose that we close this and make sure additional issues exist for other aggregate goodness, for example:
Consider key configuration--see #12078
Note: consider client-only cascade delete for aggregates, which should be fully loaded and hence won't have issues with some entities needing to be deleted in the store. However, also consider that an aggregate cannot have true cycles, and hence the SQL Server limitation might not be as bad. See #12168
See also #13036
See also scenario in #14031
See also scenarios in #14113 and #14145
See also #13890
Note for triage: @bricelam called out that most of what is described in this issue is either implemented already (owned entities,
[Owned]attributes) or captured elsewhere:
As I see it, the problem with suggesting owned entities in place of automatic Includes is that the limitations of owned entities make value objects in separate tables problematic.
How would you get around having to Include a linked department entity (to get department name) with every query with owned entities to a separate table while still supporting adding / deleting of department entities separately (e.g. needing a DBSet<Department> for a separate ASP.Net Core scaffold).
Also, there is the limitation "Instances of owned entity types cannot be shared by multiple owners" that makes sharing e.g. department among users and conference rooms (?) problematic.
@NetMage Automatic include is tracked by #2953
Most helpful comment
Just because an entity's property is an aggregate part of the entity, doesn't mean you _always_ want to include it. But _sometimes_ you do. Instead of
[Include], consider introducing[Part]to indicate a part-whole relationship. After appropriately dispersing[Part]throughout your model, you query like this:This recursively loads parts and parts of parts, etc..
Also consider adding a
RequiresFilterproperty to[Part]like this:Setting this property to true indicates that a filter must be specified for the property as part of a query with
IncludeParts, or else an exception is thrown upon loading.