I'm transfering my .NET Framework (EF6) code to ASP.NET Core (EF Core), and I stumbled upon this issue. Here is some example code:
In EF6 I use Include() and Select() for eager-loading:
return _context.Post
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
PostAuthor is a junction table and there is also a Junction table "AuthorInterest" which I didn't need to involve in EF6 (Select goes straight to a.Interests).
Anyway, I can see that in EF7 this is reworked, meaning that I should use ThenInclude() for nested queries now. However...
return _context.Post
.Include(p => p.PostAuthor)
.ThenInclude(pa => pa.Select(pa2 => pa2.Author))
...etc
The above code fails because of the Select() statement. The documentation on https://docs.efproject.net/en/latest/querying/related-data.html seems to suggest that I don't need it and I can access Author immediately, but I get an ICollection in the last lambda displayed, so I obviously need the Select(). I go through multiple junction tables further on in the query, but for simplicity's sake, let's just focus on the first one.
How do I make this work?
but I get an ICollection in the last lambda displayed, so I obviously need the Select()
No, you don't. EF Core Include / ThenInclude totally replace the need of Select / SelectMany used in EF6. Both they have separate overloads for collection and reference type navigation properties. If you use the overload with collection, ThenInclude operates on the type of the collection element, so at the end you always end up with a single entity type.
In your case, pa should resolve to your junction table element type, so Author should be directly accessible.
For instance the EF6 include chain:
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
translates to EF Core:
.Include(p => p.PostAuthor).ThenInclude(pa => pa.Author).ThenInclude(a => a.Interests)
Related
Consider two tables, table BaseService with PK ID, and table SubService with PK BaseServiceID, which is a foreign key to ID in the BaseService table. I wish to map these to classes in EF6 where SubService inherits from BaseService. I'm not sure how to describe in the mapping that the foreign key is from SubService.BaseServiceID to BaseService.ID. At the moment I have something like this:
modelBuilder.Entity<SubService>(e => {
e.ToTable("SubService");
});
and
modelBuilder.Entity<BaseService>(e => {
e.ToTable("BaseService");
e.HasKey(x => x.ID);
});
When I query though, the resulting query tries to join using BaseService.ID to SubService.ID. I've tried a few variations on my mapping, but I'm getting nowhere - can anyone suggest how this should be done?
From my testing, EF doesn't currently support having different column names for the keys in the tables in a TPT mapping. If you configure one entity to map its "Id" property to a column called "FooId", then all entities in the hierarchy will map their keys to "FooId".
You can create an EF Core Issue to provide feedback on this scenario.
modelBuilder.Entity<SubService>()
.ToTable("SubService")
.HasRequired(s => s.BaseService)
.WithMany(b => b.SubServices)
.HasForeignKey(s => s.BaseServiceID);
In this example, a HasRequired method is used to specify that the entity requires a and the method is used to specify the ability to navigate on the side of the relationship. Finally, `BaseService' property class'SubService' 'BaseService' 'WithMany' 'BaseService' 'HasForeign' 'KeyBase' 'ServiceID' 'SubService'
I'm transfering my .NET Framework (EF6) code to ASP.NET Core (EF Core), and I stumbled upon this issue. Here is some example code:
In EF6 I use Include() and Select() for eager-loading:
return _context.Post
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
PostAuthor is a junction table and there is also a Junction table "AuthorInterest" which I didn't need to involve in EF6 (Select goes straight to a.Interests).
Anyway, I can see that in EF7 this is reworked, meaning that I should use ThenInclude() for nested queries now. However...
return _context.Post
.Include(p => p.PostAuthor)
.ThenInclude(pa => pa.Select(pa2 => pa2.Author))
...etc
The above code fails because of the Select() statement. The documentation on https://docs.efproject.net/en/latest/querying/related-data.html seems to suggest that I don't need it and I can access Author immediately, but I get an ICollection in the last lambda displayed, so I obviously need the Select(). I go through multiple junction tables further on in the query, but for simplicity's sake, let's just focus on the first one.
How do I make this work?
but I get an ICollection in the last lambda displayed, so I obviously need the Select()
No, you don't. EF Core Include / ThenInclude totally replace the need of Select / SelectMany used in EF6. Both they have separate overloads for collection and reference type navigation properties. If you use the overload with collection, ThenInclude operates on the type of the collection element, so at the end you always end up with a single entity type.
In your case, pa should resolve to your junction table element type, so Author should be directly accessible.
For instance the EF6 include chain:
.Include(p => p.PostAuthor.Select(pa => pa.Author).Select(a => a.Interests))
translates to EF Core:
.Include(p => p.PostAuthor).ThenInclude(pa => pa.Author).ThenInclude(a => a.Interests)
I'm new to Fluent API. In my scenario, a Student can be in one Grade and a Grade can have many Students. Then, these two statements accomplish the same thing:
modelBuilder
.Entity<Student>()
.HasRequired<Grade>(s => s.Grade)
.WithMany(s => s.Students);
And:
modelBuilder
.Entity<Grade>()
.HasMany<Student>(s => s.Students)
.WithRequired(s => s.Grade);
My question is - how should I choose one statement over the other? Or do I need both statements?
For bidirectional relationship like yours (i.e. when both ends have navigation properties), it doesn't really matter, you can use one or the another (you can also use both, but it's not recommended because it's redundant and may lead to out of sync between the two).
It really matters when you have unidirectional relationship because only With methods have parameterless overloads.
Imagine you don't have Grade.Students property. Then you can use only:
modelBuilder.Entity<Student>()
.HasRequired(s => s.Grade)
.WithMany();
and if you don't have Student.Grade property, then you can use only:
modelBuilder.Entity<Grade>()
.HasMany(s => s.Students)
.WithRequired();
You just need one.This is more than enough for 1 : M relationship.
modelBuilder.Entity<Student>()
.HasRequired<Grade>(s => s.Grade) //Student entity requires Grade
.WithMany(s => s.Students); //Grade entity includes many Students entities
I have a couple of entities, all inherits base entity with auditing and ID fields. In the configuration for each property I have absolutely same lines like:
this.HasKey(t0 => t0.Id)
.Map(m => m.ToTable("templates"))
.Property(x => x.Id)
.HasColumnName("id")
...................
Is there way to move this code to some kind of "base configuration" to not to write it for each entity?
All you'd need to do is to implement either Table-Per-Type or Table-Per-Hierarchy:
In Table-Per-Type your entities will be split into different tables, but all offshoot tables will have its PK be a FK to the base entity table.
In Table-Per-Hierarchy your entities will all be in one table, but EF will generate a discriminator to discern which object type the entity is actually a part of.
For a clearer example of this, check out the post at this site.
I've been looking at Applying filters when explicitly loading related entities and could not get it to work for a many-to-many relationship.
I created a simple model:
Brief description:
A Student can take many Courses and a Course can have many Students.
A Student can make many Presentation, but a Presentation can be made by only one Student.
So what we have is a many-to-many relationship between Students and Courses, as well as a one-to-many relationship between Student and Presentations.
I've also added one Student, one Course and one Presentation related to each other.
Here is the code I am running:
class Program
{
static void Main()
{
using (var context = new SportsModelContainer())
{
context.Configuration.LazyLoadingEnabled = false;
context.Configuration.ProxyCreationEnabled = false;
Student student = context.Students.Find(1);
context.
Entry(student).
Collection(s => s.Presentations).
Query().
Where(p => p.Id == 1).
Load();
context.
Entry(student).
Collection(s => s.Courses).
Query().
Where(c => c.Id == 1).
Load();
// Trying to run Load without calling Query() first
context.Entry(student).Collection(s => s.Courses).Load();
}
}
}
After loading the presentations I see that the count for Presentations changed from 0 to 1: . However, after doing the same with Courses nothing changes:
So I try to load the courses without calling Query and it works as expected:
(I removed the Where clause to further highlight the point - the last two loading attempts only differ by the "Query()" call)
Now, the only difference I see is that one relationship is one-to-many while the other one is many-to-many. Is this an EF bug, or am I missing something?
And btw, I checked the SQL calls for the last two Course-loading attempts, and they are 100% identical, so it seems that it's EF that fails to populate the collection.
I could reproduce exactly the behaviour you describe. What I got working is this:
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Include(c => c.Students)
.Where(c => c.Id == 1)
.Load();
I don't know why we should be forced also to load the other side of the many-to-many relationship (Include(...)) when we only want to load one collection. For me it feels indeed like a bug unless I missed some hidden reason for this requirement which is documented somewhere or not.
Edit
Another result: Your original query (without Include) ...
context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.Load();
... actually loads the courses into the DbContext as ...
var localCollection = context.Courses.Local;
... shows. The course with Id 1 is indeed in this collection which means: loaded into the context. But it's not in the child collection of the student object.
Edit 2
Perhaps it is not a bug.
First of all: We are using here two different versions of Load:
DbCollectionEntry<TEntity, TElement>.Load()
Intellisense says:
Loads the collection of entities from
the database. Note that entities that
already exist in the context are not
overwritten with values from the
database.
For the other version (extension method of IQueryable) ...
DbExtensions.Load(this IQueryable source);
... Intellisense says:
Enumerates the query such that for
server queries such as those of
System.Data.Entity.DbSet,
System.Data.Objects.ObjectSet,
System.Data.Objects.ObjectQuery,
and others the results of the query
will be loaded into the associated
System.Data.Entity.DbContext,
System.Data.Objects.ObjectContext or
other cache on the client. This is
equivalent to calling ToList and then
throwing away the list without the
overhead of actually creating the
list.
So, in this version it is not guaranteed that the child collection is populated, only that the objects are loaded into the context.
The question remains: Why gets the Presentations collection populated but not the Courses collection. And I think the answer is: Because of Relationship Span.
Relationship Span is a feature in EF which fixes automatically relationships between objects which are in the context or which are just loaded into the context. But this doesn't happen for all types of relationships. It happens only if the multiplicity is 0 or 1 on one end.
In our example it means: When we load the Presentations into the context (by our filtered explicit query), EF also loads the foreign key of the Presentation entites to the Student entity - "transparently", which means, no matter if the FK is exposed as property in the model of not. This loaded FK allows EF to recognize that the loaded Presentations belong to the Student entity which is already in the context.
But this is not the case for the Courses collection. A course does not have a foreign key to the Student entity. There is the many-to-many join-table in between. So, when we load the Courses EF does not recognize that those courses belong to the Student which is in the context, and therefore doesn't fix the navigation collection in the Student entity.
EF does this automatic fixup only for references (not collections) for performance reasons:
To fix relationship, EF transparently
rewrites the query to bring
relationship info for all relations
which has multiplicity of 0..1 or1 on
the other end; in other words
navigation properties that are entity
reference. If an entity has
relationship with multiplicity of
greater then 1, EF will not bring back
the relationship info because it could
be performance hit and as compared to
bringing a single foreign along with
rest of the record. Bringing
relationship info means retrieving all
the foreign keys the records has.
Quote from page 128 of Zeeshan Hirani's in depth guide to EF.
It is based on EF 4 and ObjectContext but I think this is still valid in EF 4.1 as DbContext is mainly a wrapper around ObjectContext.
Unfortunately rather complex stuff to keep in mind when using Load.
And another Edit
So, what can we do when we want to explicitely load one filtered side of a many-to-many relationship? Perhaps only this:
student.Courses = context.Entry(student)
.Collection(s => s.Courses)
.Query()
.Where(c => c.Id == 1)
.ToList();