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


165

我正在尝试保存“员工”详细信息,其中包含“城市”的引用。但是每次尝试保存我的联系人时,都会得到验证“ ADO.Net 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";
        }

Answers:


241

因为这两行...

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

...不要在构造函数中使用参数,我想您在类中创建了上下文。当您加载city1...

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

...您将附加city1到中的上下文CityService。稍后,您将添加一个city1对新内容的引用,Employee e1并添加对上下文的e1 引用city1EmployeeService。结果,您已city1附加到异常所抱怨的两个不同的上下文中。

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

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

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

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


4
即使答案未包含某些背景信息,我也喜欢您弄清楚的方式。
Daniel Kmak 2014年

看起来这将解决我的问题,我只是不知道如何编写新的上下文实例:(
Ortund

12
提取ORM就像将黄色唇膏放在粪便上。
罗尼·欧弗比

我在这里可能会遗漏一些东西,但是在某些ORM(尤其是EntityFramework)中,数据上下文应该总是短暂的。引入静态或重复使用的上下文将带来其他一系列挑战和问题。
Maritim

@Maritim取决于用法。在Web应用程序中,通常是一次往返。在桌面应用程序中,您也可能每个使用一个Form(无论如何,它只代表一个工作单元)Thread(因为DbContext不能保证是线程安全的)。
LuckyLikey

30

复制步骤可以简化为:

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即可解决此问题。有关其他解决方案,请参阅其他答案。


2
假设您要使用contextTwo?(可能由于范围问题或其他原因)如何与contextOne分离并附加到contextTwo?
NullVoxPopuli

如果您需要执行此类操作,则很可能是您使用了错误的方式...我建议使用一种上下文。
Pavel Shkleinik '16

3
在某些实例中,您想使用其他实例,例如,当指向另一个数据库时。
杰伊

1
这有助于简化问题。但这并不能提供真正的答案。
BrainSlugs83

9

这是一个旧线程,但是我更喜欢另一种解决方案,就是更新cityId而不是将孔模型City分配给Employee ...以使Employee看起来像这样:

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

然后分配就足够了:

e1.CityId=city1.ID;

5

除了注射甚至更糟的Singleton,您可以致电Detach在Add之前方法。

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

EntityFramework 4: cs.Detach(city1);

还有一种方法,以防您不需要第一个DBContext对象。只需使用关键字包装即可:

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

1
我使用了以下内容:dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'进行分离,然后能够dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;用于更新。像梦一样工作
彼得·史密斯

是的,彼得。我应该提到将State标记为Modified。
Roman O

在我的应用程序启动(global.asax)逻辑中,我正在加载小部件列表..我存储到内存中的参考对象的简单列表。由于我是在Using语句中执行EF上下文的,所以我认为以后控制器将这些对象分配到业务图中时就没有问题了(嘿,那个旧上下文消失了,对吗?)-这个答案救了我。
bkwdesign

4

我有同样的问题,但是我对@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;
  [...]

确实存在其他解决方案,例如一次实例化上下文并将其传递到服务/存储库层的构造函数中,或者我所读到的另一个解决方案正在实现工作单元模式。我敢肯定还有更多...


9
...在您尝试使用多线程功能时,这种功能就不会崩溃吗?
CaffGeek

8
上下文不应保持打开状态超过必要的时间,使用Singleton使其永远保持打开状态是您要做的最后一件事。
enzi

3
我已经看到了每个请求的良好实现。使用Static关键字是错误的,但是如果您使用此模式在请求的开始实例化上下文并将其放置在请求的末尾,则这将是一个合法的解决方案。
艾丁2013年

1
这确实是个坏建议。如果您使用的是DI(我在这里看不到证据?),则应让您的DI容器管理上下文生存期,并且它可能应按请求进行。
Casey 2014年

3
这不好。坏。坏。坏。坏。特别是如果这是一个Web应用程序,因为静态对象在所有线程和用户之间共享。这意味着您网站的多个同时用户将踩踏您的数据上下文,可能会破坏它,保存您不想要的更改,甚至只是造成随机崩溃。请勿在线程之间共享DbContext。还有一个问题是静电永远不会被破坏,因此它将坐下来并继续使用越来越多的内存...
Erik Funkenbusch 2014年

3

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

我通过ApplicationUser仅使用Idfrom UserManager方法创建一个新实体并引用该新实体来解决了这一问题。


1

我有同样的问题,我可以解决要尝试更新的对象的新实例的问题。然后,我将该对象传递给我的存储库。


能否请您提供示例代码。?因此,您要说的很清楚
BJ Patel

1

在这种情况下,事实证明错误很明显:Entity Framework无法使用的多个实例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);


0

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

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

但是,使用IoC实例化存储库会使它们全部具有单独的上下文,并且我开始遇到此错误。

现在,我已经回到了以通用上下文更新存储库的同时,我想到了一种更好的方法。


0

这就是我遇到此问题的方式。首先,我需要保存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()); 

-2

错误源:

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

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


我不确定这是否能回答问题。也许某些情况会有所帮助。
Stuart Siegler,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.