2013-06-13 11 views
8

Próbuję wczytać listę różnych kolorów z poprzednio wczytanej listy produktów na stronie. Tak ciągnąć w produktach zrobić to:Entity Framework AsNoTracking przerywa połączenie z Distinct

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

Potem zrobić niektóre przetwarzania na produkty nich chcę uzyskać listę wszystkich odrębnych kolorów używanych przez produkty, więc mogę to zrobić:

var colors = products 
    .Select(p => p.ProductColor) 
    .Distinct(); 

Działa to świetnie, jednak jeśli dodam połączenie z numerem .AsNoTracking() do oryginalnego wywołania produktów, otrzymuję teraz wpis na mojej liście kolorów dla każdego wpisu na liście produktów.

Dlaczego jest różnica w tych dwóch? Czy istnieje sposób na utrzymanie Entity Framework w śledzeniu obiektów (są one używane tylko do odczytu) i uzyskania pożądanego zachowania?

Oto moja kwerenda po dodaniu wywołanie AsNoTracking()

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

Z tego, co napisałeś .AsNoTracking powinien działać dobrze, gdzie dokładnie umieszczasz go w zapytaniu –

+0

@LukeMcGregor, zaktualizowałem pytanie o moje zapytanie za pomocą '.AsNoTracking' – heavyd

+0

Czy to tylko literówka, zapytanie kończy się na ToList i nie ma w nim Odkrycia? –

Odpowiedz

19

AsNoTracking "przerw" Distinct ponieważ AsNoTracking "przerwy" mapowanie tożsamości. Ponieważ elementy załadowane z AsNoTracking() nie zostaną dołączone do pamięci podręcznej kontekstu EF, materializują nowe elementy dla każdego wiersza zwróconego z zapytania, a gdy włączone jest śledzenie, sprawdzają, czy obiekt o tej samej wartości klucza już istnieje w kontekście i jeśli tak, , nie utworzyłby nowego obiektu i użyłby zamiast tego dołączonej instancji obiektu.

Na przykład, jeśli masz 2 produkty i oba są zielone: ​​

  • Bez AsNoTracking() zapytaniu zostaną zrealizowane 3 obiekty: 2 Product obiektów i 1 ProductColor obiektu (zielony). Produkt 1 ma odniesienie do Green (w ProductColor własności) i produkt 2 ma odniesienie do tej samej instancji obiektu zielonej, czyli

    object.ReferenceEquals(product1.ProductColor, product2.ProductColor) == true 
    
  • Z AsNoTracking() zapytanie zostaną zrealizowane 4 obiekty: 2 obiekty produktu i 2 kolorowe obiekty (obie reprezentują kolor zielony i mają tę samą wartość klucza). Produkt 1 ma odniesienie do Green (w ProductColor własności) i produkt 2 zawiera odniesienie do zielono, ale to kolejna instancja obiektu, tj

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

Teraz, jeśli zadzwonisz Distinct() na zbiór w pamięć (LINQ-to-Objects) domyślnym porównaniem dla parametru Distinct() bez parametru jest porównanie tożsamości referencji obiektu. Tak więc, w przypadku 1 otrzymasz tylko 1 zielony obiekt, ale w przypadku 2 otrzymasz 2 zielone obiekty.

Aby uzyskać pożądany wynik po uruchomieniu zapytania za pomocą AsNoTracking(), konieczne jest porównanie za pomocą klucza encji. Możesz użyć drugiego przeciążenia z Distinct, które przyjmuje jako parametr IEqualityComparer. Przykładem jego implementacji jest here, a do porównania dwóch obiektów użyjesz kluczowej właściwości ProductColor.

Albo - co wydaje się łatwiejsze do mnie niż żmudne IEqualityComparer realizacji - przepisać Distinct() użyciu GroupBy (z kluczem własności jako klucz grupowanie ProductColor):

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

First() w zasadzie oznacza, że ​​jesteś odrzucając wszystkie duplikaty i po prostu zatrzymuję pierwszą instancję obiektu na wartość klucza.