EF 4.1 caricamento collezioni filtrate non funzionanti per molti a molti

Ho osservato Applying filters quando caricavo esplicitamente quadro correlate e non riuscivo a farlo funzionare per una relazione many-to-many.

Ho creato un modello semplice: Modello

Breve descrizione:
Uno Student può frequentare molti Courses e un Course può avere molti Students .
Uno Student può fare molte Presentation , ma una Presentation può essere fatta da un solo Student .
Quindi quello che abbiamo è una relazione molti-a-molti tra Students e Courses , così come una relazione uno-a-molti tra Student e Presentations .

Ho anche aggiunto uno Student , un Course e una Presentation correlati tra loro.

Ecco il codice che sto eseguendo:

 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(); } } } 

Dopo aver caricato le presentazioni, vedo che il conteggio delle Presentations cambiato da 0 a 1: Dopo aver caricato le presentazioni . Tuttavia, dopo aver fatto lo stesso con i Courses non cambia nulla: Dopo aver tentato di caricare i percorsi

Così provo a caricare i corsi senza chiamare Query e funziona come previsto: Corsi caricati

(Ho rimosso la clausola Where per evidenziare ulteriormente il punto – gli ultimi due tentativi di caricamento differiscono solo dalla chiamata “Query ()”)

Ora, l’unica differenza che vedo è che una relazione è uno a molti mentre l’altra è molti-a-molti. Si tratta di un bug EF, o mi sto sfuggendo qualcosa?

E btw, ho controllato le chiamate SQL per gli ultimi due tentativi di caricamento del Course , e sono identiche al 100%, quindi sembra che sia EF che non riesce a popolare la raccolta.

Potrei riprodurre esattamente il comportamento che descrivi. Quello che ho ottenuto è questo:

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

Non so perché dovremmo essere costretti a caricare anche l’altro lato della relazione molti-a-molti ( Include(...) ) quando vogliamo caricare solo una raccolta. Per me sembra davvero un baco a meno che non mi sia sfuggito qualche motivo nascosto per questo requisito che è documentato da qualche parte o meno.

modificare

Un altro risultato: la tua query originale (senza Include) …

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

… carica effettivamente i corsi in DbContext come …

 var localCollection = context.Courses.Local; 

… Spettacoli. Il corso con Id 1 è proprio in questa collezione che significa: caricato nel contesto. Ma non è nella raccolta secondaria dell’object studente.

Modifica 2

Forse non è un bug.

Prima di tutto: stiamo usando qui due diverse versioni di Load :

 DbCollectionEntry.Load() 

Intellisense dice:

Carica la raccolta di entity framework dal database. Si noti che le quadro già esistenti nel contesto non vengono sovrascritte con i valori dal database.

Per l’altra versione (metodo di estensione di IQueryable ) …

 DbExtensions.Load(this IQueryable source); 

… Intellisense dice:

Enumera la query in modo tale che per le query del server come quelle di System.Data.Entity.DbSet, System.Data.Objects.ObjectSet, System.Data.Objects.ObjectQuery e altri i risultati della query verranno caricati nel sistema associato .Data.Entity.DbContext, System.Data.Objects.ObjectContext o altra cache sul client. Questo equivale a chiamare ToList e quindi a buttare via la lista senza il sovraccarico di creare effettivamente la lista.

Quindi, in questa versione non è garantito che la collezione figlio sia popolata , solo che gli oggetti siano caricati nel contesto.

La domanda rimane: Perché la raccolta di Presentations popolata ma non la raccolta Courses . E penso che la risposta sia: a causa di Relationship Span .

Relationship Span è una funzione in EF che corregge automaticamente le relazioni tra oggetti che sono nel contesto o che sono appena caricati nel contesto. Ma questo non accade per tutti i tipi di relazioni. Succede solo se la molteplicità è 0 o 1 a un’estremità.

Nel nostro esempio significa: Quando carichiamo le Presentations nel contesto (tramite la nostra query esplicita filtrata), EF carica anche la chiave esterna delle Presentation della Presentation sull’ quadro Student – “in modo trasparente”, il che significa, non importa se l’FK è esposto come proprietà nel modello di no. Questo FK caricato consente a EF di riconoscere che le Presentations caricate appartengono all’entity framework Student già presente nel contesto.

Ma questo non è il caso della collezione Courses . Un corso non ha una chiave esterna per l’ quadro Student . C’è una tabella di join molti-a-molti in mezzo. Quindi, quando carichiamo i Courses EF non riconosce che quei corsi appartengono allo Student che è nel contesto, e quindi non aggiusta la raccolta di navigazione nell’ quadro Student .

EF esegue questa correzione automatica solo per riferimenti (non raccolte) per motivi di prestazioni:

Per correggere la relazione, EF riscrive in modo trasparente la query per portare informazioni sulla relazione per tutte le relazioni che hanno molteplicità di 0..1 o1 dall’altra parte; in altre parole, proprietà di navigazione che sono riferimento all’entity framework. Se un’entity framework ha una relazione con una molteplicità maggiore di 1, EF non riporterà le informazioni sulla relazione perché potrebbe essere un successo in termini di prestazioni e rispetto al portare un singolo straniero insieme al resto del record. Portare informazioni sulla relazione significa recuperare tutte le chiavi estranee che i record hanno.

Citazione dalla pagina 128 della guida approfondita di Zeeshan Hirani a EF .

È basato su EF 4 e ObjectContext ma penso che sia ancora valido in EF 4.1 poiché DbContext è principalmente un wrapper attorno a ObjectContext.

Purtroppo cose piuttosto complesse da tenere a mente quando si usa Load .

E un altro Modifica

Quindi, cosa possiamo fare quando vogliamo caricare in modo esplicito un lato filtrato di una relazione molti-a-molti? Forse solo questo:

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