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";
}
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 Employee
e1
et ajoutez e1
en 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.
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.
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;
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 ...
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));
}
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é.
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);
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.
Utilisez le même objet DBContext tout au long de la transaction.