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.SchoolclassCodeId
is 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
pupil
has three elements in itsSchoolclassCodes
, thenIsLoaded
will betrue
, and theforeach
loop will show three ids.Then comes your first query:
and let's test it:
Suppose there indeed is a
SchoolclassCode
whichId
is1
, theAsyncLoad
should load exactly oneSchoolclassCode
into memory. Yet in the output you can seeIsLoaded = false
, and theforeach
gives nothing at all! Why?Well, first the
AsyncLoad
is not applied toCollection(p => p.SchoolclassCodes)
, but anIQueryable
derived from it, soIsLoaded
should befalse
, this is understandable.But one
SchoolclassCode
is indeed loaded into the context:this
foreach
outputs a single1
. So why we can't find thatSchoolclassCode
inpupil.SchoolclassCodes
?The answer is: the relationship between
SchoolclassCode
andPupil
is many-to-many. In such circumstances Entity Framework does NOT do relationship fixup, i.e. automatically adding aSchoolclassCode
toPupil.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
Pupil
andSchoolclassCode
is many-to-many, as before, and the relationship betweenPupil
and the newly addedBook
is 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
Load
method 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
Books
and one forSchoolclassCodes
. Two contexts are used to make sure the two parts do not interfere with each other. We use the collection'sLoad
method to load related data directly into the navigation property. The results show that:IsLoaded
property is set totrue
;pupil.Books
andpupil.SchoolclassCodes
);context.Books.Local
andcontext.SchoolclassCodes.Local
).Experiment 2: Partial Load with Query
We load part of the related data using the
Query
method 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?
IsLoaded
is nowfalse
in both cases;SchoolclassCodes
case, while it do in theBooks
case.The difference is caused by the types of relationship:
Books
is one-to-many, whileSchoolclassCodes
is many-to-many. Entity Framework treats those two types differently.Experiment 3: Full Load with Query
So what if we use
Query
without aWhere
? Let's see:The output:
Even though we load all the related data,
IsLoaded
is 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
Query
call:can be traced to the following code fragment, which I have edited for clarity:
Here
TEntity
isBook
,_context
is theObjectContext
behind ourDbContext
, andsourceQuery
is the following Entity SQL statement:After
AddQueryParameters
the parameter@EntityKeyValue1
is bound to the value1
, which is theId
of thepupil
. So the above query is basically the same as:That is, the
Query
method just constructs a query that retrievesBooks
withPupil.Id
matchingId
of 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
Load
call 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
Load
afterQuery
?This
Load
is defined as an extension method in theQueryableExtensions
class, 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
Query
method 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
Load
all 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.