Entity-Framework C #: come posso combinare un .Find e .Include su un oggetto modello?


145

Sto facendo il tutorial di pratica mvcmusicstore. Ho notato qualcosa durante la creazione dello scaffold per il gestore album (aggiungi elimina modifica).

Voglio scrivere codice elegantemente, quindi sto cercando il modo pulito per scrivere questo.

Cordiali saluti, sto rendendo il negozio più generico:

Album = Articoli

Generi = Categorie

Artista = Marchio

Ecco come viene recuperato l'indice (generato da MVC):

var items = db.Items.Include(i => i.Category).Include(i => i.Brand);

Ecco come viene recuperato l'elemento per l'eliminazione:

Item item = db.Items.Find(id);

Il primo riporta tutti gli articoli e popola la categoria e i modelli di marca all'interno del modello di articoli. Il secondo, non popola la categoria e il marchio.

Come posso scrivere il secondo per fare la ricerca E popolare ciò che è dentro (preferibilmente in 1 riga) ... teoricamente - qualcosa del tipo:

Item item = db.Items.Find(id).Include(i => i.Category).Include(i => i.Brand);

Se qualcuno ha bisogno di fare questo genericamente in.net-core vedi la mia risposta
johnny 5

Risposte:


162

È necessario utilizzare Include()prima, quindi recuperare un singolo oggetto dalla query risultante:

Item item = db.Items
              .Include(i => i.Category)
              .Include(i => i.Brand)
              .SingleOrDefault(x => x.ItemId == id);

24
Consiglio vivamente di usare quest'ultimo (SingleOrDefault), ToList recupererà prima tutte le voci e poi ne selezionerà una
Sander Rijken

5
Ciò si interrompe se disponiamo di una chiave primaria composita e stiamo utilizzando il relativo sovraccarico di ricerca.
jhappoldt,

78
Funzionerebbe, ma c'è una differenza tra l'uso di "Trova" e "SingleOrDefault". Il metodo "Trova" restituisce l'oggetto dall'archivio tracciato locale, se esiste, evitando un round trip al database, dove l'utilizzo di "SingleOrDefault" forza comunque una query nel database.
Iravanchi,

3
@Iravanchi è corretto. Questo potrebbe aver funzionato per l'utente, ma l'operazione e i suoi effetti collaterali non sono equivalenti a Trova, per quanto ne so.
mwilson,

3
In realtà non risponde alla domanda di operazioni in quanto non utilizza. Trova
Paul Swetz

73

La risposta di Dennis sta usando Includee SingleOrDefault. Quest'ultimo fa un giro circolare nel database.

Un'alternativa è utilizzare Find, in combinazione con Load, per il caricamento esplicito di entità correlate ...

Di seguito un esempio MSDN :

using (var context = new BloggingContext()) 
{ 
  var post = context.Posts.Find(2); 

  // Load the blog related to a given post 
  context.Entry(post).Reference(p => p.Blog).Load(); 

  // Load the blog related to a given post using a string  
  context.Entry(post).Reference("Blog").Load(); 

  var blog = context.Blogs.Find(1); 

  // Load the posts related to a given blog 
  context.Entry(blog).Collection(p => p.Posts).Load(); 

  // Load the posts related to a given blog  
  // using a string to specify the relationship 
  context.Entry(blog).Collection("Posts").Load(); 
}

Naturalmente, Findrestituisce immediatamente senza effettuare una richiesta al negozio, se tale entità è già caricata dal contesto.


30
Questo metodo utilizza Findquindi se l'entità è presente, non vi è alcun round trip per il DB per l'entità stessa. MA, avrai un viaggio di andata e ritorno per ogni relazione che stai Loadintraprendendo, mentre la SingleOrDefaultcombinazione con Includecarica tutto in una volta sola.
Iravanchi,

Quando ho confrontato il 2 nel profiler SQL, Trova / Carica era migliore per il mio caso (avevo una relazione 1: 1). @Iravanchi: vuoi dire se avessi avuto una relazione 1: m avrebbe chiamato m volte il negozio? ... perché non avrebbe molto senso.
Principiante

3
Non relazione 1: m, ma relazioni multiple. Ogni volta che si chiama la Loadfunzione, la relazione deve essere popolata al ritorno della chiamata. Quindi, se chiamate Loadpiù volte per più relazioni, ogni volta ci sarà un viaggio di andata e ritorno. Anche per una singola relazione, se il Findmetodo non trova l'entità in memoria, effettua due round trip: uno per Finde il secondo per Load. Ma il Include. SingleOrDefaultapproccio recupera l'entità e la relazione in una sola volta per quanto ne so (ma non ne sono sicuro)
Iravanchi

1
Sarebbe stato bello se avessero potuto seguire il design Includi in qualche modo piuttosto che dover trattare collezioni e riferimenti in modo diverso. Ciò rende più difficile creare una facciata GetById () che richiede solo una raccolta facoltativa di Expression <Func <T, oggetto >> (ad esempio _repo.GetById (id, x => x.MyCollection))
Derek Greer

4
Ricordati di menzionare il riferimento del tuo post: msdn.microsoft.com/en-us/data/jj574232.aspx#explicit
Hossein

1

Devi lanciare IQueryable su DbSet

var dbSet = (DbSet<Item>) db.Set<Item>().Include("");

return dbSet.Find(id);


Non c'è .Find o .FindAsync nel dbSet. Questo è EF Core?
Thierry,

c'è ef 6 anche su ef core
Rafael R. Souza,

Ero fiducioso e poi "InvalidCastException"
ZX9

0

Non ha funzionato per me. Ma l'ho risolto facendo così.

var item = db.Items
             .Include(i => i.Category)
             .Include(i => i.Brand)
             .Where(x => x.ItemId == id)
             .First();

Non so se questa è una soluzione ok. Ma l'altro che Dennis mi ha dato mi ha dato un errore bool .SingleOrDefault(x => x.ItemId = id);


4
Anche la soluzione di Dennis deve funzionare. Forse hai questo errore SingleOrDefault(x => x.ItemId = id)solo a causa del singolo sbagliato =invece del doppio ==?
Slauma,

6
sì, sembra che tu abbia usato = non ==. Errore di sintassi;)
Ralph N,

Li ho provati entrambi == e = mi ha ancora dato un errore in .SingleOrDefault (x => x.ItemId = id); = / Deve essere qualcos'altro nel mio codice che è sbagliato. Ma il modo in cui l'ho fatto è un brutto modo? Forse non capisco cosa intendi dire che anche Dennis ha un singel = nel suo codice.
Johan,

0

Non esiste un modo semplice per filtrare con una ricerca. Ma ho trovato un modo vicino per replicare la funzionalità, ma per favore prendi nota di alcune cose per la mia soluzione.

Questa soluzione consente di filtrare genericamente senza conoscere la chiave primaria in .net-core

  1. Trova è fondamentalmente diverso perché ottiene l'entità se è presente nel tracciamento prima di eseguire una query sul database.

  2. Inoltre può filtrare per oggetto in modo che l'utente non debba conoscere la chiave primaria.

  3. Questa soluzione è per EntityFramework Core.

  4. Ciò richiede l'accesso al contesto

Ecco alcuni metodi di estensione da aggiungere che ti aiuteranno a filtrare in base alla chiave primaria

    public static IReadOnlyList<IProperty> GetPrimaryKeyProperties<T>(this DbContext dbContext)
    {
        return dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties;
    }

    //TODO Precompile expression so this doesn't happen everytime
    public static Expression<Func<T, bool>> FilterByPrimaryKeyPredicate<T>(this DbContext dbContext, object[] id)
    {
        var keyProperties = dbContext.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var body = keyProperties
            // e => e.PK[i] == id[i]
            .Select((p, i) => Expression.Equal(
                Expression.Property(parameter, p.Name),
                Expression.Convert(
                    Expression.PropertyOrField(Expression.Constant(new { id = id[i] }), "id"),
                    p.ClrType)))
            .Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }

    public static Expression<Func<T, object[]>> GetPrimaryKeyExpression<T>(this DbContext context)
    {
        var keyProperties = context.GetPrimaryKeyProperties<T>();
        var parameter = Expression.Parameter(typeof(T), "e");
        var keyPropertyAccessExpression = keyProperties.Select((p, i) => Expression.Convert(Expression.Property(parameter, p.Name), typeof(object))).ToArray();
        var selectPrimaryKeyExpressionBody = Expression.NewArrayInit(typeof(object), keyPropertyAccessExpression);

        return Expression.Lambda<Func<T, object[]>>(selectPrimaryKeyExpressionBody, parameter);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this DbSet<TEntity> dbSet, DbContext context, object[] id)
        where TEntity : class
    {
        return FilterByPrimaryKey(dbSet.AsQueryable(), context, id);
    }

    public static IQueryable<TEntity> FilterByPrimaryKey<TEntity>(this IQueryable<TEntity> queryable, DbContext context, object[] id)
        where TEntity : class
    {
        return queryable.Where(context.FilterByPrimaryKeyPredicate<TEntity>(id));
    }

Una volta che hai questi metodi di estensione, puoi filtrare in questo modo:

query.FilterByPrimaryKey(this._context, id);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.