web-dev-qa-db-ja.com

エンティティオブジェクトは、IEntityChangeTrackerの複数のインスタンスから参照することはできません。 Entity Framework 4.1でエンティティに関連オブジェクトを追加しながら

私はCityと関連している従業員の詳細を保存しようとしています。ただし、連絡先を保存しようとするたびに例外が発生します"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";
        }
154
Smily

これら2行が...

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

...コンストラクターでパラメーターを使用しないでください。クラス内でコンテキストを作成すると思います。 city1...をロードすると.

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

...city1CityServiceのコンテキストに添付します。後で、新しいEmployeecity1への参照としてe1を追加し、e1city1へのこの参照を含むを追加しますEmployeeServiceのコンテキスト。その結果、city1が2つの異なるコンテキストにアタッチされており、これが例外について不平を言っています。

これを修正するには、サービスクラスの外部でコンテキストを作成し、両方のサービスでそれを挿入して使用します。

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

サービスクラスは、1つのエンティティタイプのみを担当するリポジトリに少し似ています。そのような場合、サービスに個別のコンテキストを使用する場合、エンティティ間の関係が関係するとすぐに、常に問題が発生します。

EmployeeCityService(単一のコンテキストを持つ)のような密接に関連するエンティティのセットを担当する単一のサービスを作成し、Button1_Clickメソッドの操作全体をこのメソッドに委任することもできます。サービス。

230
Slauma

再現する手順はこれに簡略化できます。

var contextOne = new EntityContext();
var contextTwo = new EntityContext();

var user = contexOne.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();
23
Pavel Shkleinik

これは古いスレッドですが、私が好むもう1つの解決策は、単にcityIdを更新し、穴モデルCityをEmployeeに割り当てないことです。これを行うには、Employeeを次のようにします。

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

それで十分な代入です:

e1.CityId=city1.ID;
8
user3484623

私は同じ問題を抱えていましたが、@ Slaumaの解決策(私は場合によっては素晴らしいことですが)では、コンテキストをサービスに渡すことを推奨しています。それはまた私のコントローラとサービス層の間の密接な結合を強いる。

私はDependency Injectionを使用してサービス/リポジトリ層をコントローラに注入しているので、コントローラからコンテキストにアクセスすることはできません。

私の解決策は、サービス/リポジトリ層がコンテキストの同じインスタンス - 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;

コンテキストを一度インスタンス化してそれをあなたのサービス/リポジトリ層のコンストラクタに渡すか、または私が読んだ別の作業単位パターンを実装しているかのような他の解決策もあります。きっともっとある….

5
kmullings

インジェクションやさらに悪いシングルトンの代わりに、Addの前にDetachメソッドを呼び出すことができます。

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

EntityFramework 4:cs.Detach(city1);

あなたが最初のDBContextオブジェクトを必要としない場合には、さらに別の方法があります。 singキーワードでラップするだけです。

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

私の場合は、ASP.NET Identity Frameworkを使用していました。 ApplicationUserエンティティを取得するために、組み込みのUserManager.FindByNameAsyncメソッドを使用しました。次に、このエンティティを別のDbContextに新しく作成されたエンティティで参照しようとしました。これはあなたが最初に見た例外をもたらしました。

ApplicationUserメソッドからのIdのみを使用して新しいUserManagerエンティティを作成し、その新しいエンティティを参照することでこれを解決しました。

3
Justin Skiles

この場合、エラーが非常に明確であることがわかります。Entity FrameworkはIEntityChangeTrackerの複数のインスタンス、または通常はDbContextの複数のインスタンスを使用してエンティティを追跡できません。解決策は次のとおりです。DbContextのインスタンスを1つ使用します。単一のリポジトリを介して必要なすべてのエンティティにアクセスする(1つの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!

3つのリポジトリはすべてリクエストごとに異なるDbContextインスタンスに依存しているので、この問題を回避して別々のリポジトリを管理するには2つの方法があります。

// 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);
1
Kjata30

私は同じ問題を抱えていて、更新しようとしていたオブジェクトの新しいインスタンスを作成することで解決できました。それから私はそのオブジェクトを私のリポジトリに渡しました。

1
karolanet333

トランザクション全体で同じDBContextオブジェクトを使用してください。

0