Do obiektu jednostki nie można odwoływać się przez wiele wystąpień IEntityChangeTracker. podczas dodawania powiązanych obiektów do jednostki w Entity Framework 4.1

165

Próbuję zapisać dane pracownika, które mają odniesienia do miasta. Ale za każdym razem, gdy próbuję zapisać kontakt, który jest sprawdzany, pojawia się wyjątek „ADO.Net Entity Framework Do obiektu jednostki nie można odwoływać się w wielu wystąpieniach IEntityChangeTracker”

Przeczytałem tak wiele postów, ale nadal nie mam pojęcia, co robić ... mój kod kliknięcia przycisku Zapisz znajduje się poniżej

protected void Button1_Click(object sender, EventArgs e)
    {
        EmployeeService es = new EmployeeService();
        CityService cs = new CityService();

        DateTime dt = new DateTime(2008, 12, 12);
        Payroll.Entities.Employee e1 = new Payroll.Entities.Employee();

        Payroll.Entities.City city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));

        e1.Name = "Archana";
        e1.Title = "aaaa";
        e1.BirthDate = dt;
        e1.Gender = "F";
        e1.HireDate = dt;
        e1.MaritalStatus = "M";
        e1.City = city1;        

        es.AddEmpoyee(e1,city1);
    }

i Kodeks obsługi pracowników

public string AddEmpoyee(Payroll.Entities.Employee e1, Payroll.Entities.City c1)
        {
            Payroll_DAO1 payrollDAO = new Payroll_DAO1();
            payrollDAO.AddToEmployee(e1);  //Here I am getting Error..
            payrollDAO.SaveChanges();
            return "SUCCESS";
        }
Smily
źródło

Odpowiedzi:

241

Ponieważ te dwie linie ...

EmployeeService es = new EmployeeService();
CityService cs = new CityService();

... nie bierz parametru w konstruktorze, myślę, że tworzysz kontekst wewnątrz klas. Po załadowaniu city1...

Payroll.Entities.City city1 = cs.SelectCity(...);

... dołączasz city1do kontekstu w CityService. Później dodajesz city1jako odniesienie do nowego Employee e1i dodajesz e1 to odniesienie docity1 kontekstu w EmployeeService. W rezultacie maszcity1 dołączyłeś do dwóch różnych kontekstów, na które skarży się wyjątek.

Możesz to naprawić, tworząc kontekst poza klasami usług oraz wstrzykując i używając go w obu usługach:

EmployeeService es = new EmployeeService(context);
CityService cs = new CityService(context); // same context instance

Twoje klasy usług wyglądają trochę jak repozytoria, które są odpowiedzialne tylko za jeden typ jednostki. W takim przypadku zawsze będziesz mieć problemy, gdy tylko relacje między podmiotami będą zaangażowane, gdy będziesz używać oddzielnych kontekstów dla usług.

Możesz również utworzyć pojedynczą usługę, która jest odpowiedzialna za zestaw blisko powiązanych jednostek, takich jak EmployeeCityService(która ma pojedynczy kontekst) i delegować całą operację w Button1_Clickmetodzie do metody tej usługi.

Slauma
źródło
4
Podoba mi się sposób, w jaki to rozgryzłeś, nawet jeśli odpowiedź nie zawierała pewnych podstawowych informacji.
Daniel Kmak,
Wygląda na to, że to rozwiąże mój problem, po prostu nie mam pojęcia, jak napisać nową instancję kontekstu :(
Ortund,
12
Abstrakcja ORM jest jak nakładanie żółtej szminki na łajno.
Ronnie Overby
Mogę czegoś tutaj brakować, ale w niektórych ORMach (zwłaszcza EntityFramework) kontekst danych powinien być zawsze krótki. Wprowadzenie kontekstu statycznego lub ponownie wykorzystanego wprowadzi cały zestaw wyzwań i problemów.
Maritim
@Maritim to zależy od użytkowania. W aplikacjach internetowych jest to zwykle jedna podróż w obie strony. W aplikacjach komputerowych możesz również użyć jednego na Form(co zawsze reprezentuje tylko jedną jednostkę pracy) na Thread(ponieważ DbContextnie ma gwarancji, że będzie bezpieczny wątkowo).
LuckyLikey
30

Kroki do reprodukcji można uprościć do tego:

var contextOne = new EntityContext();
var contextTwo = new EntityContext();

var user = contextOne.Users.FirstOrDefault();

var group = new Group();
group.User = user;

contextTwo.Groups.Add(group);
contextTwo.SaveChanges();

Kod bez błędów:

var context = new EntityContext();

var user = context.Users.FirstOrDefault();

var group = new Group();
group.User = user; // Be careful when you set entity properties. 
// Be sure that all objects came from the same context

context.Groups.Add(group);
context.SaveChanges();

Użycie tylko jednego EntityContextmoże rozwiązać ten problem. Zapoznaj się z innymi odpowiedziami, aby uzyskać inne rozwiązania.

Pavel Shkleinik
źródło
2
powiedzmy, że chciałeś użyć contextTwo? (może z powodu problemów z zasięgiem lub coś w tym rodzaju) jak odrywasz się od contextOne i dołączasz do contextTwo?
NullVoxPopuli
Jeśli musisz coś takiego zrobić, najprawdopodobniej robisz to w niewłaściwy sposób ... Proponuję użyć jednego kontekstu.
Pavel Shkleinik
3
Istnieją przypadki, w których chciałbyś użyć innej instancji, na przykład wskazując inną bazę danych.
Jay
1
Jest to pomocne uproszczenie problemu; ale nie daje prawdziwej odpowiedzi.
BrainSlugs83
9

To jest stary wątek, ale innym rozwiązaniem, które wolę, jest po prostu zaktualizowanie cityId i nie przypisywanie modelu otworu City do Employee ... aby to zrobić Pracownik powinien wyglądać następująco:

public class Employee{
    ...
    public int? CityId; //The ? is for allow City nullable
    public virtual City City;
}

Wtedy wystarczy przypisać:

e1.CityId=city1.ID;
user3484623
źródło
5

Alternatywnie do wtrysku i jeszcze gorzej Singletona możesz zadzwonić do Detach metodę przed Add.

EntityFramework 6: ((IObjectContextAdapter)cs).ObjectContext.Detach(city1);

EntityFramework 4: cs.Detach(city1);

Jest jeszcze jeden sposób, na wypadek gdybyś nie potrzebował pierwszego obiektu DBContext. Po prostu opakuj go za pomocą słowa kluczowego:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
Roman O
źródło
1
Użyłem następującego: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'aby odłączyć, a następnie mogłem użyć dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;do aktualizacji. Pracował jak sen
Peter Smith
Tak, Peter. Powinienem wspomnieć o oznaczeniu stanu jako zmodyfikowanego.
Roman O
W logice uruchamiania mojej aplikacji (global.asax) ładowałem listę widżetów ... prostą listę obiektów referencyjnych, które przechowuję w pamięci. Ponieważ robiłem mój kontekst EF wewnątrz instrukcji Using, pomyślałem, że nie będzie problemu później, gdy mój kontroler zajmie się przypisaniem tych obiektów do wykresu biznesowego (hej, ten stary kontekst zniknął, prawda?) - ta odpowiedź mnie uratowała .
bkwdesign
4

Miałem ten sam problem, ale mój problem z rozwiązaniem @ Slauma (chociaż w niektórych przypadkach świetny) polega na tym, że zaleca ono przekazanie kontekstu do usługi, co oznacza, że ​​kontekst jest dostępny z mojego kontrolera. Wymusza również ścisłe powiązanie między moim kontrolerem a warstwami usług.

Używam Dependency Injection, aby wstrzyknąć warstwy usługi / repozytorium do kontrolera i jako takie nie mam dostępu do kontekstu z kontrolera.

Moje rozwiązanie polegało na tym, aby warstwy usług / repozytorium korzystały z tego samego wystąpienia kontekstu - Singleton.

Klasa pojedyncza kontekstu:

Źródła: http://msdn.microsoft.com/en-us/library/ff650316.aspx
i http://csharpindepth.com/Articles/General/Singleton.aspx

public sealed class MyModelDbContextSingleton
{
  private static readonly MyModelDbContext instance = new MyModelDbContext();

  static MyModelDbContextSingleton() { }

  private MyModelDbContextSingleton() { }

  public static MyModelDbContext Instance
  {
    get
    {
      return instance;
    }
  }
}  

Klasa repozytorium:

public class ProjectRepository : IProjectRepository
{
  MyModelDbContext context = MyModelDbContextSingleton.Instance;
  [...]

Istnieją inne rozwiązania, takie jak jednokrotne utworzenie wystąpienia kontekstu i przekazanie go do konstruktorów warstw usługi / repozytorium lub innego, o którym czytałem, który implementuje wzorzec jednostki pracy. Jestem pewien, że jest więcej ...

kmullings
źródło
9
... czy to się nie psuje, gdy tylko spróbujesz użyć wielowątkowości?
CaffGeek
8
Kontekst nie powinien pozostawać otwarty dłużej niż to konieczne, używanie Singletona, aby pozostawać otwarte na zawsze, jest ostatnią rzeczą, którą chcesz robić.
enzi
3
Widziałem dobre implementacje tego na żądanie. Używanie słowa kluczowego Static jest błędne, ale jeśli utworzysz ten wzorzec, aby utworzyć wystąpienie kontekstu na początku żądania i usunąć go na końcu żądania, byłoby to uzasadnione rozwiązanie.
Aidin
1
To naprawdę zła rada. Jeśli używasz DI (nie widzę tutaj dowodów?), Powinieneś pozwolić kontenerowi DI zarządzać okresem istnienia kontekstu i prawdopodobnie powinno to być na żądanie.
Casey,
3
To jest złe. ZŁY. ZŁY. ZŁY. ZŁY. Szczególnie, jeśli jest to aplikacja internetowa, ponieważ obiekty statyczne są współdzielone między wszystkimi wątkami i użytkownikami. Oznacza to, że wielu jednoczesnych użytkowników Twojej witryny będzie nadeptywać kontekst danych, potencjalnie uszkadzając go, zapisując zmiany, których nie zamierzałeś, lub nawet tworząc losowe awarie. DbContexts NIGDY nie powinny być udostępniane między wątkami. Jest też problem, że statyka nigdy nie zostanie zniszczona, więc będzie nadal korzystać z coraz większej ilości pamięci ...
Erik Funkenbusch,
3

W moim przypadku korzystałem z platformy ASP.NET Identity Framework. Użyłem wbudowanej UserManager.FindByNameAsyncmetody, aby pobrać ApplicationUserjednostkę. Następnie próbowałem odwołać się do tej jednostki na nowo utworzonej jednostce na innejDbContext . Spowodowało to wyjątek, który pierwotnie widziałeś.

Rozwiązałem to, tworząc nową ApplicationUserjednostkę z tylko metodą Idfrom the UserManageri odwołując się do tej nowej jednostki.

Justin Skiles
źródło
1

Miałem ten sam problem i mogłem rozwiązać tworzenie nowej instancji obiektu, który próbowałem zaktualizować. Następnie przekazałem ten obiekt do mojego repozytorium.

karolanet333
źródło
Czy możesz pomóc z przykładowym kodem. ? więc będzie jasne, co próbujesz powiedzieć
BJ Patel
1

W tym przypadku okazuje się, że błąd jest bardzo wyraźny: Entity Framework nie może śledzić jednostki przy użyciu wielu wystąpień IEntityChangeTrackerlub zazwyczaj wielu wystąpień DbContext. Rozwiązania są następujące: użyj jednej instancji DbContext; dostęp do wszystkich potrzebnych jednostek za pośrednictwem jednego repozytorium (w zależności od jednego wystąpieniaDbContext ); lub wyłączając śledzenie dla wszystkich jednostek, do których uzyskano dostęp za pośrednictwem repozytorium innego niż to, które zgłasza ten szczególny wyjątek.

Po odwróceniu wzorca sterowania w .Net Core Web API często stwierdzam, że mam kontrolery z zależnościami, takimi jak:

private readonly IMyEntityRepository myEntityRepo; // depends on MyDbContext
private readonly IFooRepository fooRepo; // depends on MyDbContext
private readonly IBarRepository barRepo; // depends on MyDbContext
public MyController(
    IMyEntityRepository myEntityRepo, 
    IFooRepository fooRepo, 
    IBarRepository barRepo)
{
    this.fooRepo = fooRepo;
    this.barRepo = barRepo;
    this.myEntityRepo = myEntityRepo;
}

i sposób użycia

...
myEntity.Foo = await this.fooRepository.GetFoos().SingleOrDefaultAsync(f => f.Id == model.FooId);
if (model.BarId.HasValue)
{
    myEntity.Foo.Bar = await this.barRepository.GetBars().SingleOrDefaultAsync(b => b.Id == model.BarId.Value);
}

...
await this.myEntityRepo.UpdateAsync(myEntity); // this throws an error!

Ponieważ wszystkie trzy repozytoria zależą od różnych DbContextinstancji na żądanie, mam dwie możliwości uniknięcia problemu i utrzymania oddzielnych repozytoriów: zmień iniekcję DbContext, aby utworzyć nową instancję tylko raz na wywołanie:

// services.AddTransient<DbContext, MyDbContext>(); <- one instance per ctor. bad
services.AddScoped<DbContext, MyDbContext>(); // <- one instance per call. good!

lub, jeśli jednostka podrzędna jest używana tylko do odczytu, wyłączenie śledzenia w tej instancji:

myEntity.Foo.Bar = await this.barRepo.GetBars().AsNoTracking().SingleOrDefault(b => b.Id == model.BarId);
Kjata30
źródło
0

Użyj tego samego obiektu DBContext w całej transakcji.

Nalan Madheswaran
źródło
0

Trafiłem na ten sam problem po wdrożeniu IoC dla projektu (ASP.Net MVC EF6.2).

Zwykle inicjowałbym kontekst danych w konstruktorze kontrolera i używałbym tego samego kontekstu do inicjalizacji wszystkich moich repozytoriów.

Jednak użycie IoC do utworzenia instancji repozytoriów spowodowało, że wszystkie miały oddzielne konteksty i zacząłem otrzymywać ten błąd.

Na razie wróciłem do tworzenia nowych repozytoriów ze wspólnym kontekstem, podczas gdy myślę o lepszym sposobie.

Richard Moore
źródło
0

Tak właśnie napotkałem ten problem. Najpierw muszę zapisać mój, Orderktóry wymaga odniesienia do mojej ApplicationUsertabeli:

  ApplicationUser user = new ApplicationUser();
  user = UserManager.FindById(User.Identity.GetUserId());

  Order entOrder = new Order();
  entOrder.ApplicationUser = user; //I need this user before saving to my database using EF

Problem polega na tym, że inicjuję nowy ApplicationDbContext, aby zapisać moją nową Orderjednostkę:

 ApplicationDbContext db = new ApplicationDbContext();
 db.Entry(entOrder).State = EntityState.Added;
 db.SaveChanges();

Aby rozwiązać problem, użyłem tego samego ApplicationDbContext zamiast korzystania z wbudowanego UserManagera w ASP.NET MVC.

Zamiast tego:

user = UserManager.FindById(User.Identity.GetUserId());

Użyłem mojej istniejącej instancji ApplicationDbContext:

//db instance here is the same instance as my db on my code above.
user = db.Users.Find(User.Identity.GetUserId()); 
Willy David Jr
źródło
-2

Źródło błędu:

ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.Name);
ApplicationDbContext db = new ApplicationDbContent();
db.Users.Uploads.Add(new MyUpload{FileName="newfile.png"});
await db.SavechangesAsync();/ZZZZZZZ

Mam nadzieję, że ktoś zaoszczędzi trochę cennego czasu

Bourne Kolo
źródło
Nie jestem pewien, czy to odpowiada na pytanie. Może jakiś kontekst pomoże.
Stuart Siegler