Entity Framework: caricamento esplicito delle entità correlate

3

Utilizzo Entity Framework da alcuni anni. Ho flip-floppato tra richiamare i repository nella mia logica aziendale o utilizzare il caricamento lazy per recuperare i dati mentre lavoro nel mio codice.

Il problema con il primo approccio è che un singolo oggetto business può finire per fare affidamento su molti repository (nel caso del repository per tabella). La logica risultante è strongmente combinata con le chiamate al livello dati, il che rende più difficile testare (un sacco di mock).

Il problema con il secondo approccio è che il caricamento lento porta a volte sorprese. I novizi chiamano il database all'interno di loop senza rendersene conto. Anche l'utilizzo di Include richiede molta cura. Troppi Include s e la query generata può essere molto lenta.

Quindi mi sentivo un po 'frustrato. Qualche settimana fa ho scoperto le nuove classi DbEntityEntry<TEntity> in Entity Framework. Ciò mi ha permesso di caricare esplicitamente le entità correlate e verificare se erano già state caricate. Ciò ha portato ad un approccio ibrido in cui ho passato un singolo oggetto root e una classe "loader". Prima di accedere a una proprietà di navigazione, chiederei al caricatore di caricarlo. Se l'oggetto fosse già caricato, non farebbe nulla. Poiché non costa nulla, l'ho chiamato ogni volta che ho fatto riferimento a una proprietà di navigazione.

Ho aggiunto questa classe a un progetto open source esistente per chiunque voglia vederlo: link .

Un collega ha sottolineato che questa classe ha risolto solo alcuni problemi. Proprio come il caricamento lento, significava utilizzare Include s o gestire più accessi al database all'interno di un ciclo. Poi l'altro giorno ho avuto un'altra idea: potrei prendere le entità correlate per una collezione di entità. Ad esempio, ottenendo tutti i ruoli per un elenco di utenti. In questo modo, posso catturare tutte le entità correlate in un singolo database colpito al di fuori di un ciclo.

Ho finito con questo corso: link . Questa classe crea dinamicamente una query basata sulle entità. Sotto il cofano, costruisce un join che filtra le entità correlate tramite le chiavi primarie delle entità principali.

Entrambe le classi caricatore forniscono metodi che restituiscono un IQueryable<TRelated> per consentire ulteriori filtri, ordinamenti, ecc. Il problema con questi metodi è che è impossibile sapere se le entità correlate sono già state caricate, quindi danno sempre come risultato un colpo di database. Questo è ancora utile per fare più Include s in una singola query.

I miei oggetti business ora accettano solo una fabbrica di caricatori nei loro costruttori. Il metodo chiamato oggetto business chiamato prende solo gli oggetti di livello superiore. Quindi, ho solo una singola dipendenza dal mio livello dati. Questa dipendenza è nascosta dietro un'interfaccia.

Mi chiedo quali sono gli svantaggi di questo approccio. Mescola ancora le chiamate all'oggetto livello dati, in tutto, tranne che ora posso navigare ai risultati dalle proprietà di navigazione dell'oggetto di livello superiore. Inoltre è una singola dipendenza che viene facilmente derisa (tutto diventa un no-op). Può essere utilizzato insieme al caricamento lento, quindi non sarà un disastro se si dimentica di chiamare prima Load . All'inizio ero preoccupato che avrei creato query nel mio livello aziendale, ma mi sono reso conto che stavo solo dicendo Load(u => u.Role) e quindi accedendo immediatamente a user.Role . La classe del caricatore e Entity Framework si occupano di come popolare la proprietà di navigazione.

Mi manca qualcosa? Questo sembra un compromesso davvero impressionante tra il puro caricamento pigro e il caricamento ansioso.

Aggiornamento

In realtà ho spostato questo codice nel proprio repository GitHub e nel pacchetto NuGet: link

    
posta Travis Parks 22.04.2014 - 04:04
fonte

0 risposte