5

Występuje problem z kodem Entity Framework - pierwszy w MVC3. Wybieram ten wyjątek:Entity Framework Code-First: "ObjectStateManager nie może śledzić wielu obiektów za pomocą tego samego klucza."

Obiekt o tym samym kluczu już istnieje w ObjectStateManager. ObjectStateManager nie może śledzić wielu obiektów za pomocą tego samego klucza .

Jest to adresowane wiele razy na SO, ale mam problem z wykorzystaniem któregokolwiek z sugerowanych rozwiązań w mojej sytuacji.

Oto przykładowy kod:

FestORM.SaleMethod method = new FestORM.SaleMethod 
{ 
    Id = 2, 
    Name = "Test Sale Method" 
}; 
FestContext context = new FestContext(); 

//everything works without this line: 
string thisQueryWillMessThingsUp = 
    context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

context.Entry(method).State = System.Data.EntityState.Modified; 
context.SaveChanges(); 

edytowane wyjaśnienia: Ja próbuje zaktualizować obiekt, który już istnieje w bazie danych.

Wszystko działa poprawnie bez zapytania zanotowanego w kodzie. W mojej aplikacji mój kontroler tworzy instancję kontekstu i ten sam kontekst jest przekazywany do kilku repozytoriów, które są używane przez kontroler - więc nie jestem w stanie po prostu użyć innego kontekstu do początkowej operacji zapytania. Próbowałem usunąć obiekt, który nie był śledzony w ObjectStateManager, ale nie mogę się z tym nigdzie dostać. Próbuję znaleźć rozwiązanie, które będzie działać dla obu warunków: czasami będę aktualizować obiekt, który jest śledzony przez ObjectStateManager, a czasem stanie się, że nie został jeszcze śledzony.

FWIW, moi prawdziwi funkcje repozytorium wyglądać tak, jak na powyższym kodzie:

public void Update(T entity) 
{ 
    //works ONLY when entity is not tracked by ObjectStateManager 
    _context.Entry(entity).State = System.Data.EntityState.Modified; 
} 

public void SaveChanges() 
{ 
    _context.SaveChanges(); 
} 

Jakieś pomysły? Walczyłem to zbyt długo ...

Odpowiedz

12

Problemem jest to, że zapytania

string thisQueryWillMessThingsUp = 
    context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

niesie jeden przykład jednostki SaleMethod w kontekście czym ten kod

context.Entry(method).State = System.Data.EntityState.Modified; 

przykłada inny obiekt kontekstu.Obie instancje mają ten sam klucz podstawowy, więc EF myśli, że próbujesz dołączyć dwie różne jednostki z tym samym kluczem do kontekstu. Nie wie, że obaj mają być tą samą istotą.

Jeśli z jakiegoś powodu po prostu trzeba zapytać o imię, ale nie chce, aby rzeczywiście przynieść pełną jednostkę do kontekstu, to można to zrobić:

string thisQueryWillMessThingsUp =   
    context.SaleMethods.Where(m => m.Id == 2).AsNoTracking().Single().Name; 

Jeśli są wiązana zrobić to zaktualizować istniejący podmiot i masz wartości dla wszystkich odwzorowanych właściwości tej jednostki, a następnie najprostszą rzeczą do zrobienia jest, aby nie uruchomić kwerendę i po prostu użyć:

context.Entry(method).State = System.Data.EntityState.Modified; 

Jeśli nie chcesz zaktualizować wszystkie właściwości, prawdopodobnie dlatego, że nie masz wartości dla wszystkich właściwości, a następnie zapytania dla encji i ustawianie jej właściwości przed wywołaniem SaveChanges jest akceptowalnym podejściem. Istnieje kilka sposobów, aby to zrobić w zależności od Twoich konkretnych wymagań. Jednym ze sposobów jest użycie metody Property, coś tak:

var salesMethod = context.SaleMethods.Find(2); // Basically equivalent to your query 
context.Entry(salesMethod).Property(e => e.Name).CurrentValue = newName; 
context.Entry(salesMethod).Property(e => e.SomeOtherProp).CurrentValue = newOtherValue; 
context.SaveChanges(); 

Takie blogi zawierają pewne dodatkowe informacje, które mogą być pomocne:

http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx

http://blogs.msdn.com/b/adonet/archive/2011/01/30/using-dbcontext-in-ef-feature-ctp5-part-5-working-with-property-values.aspx

+0

Dzięki. (Nie pozwól mi zagłosować na twoją odpowiedź, ponieważ jestem nowy.) Problem polega na tym, że nie wiem, kiedy robię zapytanie poprzedzające przyszłą aktualizację. Zapytanie może zostać zakopane gdzieś w warstwie usług, a następnie, gdy przejdę do aktualizacji tego samego obiektu, który był zapytany, trafię w błąd. Czy istnieje sposób, w jaki mogę usunąć obiekt ze śledzenia kontekstowego, lub uzyskać odbicie obiektu śledzonego przez kontekst i przekazać do kontekstu, że rzeczywiście aktualizuję ten jeden przedmiot? Zbadałem niektórych w tym kierunku, ale bez powodzenia. – Josh

+0

To zależy od zamiaru zapytania. Jeśli zapytanie ma na celu przeniesienie encji do kontekstu, aby można było je aktualizować i zapisywać, co jest normalne, należy to zaprojektować, prawdopodobnie upewniając się przed zapisaniem, że podmiot został wniesiony, a jeśli nie już wcześniej sprowadzono. Jeśli intencją zapytania jest uzyskanie pewnej własności obiektu z bazy danych z jakiegoś powodu bez doprowadzenia encji do kontekstu, użyj AsNoTracking w tym zapytaniu, jak pokazano powyżej. –

+0

Aby dodać do poprzedniego komentarza, jeśli chcesz upewnić się, że obiekt został wprowadzony, użyj metody Znajdź przed aktualizacją właściwości w encji i zapisaniem. Możesz również użyć zapytania zamiast Znajdź - domyślnie zapytanie wywoła tylko encję, jeśli nie jest już w kontekście. Ale Find jest bardziej wydajny. –

1

Oczywistą odpowiedzią byłoby, że twój nie faktycznie oszczędność metody obiektu do bazy danych przed wywołaniem:

//everything works without this line: 
string thisQueryWillMessThingsUp = context.SaleMethods.Where(m => m.Id == 2).Single().Name; 

Myślę jednak, że może to jest po prostu kod, który pominąłeś. Co, jeśli sprawisz, że twoje jednostki odziedziczą po klasie abstrakcyjnej, tj.

public abstract class BaseClass 
{ 
    public int Id { get; set; } 
} 

Następnie zaktualizować repozytorium do

public class Repository<T> where T : BaseClass 
{ 
..... 
    public void Update(T entity) 
    {   
     _context.Entry(entity).State = entity.Id == 0 ? System.Data.EntityState.Added : System.Data.EntityState.Modified; 
    } 
} 

Również warto nie ustawić ID swojej SaleMethod i niech to być generowane przez bazę danych. Problem może być również dlatego, że obiekt SaleMethod w bazie danych ma identyfikator 2, a następnie próbujesz dodać kolejny obiekt SaleMethod o identyfikatorze 2. Błąd, który widzisz, wynika z próby dodania kolejnego obiektu SaleMethod o identyfikatorze 2 do ObjectStateManager.

+0

Dzięki za odpowiedź. Tak, identyfikatory są generowane przez bazę danych na pewno. Jest to operacja ** update **. Pominąłem część, w której obiekt został pierwotnie zapisany (mógł zostać początkowo zapisany przez inny fragment kodu lata temu, itp.). Chodzi o to, że aktualizacja działa dobrze, o ile wcześniej nie wykonasz zapytania. Wykonanie zapytania najwyraźniej dodaje go do śledzenia ObjectStateManager, w którym to momencie moja aktualizacja skrzecza. – Josh

Powiązane problemy