Das Entitätsobjekt kann nicht von mehreren Instanzen von IEntityChangeTracker referenziert werden. beim Hinzufügen verwandter Objekte zur Entität in Entity Framework 4.1

165

Ich versuche, Mitarbeiterdetails zu speichern, die Verweise auf City enthalten. Aber jedes Mal, wenn ich versuche, meinen validierten Kontakt zu speichern, wird die Ausnahme "ADO.Net Entity Framework Ein Entitätsobjekt kann nicht von mehreren Instanzen von IEntityChangeTracker referenziert werden" angezeigt.

Ich hatte so viele Beiträge gelesen, bekam aber immer noch keine genaue Vorstellung davon, was zu tun ist ... mein Klickcode für die Schaltfläche Speichern ist unten angegeben

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);
    }

und Employeeservice Code

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";
        }
Lächelnd
quelle

Antworten:

241

Weil diese beiden Zeilen ...

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

... nimm keinen Parameter im Konstruktor, ich denke du erstellst einen Kontext innerhalb der Klassen. Wenn Sie die city1...

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

... Sie hängen das city1an den Kontext in CityService. Später fügen Sie ein city1als Referenz zum neuen hinzu Employee e1und fügen hinzue1 einschließlich dieses Verweisescity1 auf den Kontext in hinzu EmployeeService. Infolgedessen haben Sie city1zwei verschiedene Kontexte angehängt, über die sich die Ausnahme beschwert.

Sie können dies beheben, indem Sie einen Kontext außerhalb der Serviceklassen erstellen und ihn in beide Services einfügen und verwenden:

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

Ihre Serviceklassen sehen ein bisschen wie Repositorys aus, die nur für einen einzelnen Entitätstyp verantwortlich sind. In einem solchen Fall haben Sie immer Probleme, sobald Beziehungen zwischen Entitäten beteiligt sind, wenn Sie separate Kontexte für die Dienste verwenden.

Sie können auch einen einzelnen Dienst erstellen, der für eine Reihe eng verwandter Entitäten verantwortlich ist, z. B. einen EmployeeCityService(der einen einzelnen Kontext hat), und den gesamten Vorgang in Ihrer Button1_ClickMethode an eine Methode dieses Dienstes delegieren .

Slauma
quelle
4
Ich mag die Art und Weise, wie Sie es herausgefunden haben, auch wenn die Antwort keine Hintergrundinformationen enthielt.
Daniel Kmak
Sieht so aus, als würde dies mein Problem lösen. Ich habe nur keine Ahnung, wie ich die neue Kontextinstanz schreiben soll :(
Ortund
12
ORM zu abstrahieren ist wie gelben Lippenstift auf einen Trottel zu geben.
Ronnie Overby
Möglicherweise fehlt hier etwas, aber in einigen ORMs (insbesondere EntityFramework) sollte der Datenkontext immer kurzlebig sein. Die Einführung eines statischen oder wiederverwendeten Kontexts führt zu einer Reihe weiterer Herausforderungen und Probleme.
Maritim
@ Maritim es kommt auf die Verwendung an. In Webanwendungen ist dies normalerweise eine Hin- und Rückfahrt. In Desktop-Anwendungen können Sie auch eine pro Form(was jedoch nur eine Arbeitseinheit darstellt) pro verwenden Thread(da DbContextnicht garantiert wird, dass sie threadsicher sind).
LuckyLikey
30

Die Schritte zur Reproduktion können folgendermaßen vereinfacht werden:

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();

Code ohne Fehler:

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();

Mit nur einem EntityContextkann dies gelöst werden. Weitere Lösungen finden Sie in anderen Antworten.

Pavel Shkleinik
quelle
2
Nehmen wir an, Sie wollten contextTwo verwenden? (möglicherweise aufgrund von Umfangsproblemen oder Ähnlichem) Wie können Sie sich von contextOne trennen und an contextTwo anhängen?
NullVoxPopuli
Wenn Sie so etwas tun müssen, tun Sie dies höchstwahrscheinlich falsch ... Ich schlage vor, einen Kontext zu verwenden.
Pavel Shkleinik
3
Es gibt Fälle, in denen Sie eine andere Instanz verwenden möchten, z. B. wenn Sie auf eine andere Datenbank verweisen.
Jay
1
Dies ist eine hilfreiche Vereinfachung des Problems. aber es gibt keine wirkliche Antwort.
BrainSlugs83
9

Dies ist ein alter Thread, aber eine andere Lösung, die ich bevorzuge, besteht darin, nur die cityId zu aktualisieren und das Lochmodell City nicht dem Mitarbeiter zuzuweisen. Dazu sollte der Mitarbeiter folgendermaßen aussehen:

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

Dann ist es genug zuzuweisen:

e1.CityId=city1.ID;
user3484623
quelle
5

Alternativ zur Injektion und noch schlimmer zu Singleton können Sie die Detach- Methode vor dem Hinzufügen aufrufen .

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

EntityFramework 4: cs.Detach(city1);

Es gibt noch einen anderen Weg, falls Sie kein erstes DBContext-Objekt benötigen. Wickeln Sie es einfach mit mit Stichwort:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
Roman O.
quelle
1
Ich habe Folgendes verwendet: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'zum Trennen und konnte dann dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;zum Aktualisieren verwenden. Arbeitete wie ein Traum
Peter Smith
Ja, Peter. Ich sollte erwähnen, dass State als modifiziert markiert wird.
Roman O
In meiner Anwendungsstartlogik (global.asax) habe ich eine Liste von Widgets geladen. Eine einfache Liste von Referenzobjekten, die ich im Speicher gespeichert habe. Da ich meinen EF-Kontext in Using-Anweisungen erstellt habe, dachte ich, dass es später kein Problem mehr geben würde, wenn mein Controller diese Objekte einem Geschäftsdiagramm zuordnen würde (hey, dieser alte Kontext ist weg, oder?) - diese Antwort hat mich gerettet .
bkwdesign
4

Ich hatte das gleiche Problem, aber mein Problem mit der @ Slauma-Lösung (obwohl in bestimmten Fällen großartig) ist, dass empfohlen wird, den Kontext an den Dienst zu übergeben, was impliziert, dass der Kontext von meinem Controller verfügbar ist. Es erzwingt auch eine enge Kopplung zwischen meinem Controller und den Serviceschichten.

Ich verwende Dependency Injection, um die Service- / Repository-Ebenen in den Controller zu injizieren, und habe daher vom Controller aus keinen Zugriff auf den Kontext.

Meine Lösung bestand darin, dass die Service- / Repository-Schichten dieselbe Instanz des Kontexts verwenden - Singleton.

Kontext Singleton Klasse:

Referenz: http://msdn.microsoft.com/en-us/library/ff650316.aspx
und 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;
    }
  }
}  

Repository-Klasse:

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

Es gibt andere Lösungen, z. B. das einmalige Instanziieren des Kontexts und das Übergeben an die Konstruktoren Ihrer Service- / Repository-Schichten oder eine andere, von der ich gelesen habe, dass sie das Unit of Work-Muster implementiert. Ich bin sicher, es gibt noch mehr ...

kmullings
quelle
9
... bricht das nicht zusammen, sobald Sie versuchen, Multithreading zu verwenden?
CaffGeek
8
Ein Kontext sollte nicht länger als nötig geöffnet bleiben. Die Verwendung eines Singleton, um ihn für immer offen zu halten, ist das allerletzte, was Sie tun möchten.
Enzi
3
Ich habe gute Implementierungen davon pro Anfrage gesehen. Die Verwendung des Schlüsselworts "Statisch" ist falsch. Wenn Sie jedoch dieses Muster erstellen, um den Kontext am Anfang der Anforderung zu instanziieren und am Ende der Anforderung zu entsorgen, ist dies eine legitime Lösung.
Aidin
1
Das ist wirklich ein schlechter Rat. Wenn Sie DI verwenden (ich sehe die Beweise hier nicht?), Sollten Sie Ihren DI-Container die Kontextlebensdauer verwalten lassen, und dies sollte wahrscheinlich pro Anforderung erfolgen.
Casey
3
Das ist schlecht. SCHLECHT. SCHLECHT. SCHLECHT. SCHLECHT. Insbesondere, wenn es sich um eine Webanwendung handelt, da statische Objekte von allen Threads und Benutzern gemeinsam genutzt werden. Dies bedeutet, dass mehrere gleichzeitige Benutzer Ihrer Website auf Ihren Datenkontext stampfen, ihn möglicherweise beschädigen, Änderungen speichern, die Sie nicht beabsichtigt haben, oder sogar nur zufällige Abstürze verursachen. DbContexts sollten NIEMALS für mehrere Threads freigegeben werden. Dann gibt es das Problem, dass die Statik niemals zerstört wird, so dass sie immer mehr Speicher benötigt ...
Erik Funkenbusch
3

In meinem Fall habe ich das ASP.NET Identity Framework verwendet. Ich hatte die eingebaute UserManager.FindByNameAsyncMethode verwendet, um eine ApplicationUserEntität abzurufen . Ich habe dann versucht, diese Entität auf eine neu erstellte Entität auf eine andere zu verweisenDbContext . Dies führte zu der Ausnahme, die Sie ursprünglich gesehen haben.

Ich habe dieses Problem gelöst, indem ich eine neue ApplicationUserEntität nur mit Idder UserManagerMethode from erstellt und auf diese neue Entität verwiesen habe.

Justin Skiles
quelle
1

Ich hatte das gleiche Problem und konnte eine neue Instanz des Objekts erstellen, das ich aktualisieren wollte. Dann habe ich dieses Objekt an mein Reposotory übergeben.

karolanet333
quelle
Können Sie bitte mit Beispielcode helfen. ? so wird es klar sein, was Sie sagen wollen
BJ Patel
1

In diesem Fall stellt sich heraus, dass der Fehler sehr klar ist: Entity Framework kann eine Entität nicht mit mehreren Instanzen von IEntityChangeTrackeroder normalerweise mehreren Instanzen von verfolgen DbContext. Die Lösungen sind: Verwenden Sie eine Instanz von DbContext; Zugriff auf alle benötigten Entitäten über ein einziges Repository (abhängig von einer Instanz vonDbContext ); oder Deaktivieren der Nachverfolgung für alle Entitäten, auf die über ein anderes Repository als dasjenige zugegriffen wird, das diese bestimmte Ausnahme auslöst.

Wenn ich einer Umkehrung des Steuerungsmusters in der .Net Core-Web-API folge, stelle ich häufig fest, dass ich Controller mit Abhängigkeiten habe, wie z.

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;
}

und Verwendung wie

...
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!

Da alle drei Repositorys von unterschiedlichen DbContextInstanzen pro Anforderung abhängen , habe ich zwei Möglichkeiten, um das Problem zu vermeiden und separate Repositorys zu verwalten: Ändern Sie die Injektion des DbContext, um eine neue Instanz nur einmal pro Aufruf zu erstellen:

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

Wenn die untergeordnete Entität schreibgeschützt verwendet wird, deaktivieren Sie die Nachverfolgung für diese Instanz:

myEntity.Foo.Bar = await this.barRepo.GetBars().AsNoTracking().SingleOrDefault(b => b.Id == model.BarId);
Kjata30
quelle
0

Verwenden Sie während der gesamten Transaktion dasselbe DBContext-Objekt.

Nalan Madheswaran
quelle
0

Ich habe das gleiche Problem nach der Implementierung von IoC für ein Projekt (ASP.Net MVC EF6.2) festgestellt.

Normalerweise initialisiere ich einen Datenkontext im Konstruktor eines Controllers und verwende denselben Kontext, um alle meine Repositorys zu initialisieren.

Die Verwendung von IoC zum Instanziieren der Repositorys führte jedoch dazu, dass alle unterschiedliche Kontexte hatten, und ich bekam diesen Fehler.

Im Moment habe ich nur die Repositories mit einem gemeinsamen Kontext neu gestaltet, während ich mir einen besseren Weg überlege.

Richard Moore
quelle
0

So bin ich auf dieses Problem gestoßen. Zuerst muss ich meine speichern, Orderdie einen Verweis auf meine ApplicationUserTabelle benötigt:

  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

Das Problem ist, dass ich einen neuen ApplicationDbContext initialisiere, um meine neue OrderEntität zu speichern :

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

Um das Problem zu lösen, habe ich denselben ApplicationDbContext verwendet, anstatt den integrierten UserManager von ASP.NET MVC zu verwenden.

An Stelle von:

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

Ich habe meine vorhandene ApplicationDbContext-Instanz verwendet:

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

Fehlerquelle:

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

Hoffe, jemand spart wertvolle Zeit

Bourne Kolo
quelle
Ich bin mir nicht sicher, ob dies die Frage beantwortet. Vielleicht würde ein gewisser Kontext helfen.
Stuart Siegler