2013-06-13 11 views
8

Sto provando a caricare un elenco di colori distinti dall'elenco di prodotti precedentemente caricato in una pagina. Quindi, per tirare nei prodotti che faccio questo:Entity Framework AsNoTracking interrompe la chiamata a Distinct

var products = Products 
    .Include(p => p.ProductColor) 
    .ToList(); 

Poi faccio un po 'di trasformazione dei prodotti li voglio ottenere un elenco di tutti i colori distinti utilizzati dai prodotti, in modo faccio questo:

E questo funziona benissimo, tuttavia se aggiungo una chiamata allo .AsNoTracking() alla chiamata dei prodotti originali, ora ottengo una voce nella mia lista colori per ogni voce nell'elenco prodotti.

Perché c'è una differenza in questi due? Esiste un modo per impedire a Entity Framework di tracciare gli oggetti (vengono utilizzati per sola lettura) e ottenere il comportamento desiderato?

Ecco la mia domanda dopo aver aggiunto la chiamata a AsNoTracking()

var products = Products 
    .AsNoTracking() 
    .Include(p => p.ProductColor) 
    .ToList(); 
+0

Da ciò che hai postato .AsNoTracking dovrebbe funzionare bene, dove esattamente lo stai inserendo nella tua query –

+0

@LukeMcGregor, ho aggiornato la domanda con la mia query con '.AsNoTracking' – heavyd

+0

È solo un errore di battitura che la query termina con ToList e non vi è alcuna distinzione in esso? –

risposta

19

AsNoTracking "rompe" Distinct perché AsNoTracking mappatura identità "pause". Poiché le entità caricate con AsNoTracking() non vengono allegate alla cache di contesto EF materializza nuove entità per ogni riga restituita dalla query mentre quando il rilevamento è abilitato verificherebbe se un'entità con lo stesso valore chiave esiste già nel contesto e se sì , non creerebbe un nuovo oggetto e userà invece l'istanza dell'oggetto allegata.

Ad esempio, se si dispone di 2 prodotti ed entrambi sono di colore verde:

  • Senza AsNoTracking() la query si concretizzerà 3 oggetti: 2 Product oggetti e 1 ProductColor oggetto (verde). Prodotto 1 ha un riferimento a verde (in ProductColor proprietà) e prodotto 2 ha un riferimento alla stessa istanza oggetto verde, vale a dire

    object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == true 
    
  • Con AsNoTracking() la query si concretizzerà 4 oggetti: 2 oggetti prodotti e 2 oggetti colorati (entrambi rappresentano il verde e hanno lo stesso valore chiave). Prodotto 1 ha un riferimento a verde (in ProductColor proprietà) e prodotto 2 ha un riferimento a verde, ma questo è un'altra istanza oggetto, cioè

    object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == false 
    

Ora, se si chiama Distinct() su una collezione in memoria (LINQ-to-Objects) il confronto predefinito per Distinct() senza parametro sta confrontando le identità di riferimento dell'oggetto. Quindi, nel caso 1 ottieni solo 1 oggetto verde, ma nel caso 2 otterrai 2 oggetti verdi.

Per ottenere il risultato desiderato dopo aver eseguito la query con AsNoTracking() è necessario un confronto tramite la chiave dell'entità. È possibile utilizzare il secondo sovraccarico di Distinct che accetta come parametro un IEqualityComparer. Un esempio per la sua implementazione è here e si utilizzerà la proprietà chiave di ProductColor per confrontare due oggetti.

Or - che sembra più facile per me che il noioso IEqualityComparer realizzazione - si riscrive la Distinct() utilizzando un GroupBy (con la proprietà ProductColor chiave come chiave di raggruppamento):

var colors = products 
    .Select(p => p.ProductColor) 
    .GroupBy(pc => pc.ProductColorId) 
    .Select(g => g.First()); 

Il First() fondamentalmente significa che si è buttare via tutti i duplicati e mantenere solo la prima istanza dell'oggetto per valore chiave.