web-dev-qa-db-fra.com

Les objets entity ne peuvent pas être référencés par plusieurs instances de IEntityChangeTracker. lors de l'ajout d'objets associés à une entité dans Entity Framework 4.1

J'essaie de sauvegarder les détails des employés, qui ont des références avec la ville. Mais chaque fois que j'essaie de sauvegarder mon contact, ce qui est validé, je reçois l'exception "ADO.Net Entity Framework Un objet d'entité ne peut pas être référencé par plusieurs instances de IEntityChangeTracker"

J'avais lu tellement d'articles, mais je ne comprenais toujours pas exactement ce qu'il fallait faire ... le code de clic du bouton Enregistrer est donné ci-dessous

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

et Code de service des employés

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

Parce que ces deux lignes ...

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

... ne prenez pas de paramètre dans le constructeur, je suppose que vous créez un contexte dans les classes. Lorsque vous chargez le city1...

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

... vous attachez le city1 au contexte dans CityService. Plus tard, vous ajoutez un city1 comme référence au nouveau Employeee1 et ajoutez e1en incluant cette référence à city1 contexte dans EmployeeService. En conséquence, vous avez city1 attaché à deux contextes différents, ce à quoi l'exception se plaint.

Vous pouvez résoudre ce problème en créant un contexte extérieur aux classes de service et en l'injectant et en l'utilisant dans les deux services:

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

Vos classes de service ressemblent un peu aux référentiels qui ne sont responsables que d'un seul type d'entité. Dans ce cas, vous rencontrerez toujours des problèmes dès que des relations entre entités sont impliquées lorsque vous utilisez des contextes distincts pour les services.

Vous pouvez également créer un service unique responsable d'un ensemble d'entités étroitement liées, comme un EmployeeCityService (qui a un seul contexte), et déléguer l'ensemble de l'opération de votre méthode Button1_Click à une méthode de ce type. un service.

230
Slauma

Les étapes pour reproduire peuvent être simplifiées à ceci:

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 sans erreur:

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

L'utilisation d'un seul EntityContext peut résoudre ce problème. Reportez-vous à d'autres réponses pour d'autres solutions.

25
Pavel Shkleinik

Ceci est un vieux fil, mais une autre solution, que je préfère, consiste simplement à mettre à jour l'identificateur cityId et à ne pas affecter le modèle de trou Ville à Employé ... pour le faire, l'employé devrait ressembler à ceci:

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

Ensuite, il suffit d'assigner:

e1.CityId=city1.ID;
8
user3484623

J'ai eu le même problème, mais mon problème avec la solution @ Slauma (bien que géniale dans certains cas) est qu'il me recommande de transmettre le contexte au service, ce qui implique que le contexte est disponible depuis mon contrôleur. Cela force également le couplage étroit entre les couches contrôleur et service.

J'utilise Dependency Injection pour injecter les couches de service/référentiel dans le contrôleur et, en tant que tel, n'a pas accès au contexte à partir du contrôleur.

Ma solution consistait à faire en sorte que les couches service/référentiel utilisent la même instance du contexte - Singleton.

Classe Singleton de contexte:

Référence: http://msdn.Microsoft.com/en-us/library/ff650316.aspx
and 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 de référentiel:

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

Il existe d'autres solutions, telles que l'instanciation du contexte une fois et sa transmission aux constructeurs de vos couches de service/référentiel ou à une autre méthode lue implémentant le modèle d'unité de travail. Je suis sûr qu'il y a plus ...

5
kmullings

Alternativement à l'injection et encore pire à Singleton, vous pouvez appeler la méthode Detach avant Add.

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

EntityFramework 4: cs.Detach(city1);

Il existe un autre moyen, au cas où vous n’auriez pas besoin du premier objet DBContext. Enveloppez-le simplement avec le mot-clé sing:

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

Dans mon cas, j'utilisais ASP.NET Identity Framework. J'avais utilisé la méthode intégrée UserManager.FindByNameAsync pour récupérer une entité ApplicationUser. J'ai ensuite essayé de référencer cette entité sur une entité nouvellement créée sur un autre DbContext. Cela a entraîné l'exception que vous avez vue à l'origine.

J'ai résolu ce problème en créant une nouvelle entité ApplicationUser avec uniquement la méthode Id de la méthode UserManager et en référençant cette nouvelle entité.

3
Justin Skiles

Dans ce cas, l'erreur est très claire: Entity Framework ne peut pas suivre une entité à l'aide de plusieurs instances de IEntityChangeTracker ou généralement de plusieurs instances de DbContext. Les solutions sont les suivantes: utilisez une instance de DbContext; accéder à toutes les entités nécessaires via un seul référentiel (selon une instance de DbContext); ou désactiver le suivi pour toutes les entités accessibles via un référentiel autre que celui qui lève cette exception particulière.

En suivant une inversion de modèle de contrôle dans .NET Core Web API, je constate fréquemment que j'ai des contrôleurs avec des dépendances telles que:

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

et utilisation comme

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

Étant donné que les trois référentiels dépendent de différentes instances DbContext par requête, j'ai deux options pour éviter le problème et gérer des référentiels distincts: modifiez l'injection de DbContext pour créer une nouvelle instance une seule fois par appel:

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

ou, si l'entité enfant est utilisée en lecture seule, désactivez le suivi sur cette instance:

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

J'ai eu le même problème et je pouvais résoudre en créant une nouvelle instance de l'objet que j'essayais de mettre à jour. Ensuite, j'ai passé cet objet à mon repositionnement.

1
karolanet333

Utilisez le même objet DBContext tout au long de la transaction.

0
Nalan Madheswaran