l'oggetto entità non può essere referenziato da più istanze di IEntityChangeTracker. durante l'aggiunta di oggetti correlati all'entità in Entity Framework 4.1


165

Sto cercando di salvare i dettagli dei dipendenti, che hanno riferimenti con City. Ma ogni volta che provo a salvare il mio contatto, che viene convalidato ottengo l'eccezione "ADO.Net Entity Framework Un oggetto entità non può essere referenziato da più istanze di IEntityChangeTracker"

Avevo letto così tanti post ma non ho ancora avuto l'idea esatta di cosa fare ... il mio codice clic sul pulsante Salva è riportato di seguito

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

e Codice del servizio dipendenti

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

Risposte:


241

Perché queste due linee ...

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

... non prendere un parametro nel costruttore, immagino che tu crei un contesto all'interno delle classi. Quando carichi il city1...

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

... si allega city1al contesto in CityService. Successivamente aggiungi a city1come riferimento al nuovo Employee e1e aggiungi e1 includendo questo riferimento alcity1 contesto in EmployeeService. Di conseguenza, hai city1attaccato due diversi contesti, di cui si lamenta l'eccezione.

È possibile risolvere questo problema creando un contesto esterno alle classi di servizio e iniettando e utilizzandolo in entrambi i servizi:

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

Le classi di servizio assomigliano un po 'ai repository che sono responsabili di un solo tipo di entità. In tal caso, avrai sempre problemi non appena saranno coinvolte le relazioni tra entità quando utilizzi contesti separati per i servizi.

Puoi anche creare un singolo servizio che è responsabile di un insieme di entità strettamente correlate, come un EmployeeCityService(che ha un unico contesto) e delegare l'intera operazione nel tuo Button1_Clickmetodo a un metodo di questo servizio.


4
Mi piace il modo in cui l'hai capito anche se la risposta non includeva alcune informazioni di base.
Daniel Kmak,

Sembra che questo risolverà il mio problema, non ho idea di come scrivere la nuova istanza di contesto :(
Ortund

12
Astrarre ORM è come mettere un rossetto giallo su un prato.
Ronnie Overby,

Potrei mancare qualcosa qui, ma in alcuni ORM (in particolare EntityFramework) il contesto dei dati dovrebbe sempre essere ridotto. L'introduzione di un contesto statico o riutilizzato introdurrà tutta un'altra serie di sfide e problemi.
Maritim,

@Maritim dipende dall'uso. Nelle applicazioni Web, in genere è un roundtrip. Nelle applicazioni desktop, potresti anche usarne uno per Form(qualunque cosa, rappresenta solo un'unità di lavoro) per Thread(perché DbContextnon è garantito che sia thread-safe).
LuckyLikey,

30

I passaggi per riprodurre possono essere semplificati a questo:

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

Codice senza errore:

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

Utilizzando solo uno EntityContextpuò risolvere questo. Fare riferimento ad altre risposte per altre soluzioni.


2
diciamo che volevi usare il contesto Due? (forse a causa di problemi di ambito o qualcosa del genere) come si fa a staccare da ContextOne e collegarsi a contextTwo?
NullVoxPopuli

Se devi fare smth in questo modo, molto probabilmente lo stai facendo nel modo sbagliato ... Suggerisco di usare un contesto.
Pavel Shkleinik,

3
Esistono casi in cui si desidera utilizzare un'istanza diversa, ad esempio quando si punta a un database diverso.
Jay,

1
Questa è un'utile semplificazione del problema; ma non fornisce una risposta reale.
BrainSlugs83

9

Questo è un vecchio thread, ma un'altra soluzione, che preferisco, è semplicemente aggiornare cityId e non assegnare il modello di foro City a Employee ... per farlo, il dipendente dovrebbe apparire come:

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

Quindi è sufficiente assegnare:

e1.CityId=city1.ID;

5

In alternativa all'iniezione e, peggio ancora, a Singleton, puoi chiamare il metodo Detach prima di aggiungere.

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

EntityFramework 4: cs.Detach(city1);

C'è ancora un altro modo, nel caso in cui non sia necessario il primo oggetto DBContext. Basta avvolgerlo usando la parola chiave:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}

1
Ho usato quanto segue: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'per staccare e quindi sono stato in grado di utilizzare dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;per l'aggiornamento. Ha funzionato come un sogno
Peter Smith,

Sì, Peter. Vorrei menzionare per contrassegnare State come modificato.
Roman O

Nella mia logica di avvio dell'applicazione (global.asax) stavo caricando un elenco di widget .. un semplice elenco di oggetti di riferimento che ho nascosto nella memoria. Dal momento che stavo facendo il mio contesto EF all'interno delle dichiarazioni Using, ho pensato che non ci sarebbero stati problemi in seguito quando il mio controller si è dato da fare per assegnare quegli oggetti in un grafico commerciale (ehi, quel vecchio contesto è sparito, giusto?) - questa risposta mi ha salvato .
bkwdesign,

4

Ho avuto lo stesso problema ma il mio problema con la soluzione di @ Slauma (anche se eccezionale in alcuni casi) è che mi consiglia di passare il contesto al servizio, il che implica che il contesto è disponibile dal mio controller. Forza anche l'accoppiamento stretto tra il mio controller e i livelli di servizio.

Sto usando Dependency Injection per iniettare i livelli di servizio / repository nel controller e come tale non hanno accesso al contesto dal controller.

La mia soluzione era che i livelli di servizio / repository usassero la stessa istanza del contesto: Singleton.

Classe Singleton di contesto:

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

Classe del deposito:

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

Esistono altre soluzioni come istanziare il contesto una volta e passarlo ai costruttori dei livelli di servizio / repository o di un altro che ho letto su quale sia l'implementazione del modello Unità di lavoro. Sono sicuro che ce ne sono altri ...


9
... questo non si interrompe non appena si tenta di utilizzare il multithreading?
CaffGeek,

8
Un contesto non dovrebbe rimanere aperto più del necessario, usare un Singleton per tenerlo aperto per sempre è l'ultima cosa che vuoi fare.
Enzi,

3
Ho visto buone implementazioni di questo per richiesta. L'uso della parola chiave statica è errato ma se si crea questo modello per creare un'istanza del contesto all'inizio della richiesta e eliminarlo alla fine della richiesta, sarebbe una soluzione legittima.
Aidin,

1
Questo è davvero un cattivo consiglio. Se stai usando DI (non vedo le prove qui?), Allora dovresti lasciare che il tuo contenitore DI gestisca la durata del contesto e probabilmente dovrebbe essere per richiesta.
Casey,

3
Questo non va bene. MALE. MALE. MALE. MALE. Soprattutto se si tratta di un'applicazione Web, poiché gli oggetti statici sono condivisi tra tutti i thread e gli utenti. Ciò significa che più utenti simultanei del tuo sito web calpesteranno il tuo contesto di dati, potenzialmente corrompendolo, salvando le modifiche che non avevi intenzione di fare o anche solo creando arresti casuali. DbContexts non deve MAI essere condiviso tra thread. Poi c'è il problema che la statica non viene mai distrutta, quindi siederà e continuerà a usare sempre più memoria ...
Erik Funkenbusch,

3

Nel mio caso, stavo usando ASP.NET Identity Framework. Avevo usato il UserManager.FindByNameAsyncmetodo integrato per recuperare ApplicationUserun'entità. Ho quindi cercato di fare riferimento a questa entità su un'entità appena creata su un'altra DbContext. Ciò ha comportato l'eccezione che hai visto inizialmente.

Ho risolto questo creando una nuova ApplicationUserentità con solo Idil UserManagermetodo from e facendo riferimento a quella nuova entità.


1

Ho avuto lo stesso problema e ho potuto risolvere creando una nuova istanza dell'oggetto che stavo cercando di aggiornare. Quindi ho passato quell'oggetto al mio deposito.


Potete per favore aiutarmi con il codice di esempio. ? quindi sarà chiaro cosa stai cercando di dire
BJ Patel

1

In questo caso, si scopre che l'errore è molto chiaro: Entity Framework non è in grado di tenere traccia di un'entità che utilizza più istanze IEntityChangeTrackero in genere più istanze di DbContext. Le soluzioni sono: utilizzare un'istanza di DbContext; accedere a tutte le entità necessarie tramite un unico repository (a seconda di un'istanza di DbContext); o disattivare il tracciamento per tutte le entità a cui si accede tramite un repository diverso da quello che genera questa particolare eccezione.

Quando seguo un'inversione del modello di controllo nell'API Web .Net Core, trovo spesso che ho controller con dipendenze come:

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

e utilizzo come

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

Poiché tutti e tre i repository dipendono da DbContextistanze diverse per richiesta, ho due opzioni per evitare il problema e mantenere repository separati: modificare l'iniezione di DbContext per creare una nuova istanza una sola volta per chiamata:

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

oppure, se l'entità figlio viene utilizzata in sola lettura, disattivando il rilevamento su tale istanza:

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


0

Ho riscontrato lo stesso problema dopo aver implementato l'IoC per un progetto (ASP.Net MVC EF6.2).

Di solito inizializzare un contesto di dati nel costruttore di un controller e utilizzare lo stesso contesto per inizializzare tutti i miei repository.

Tuttavia, l'utilizzo di IoC per creare un'istanza dei repository ha fatto sì che tutti avessero contesti separati e ho iniziato a ricevere questo errore.

Per ora sono tornato a rinnovare i repository con un contesto comune mentre penso a un modo migliore.


0

Ecco come ho riscontrato questo problema. Per prima cosa devo salvare il mio Orderche necessita di un riferimento alla mia ApplicationUsertabella:

  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

Il problema è che sto inizializzando un nuovo ApplicationDbContext per salvare la mia nuova Orderentità:

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

Quindi, per risolvere il problema, ho usato lo stesso ApplicationDbContext invece di utilizzare il UserManager incorporato di ASP.NET MVC.

Invece di questo:

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

Ho usato la mia istanza ApplicationDbContext esistente:

//db instance here is the same instance as my db on my code above.
user = db.Users.Find(User.Identity.GetUserId()); 

-2

Fonte di errore:

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

Spero che qualcuno risparmi tempo prezioso


Non sono sicuro che questo risponda alla domanda. Forse un certo contesto sarebbe d'aiuto.
Stuart Siegler,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.