在实体框架4.1中向实体添加相关对象时,IEntityChangeTracker 的多个实例不能引用实体对象

我正试图保存员工的详细信息,其中有与金融城的参考。但是每次我试图保存我的联系人,这是验证我得到异常 “ Entity Framework 一个实体对象不能被多个 iEntityChangeTracker 实例引用”

我已经看了很多帖子,但仍然不知道该怎么办... ..。 我的保存按钮点击代码如下

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

雇员服务守则

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";
}
200139 次浏览

因为这两句台词..。

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

... 不要在构造函数中使用参数,我猜你在类中创建了一个上下文。当你装载 city1的时候..。

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

你把 city1CityService的上下文连接起来。稍后,您添加一个 city1作为对新的 Employee e1的引用,并将 e1 包括对 city1的引用添加到 EmployeeService中的上下文中。因此,您将 city1连接到两个不同的上下文,这正是异常所抱怨的。

您可以通过在服务类之外创建一个上下文并在两个服务中注入和使用它来解决这个问题:

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

您的服务类看起来有点像只负责单个实体类型的存储库。在这种情况下,当您为服务使用单独的上下文时,一旦涉及到实体之间的关系,您总是会遇到麻烦。

您还可以创建一个单独的服务,它负责一组紧密相关的实体,如 EmployeeCityService(具有单个上下文) ,并将 Button1_Click方法中的整个操作委托给此服务的一个方法。

我也有同样的问题,但是我对@Slauma 的解决方案的看法(尽管在某些情况下非常好)是,它建议我将上下文传递到服务中,这意味着上下文可以从我的控制器获得。它还强制在控制器和服务层之间进行紧密耦合。

我使用依赖注入将服务/存储库层注入到控制器中,因此无法从控制器访问上下文。

我的解决方案是让服务/存储库层使用相同的上下文实例—— Singleton。

背景单例类:

参考资料: http://msdn.microsoft.com/en-us/library/ff650316.aspx
以及一个 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;
}
}
}

储存库类别:

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

还有其他解决方案,比如一次实例化上下文并将其传递到服务/存储库层的构造函数中,或者我读到的实现工作单元(Unit of Work)模式的其他解决方案。我相信还有更多..。

我有同样的问题,我可以解决创建一个新的对象实例,我试图更新。然后将该对象传递到存储库。

这是一个老线程,但是另一个解决方案,我更喜欢,只是更新 cityId,而不是将洞模型 City 分配给 Employee... ... 来完成这个 Employee 应该像这样:

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

那么分配就足够了:

e1.CityId=city1.ID;

复制步骤可简化为:

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

没有错误的代码:

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

只使用一个 EntityContext就可以解决这个问题。

或者注入,甚至更糟糕的单例,您可以在添加之前调用 分离方法。

实体框架6: ((IObjectContextAdapter)cs).ObjectContext.Detach(city1);

实体框架4: cs.Detach(city1);

如果您不需要第一个 DBContext 对象,那么还有另外一种方法,只需用 使用关键字包装它:

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

在我的例子中,我使用的是 ASP.NET 身份框架。我使用了内置的 UserManager.FindByNameAsync方法来检索 ApplicationUser实体。然后,我尝试在另一个 DbContext上新创建的实体上引用这个实体。这导致了您最初看到的异常。

我通过创建一个新的 ApplicationUser实体来解决这个问题,该实体只包含来自 UserManager方法的 Id并引用该新实体。

错误来源:

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

希望有人能节省一些宝贵的时间

在整个事务中使用相同的 DBContext 对象。

在这种情况下,错误显而易见: 实体框架无法使用多个 IEntityChangeTracker实例或通常是多个 DbContext实例跟踪一个实体。解决方案是: 使用 DbContext的一个实例; 通过一个存储库访问所有需要的实体(取决于 DbContext的一个实例) ; 或者关闭对通过存储库访问的所有实体的跟踪,而不是抛出这个特定异常的实体。

当按照控制反转模式。Net Core Web API,我经常发现我有一些具有依赖关系的控制器,比如:

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

和使用方法

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

由于所有三个存储库都依赖于每个请求的不同 DbContext实例,因此我有两个选择来避免这个问题并维护单独的存储库: 更改 DbContext 的注入,以便每次调用只创建一个新实例:

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

或者,如果以只读方式使用子实体,则关闭对该实例的跟踪:

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

在为一个项目(ASP.Net MVC EF6.2)实现 IoC 之后,我遇到了同样的问题。

通常,我会在控制器的构造函数中初始化一个数据上下文,并使用相同的上下文来初始化我的所有存储库。

然而,使用 IoC 实例化存储库导致它们都有单独的上下文,我开始得到这个错误。

现在,我回过头来只是用一个公共上下文创建存储库,同时考虑一种更好的方法。

这就是我遇到这个问题的原因。首先,我需要保存我的 Order,它需要一个参考我的 ApplicationUser表:

  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

问题是,我正在初始化一个新的 ApplicationDbContext 来保存我的新 Order实体:

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

因此,为了解决这个问题,我使用了相同的 ApplicationDbContext,而不是使用 ASP.NET MVC 的内置 UserManager。

而不是这样:

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

我使用了现有的 ApplicationDbContext 实例:

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

对于我的场景,我们有一个解决方案,其中几个应用程序引用相同的上下文。我必须更新 unity.config 文件,将生存期类型添加到上下文中。

<lifetime type="PerResolveLifetimeManager" />