EF 4.1 loading filtered child collections not working for many-to-many

I looked at Using Filters to Explicitly Load Related Objects and couldn't get it to work for a many-to-many relationship.

I created a simple model: Model

Short description:
A Student can take many Courses , and Course can have many Students .
A Student can do a lot of Presentation , but a Presentation can only be done by one Student .
So, we have a many-to-many relationship between Students and Courses , as well as a one-to-many relationship between Student and Presentations .

I also added one Student , one Course and one Presentation , related to each other.

Here is the code I'm 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 counter for Presentations has changed from 0 to 1: After loading presentations . However, after doing the same with Courses nothing changes: After attempting to load courses

Therefore, I am trying to load courses without calling Query and it works as expected: Courses loaded

(I deleted the Where clause to further highlight the point - the last two download attempts differ only by calling "Query ()"

Now the only difference that I see is that one connection is one to many, and the other is to many. Is this an EF error, or am I missing something?

And btw, I checked the SQL calls for the last two attempts at Course -loading, and they are 100% identical, so it seems that EF is not populating the collection.

+13
entity-framework
May 26 '11 at 16:34
source share
1 answer

I could reproduce exactly the behavior that you are describing. What I got is:

 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 also be forced to load the other side of the many-to-many relationship ( Include(...) ) when we want to load only one collection. For me, it really seems like an error if I did not miss the hidden reason for this requirement, which is registered somewhere or not.

Edit

Another result: your original request (without inclusion) ...

 context.Entry(student) .Collection(s => s.Courses) .Query() .Where(c => c.Id == 1) .Load(); 

... actually loads the courses into a DbContext like ...

 var localCollection = context.Courses.Local; 

... shows. A course with Id 1 is indeed in this collection, which means: loading into context. But this is not in the child collection of the student object.

Edit 2

Perhaps this is not a mistake.

First of all: we use two different versions of Load :

 DbCollectionEntry<TEntity, TElement>.Load() 

Intellisense says:

Loads a collection of objects from the database. Note that objects that already exist in the context are not overwritten with values โ€‹โ€‹from the database.

For another version ( IQueryable extension IQueryable ) ...

 DbExtensions.Load(this IQueryable source); 

... Intellisense says:

Enumerates the query so that for server queries, such as System.Data.Entity.DbSet, System.Data.Objects.ObjectSet, System.Data.Objects.ObjectQuery, and other query results will be loaded into the associated System.Data.Entity. DbContext, System.Data.Objects.ObjectContext or another cache on the client. This is equivalent to calling ToList, and then dropping the list without the overhead of the list.

So, in this version it is not guaranteed that the child collection is populated only so that the objects are loaded into the context.

The question remains: why does it receive the Presentations collection, and not the Courses collection. And I think the answer is this: because of Relationship> .

A relationship sector is a function in EF that automatically captures relationships between objects that are in the context or that simply load into the context. But this does not happen for all types of relationships. This only happens if the multiplicity is 0 or 1 at one end.

In our example, this means: when we load Presentations into a context (according to our filtered explicit request), EF also loads the Presentation entites foreign key into the Student object - transparently, which means that regardless of whether the FK is set as a property in no model. This loaded FK allows EF to recognize that the loaded Presentations belongs to the Student entity, which is already in context.

But this is not the case for the Courses collection. The course does not have a foreign key for the Student object. Between them there is a many, many, connection table. Thus, when loading Courses EF does not recognize that these courses belong to Student , which is in context, and therefore does not commit the navigation collection to the Student object.

EF does this automatic fix only for links (not collections) for performance reasons:

To fix the relationship, EF transparently rewrites the request so that the relationship information for all relations that has a multiplicity of 0..1 or1 on the other end; in other words, the navigation properties, which are the Help entity. If an enterprise has a relationship with a multiplicity greater than 1, EF will not return the relationship information because it can be successful in performance and compared to involving one foreigner along with the rest of the record. Bringing relationship information means getting all the foreign keys that have been written.

Quote from page 128 of Zeeshan Hirani in the EF detailed guide .

It is based on EF 4 and ObjectContext, but I think it still works in EF 4.1, since DbContext is basically a wrapper around ObjectContext.

Unfortunately, this is a rather complex material to consider when using Load .

And another Edit

So, what can we do when we want to explicitly load one filtered side of the many-to-many relationship? Perhaps only this:

 student.Courses = context.Entry(student) .Collection(s => s.Courses) .Query() .Where(c => c.Id == 1) .ToList(); 
+18
May 26 '11 at 17:01
source share
โ€” -



All Articles