context.Pupils.Attach(pupil);
The pupil.SchoolclassCodes collection is empty but there must be a schoolclassCode because in the bottom LoadAsync method is the SchoolclassCode with the Id I query here
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).Query().Where(s => s.Id == schoolclassCodeId).LoadAsync();
Fill the pupil with ONE schoolclassCode in the pupil.SchoolclassCodes collection
await context.Entry(pupil).Collection(p => p.SchoolclassCodes).LoadAsync();
Why does the bottom LoadAsync work but not the top LoadAsync?
UPDATE
Sorry for the confusion about the pupil.SchoolclassCodeId which is a [NotMappedAttribute]
This is the relationship.
Pupil N has M SchoolclassCodes.
Each entity has a collection of the other entity.
The last LoadAsync works as I said, there is no problem in the relationship setup.
The problem is the first LoadAsync does NOT worka as described above!
LAZY Loading is completely disabled! I use no virtual props anywhere!
UPDATE 2
public class SchoolclassCode
{
public SchoolclassCode()
{
Pupils = new HashSet<Pupil>();
}
public int Id { get; set; }
public ISet<Pupil> Pupils { get; set; }
}
public class Pupil
{
public Pupil()
{
SchoolclassCodes = new HashSet<SchoolclassCode>();
}
public int Id { get; set; }
public ISet<SchoolclassCode> SchoolclassCodes { get; set; }
[NotMapped]
public int SchoolclassCodeId { get; set; }
}
The field
Pupil.SchoolclassCodeIdis apparently unused for the purpose of this question, so let's forget it.Your second query:
works as expected. We can verify it with the following code:
Suppose the
pupilhas three elements in itsSchoolclassCodes, thenIsLoadedwill betrue, and theforeachloop will show three ids.Then comes your first query:
and let's test it:
Suppose there indeed is a
SchoolclassCodewhichIdis1, theAsyncLoadshould load exactly oneSchoolclassCodeinto memory. Yet in the output you can seeIsLoaded = false, and theforeachgives nothing at all! Why?Well, first the
AsyncLoadis not applied toCollection(p => p.SchoolclassCodes), but anIQueryablederived from it, soIsLoadedshould befalse, this is understandable.But one
SchoolclassCodeis indeed loaded into the context:this
foreachoutputs a single1. So why we can't find thatSchoolclassCodeinpupil.SchoolclassCodes?The answer is: the relationship between
SchoolclassCodeandPupilis many-to-many. In such circumstances Entity Framework does NOT do relationship fixup, i.e. automatically adding aSchoolclassCodetoPupil.SchoolclassCodes, so you'll not see it there. If you really want to fix up the relationship, you'll have to do it manually.Update 1
Quote from MSDN:
It is a little confusing. It seems to be contradicting to my argument, but it's not. Actually, in the above quote the word "load" means "load into the context", not "load into the navigation property", so both MSDN and my answer are correct. To prove my claim, let's begin with a few experiments, then we'll dive into the source code.
The Model
For demonstration purposes, we add another class into the model:
The relationship between
PupilandSchoolclassCodeis many-to-many, as before, and the relationship betweenPupiland the newly addedBookis one-to-many. The context class is:The Data
We have the following entries in the database:
Experiment 1: Direct Load
We load related data directly into the navigation property. For simplicity, we use the
Loadmethod instead ofLoadAsync. They do exactly the same, except that the former is synchronous and the latter is asynchronous. The code:and the output:
The experiment is divided into two parts, one for
Booksand one forSchoolclassCodes. Two contexts are used to make sure the two parts do not interfere with each other. We use the collection'sLoadmethod to load related data directly into the navigation property. The results show that:IsLoadedproperty is set totrue;pupil.Booksandpupil.SchoolclassCodes);context.Books.Localandcontext.SchoolclassCodes.Local).Experiment 2: Partial Load with Query
We load part of the related data using the
Querymethod followed by aWhere:Most of the code is the same as in Experiment 1; please pay attention to the lines beginning with
context.Entry(pupil).... The output:See the difference?
IsLoadedis nowfalsein both cases;SchoolclassCodescase, while it do in theBookscase.The difference is caused by the types of relationship:
Booksis one-to-many, whileSchoolclassCodesis many-to-many. Entity Framework treats those two types differently.Experiment 3: Full Load with Query
So what if we use
Querywithout aWhere? Let's see:The output:
Even though we load all the related data,
IsLoadedis still false, and the loaded data still do not go intoSchoolclassCodes. ApparentlyLoad()is not the same asQuery().Load().Source Code of the Query Method
So what's happening under the hood? The source code of EF6 can be found on CodePlex. The following
Querycall:can be traced to the following code fragment, which I have edited for clarity:
Here
TEntityisBook,_contextis theObjectContextbehind ourDbContext, andsourceQueryis the following Entity SQL statement:After
AddQueryParametersthe parameter@EntityKeyValue1is bound to the value1, which is theIdof thepupil. So the above query is basically the same as:That is, the
Querymethod just constructs a query that retrievesBookswithPupil.IdmatchingIdof the given pupil. It has nothing to do with loading data intopupil.Books. This also holds in the case ofpupil.SchoolclassCodes.Source Code of the Collection's Load Method
Next we check the following method call:
This
Loadcall leads to the following (edited again for clarity):As you can see, it constructs a query, which is exactly the same query we've seen above, then it executes the query and receives the data in
refreshedValues, and finally it merges the data into the navigation property, i.e.pupil.Books.Source Code of the Load Method Following Query
What if we do
LoadafterQuery?This
Loadis defined as an extension method in theQueryableExtensionsclass, and it's quite straightforward:Yes, this time the full source code is shown; I didn't edit anything. And that's right, it is effectively an empty
foreach, looping through all the loaded items and do absolutely nothing with them. Except something has been done: those items are added into the context, and if the relationship is one-to-many, relationship fix-up kicks in and fixes the associations up. This is part of the enumerator's job.One More Experiment: Side Load
In the above we see that the collection's
Querymethod simply constructs an ordinary query (an IQueryable). There are, of course, more than one way to construct such a query. We don't have to begin withcontext.Entry(...).Collection(...). We can begin right from the top:The output:
Exactly the same as in Experiment 3.
Update 2
To delete part of the associations in a many-to-many relationship, the officially recommended way is to
Loadall the related objects first and then remove the associations. For example:This might, of course, load unneeded related objects from the database. If that's undesirable, we can drop down to ObjectContext and use ObjectStateManager:
This way only the relevant related object is loaded. In fact, if we already know the primary key of the related object, even that one can be eliminated:
Note, however, that EF7 will remove ObjectContext, so the above code will have to be modified if we want to migrate to EF7 in the future.