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


165

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

およびEmployeeservice Code

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

回答:


241

これらの2行が...

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

...コンストラクタでパラメータを取らないでください。クラス内にコンテキストを作成すると思います。あなたがロードするとcity1...

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

...でcity1コンテキストにをアタッチしCityServiceます。後でcity1、への参照としてa Employee e1を追加しe1 、この参照を含めcity1てのコンテキストに追加しEmployeeServiceます。結果として、city12つの異なるコンテキストに関連付けられました。これは、例外が不満を言うものです。

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

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

サービスクラスは、単一のエンティティタイプのみを処理するリポジトリのように見えます。このような場合、サービスに個別のコンテキストを使用すると、エンティティ間の関係が関与するとすぐに問題が発生します。

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


4
答えに背景情報が含まれていなかったとしても、あなたがそれを理解した方法が好きです。
Daniel Kmak 14

これで問題が解決するようですが、新しいコンテキストインスタンスの記述方法がわかりません:(
Ortund

12
ORMを抽象化することは、黄色の口紅を芝生に置くようなものです。
ロニーオーバーバイ2016年

ここで何か不足している可能性がありますが、一部のORM(特にEntityFramework)では、データコンテキストは常に短命です。静的または再利用されたコンテキストを導入すると、他の一連の課題と問題が発生します。
Maritim 2016年

@Maritimそれは使用法に依存します。Webアプリケーションでは、通常は1回の往復です。デスクトップアプリケーションでは、(スレッドセーフであることが保証されていないためForm)ごとに1つ(何であれ、1つの作業単位を表す)を使用することもできます。ThreadDbContext
LuckyLikey 2017

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これを解決できるのは1つだけです。他のソリューションについては、他の回答を参照してください。


2
あなたがcontextTwoを使いたかったとしましょうか?(おそらくスコープの問題か何かが原因です)どのようにcontextOneからデタッチしてcontextTwoにアタッチしますか?
NullVoxPopuli 2016年

このような処理を行う必要がある場合は、おそらく間違った方法でこれを行っています... 1つのコンテキストを使用することをお勧めします。
Pavel Shkleinik、2016年

3
別のデータベースを指す場合など、別のインスタンスを使用したい場合があります。
ジェイ

1
これは、問題の単純化に役立ちます。しかし、それは本当の答えを提供しません。
BrainSlugs83 2017年

9

これは古いスレッドですが、私が好む別の解決策は、cityIdを更新するだけで、穴モデルCityをEmployeeに割り当てないことです。

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

次に、十分な割り当てです:

e1.CityId=city1.ID;

5

インジェクションの代わりに、さらに悪いシングルトン、デタッチを呼び出すことができます 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 2015

4

私は同じ問題を抱えていましたが、@ Slaumaのソリューションの問題(特定の状況では素晴らしいものです)は、コンテキストをサービスに渡すことをお勧めします。これは、コンテキストがコントローラーから利用できることを意味します。また、コントローラーとサービスレイヤー間の緊密な結合を強制します。

Dependency Injectionを使用してサービス/リポジトリレイヤーをコントローラーに注入しているため、コントローラーからコンテキストにアクセスできません。

私の解決策は、サービス/リポジトリ層にコンテキストの同じインスタンス-シングルトンを使用させることでした。

コンテキストシングルトンクラス:

リファレンス: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;
  [...]

コンテキストを1回インスタンス化してサービス/リポジトリレイヤーのコンストラクターに渡す、または私が読んだ作業単位パターンを実装している別のソリューションなど、他のソリューションも存在します。もっとあると思います...


9
...マルチスレッドを使用してみるとすぐに機能しなくなりますか?
CaffGeek 2013年

8
コンテキストは必要以上に開いたままにしないでください。シングルトンを使用してコンテキストを永久に開いたままにすることは、最後に行うことです。
enzi 2013

3
リクエストごとにこれを適切に実装しているのを見てきました。Staticキーワードの使用は間違っていますが、このパターンを作成してリクエストの最初にコンテキストをインスタンス化し、リクエストの最後にそれを破棄する場合、それは正当な解決策になります。
アイディン

1
これは本当に悪いアドバイスです。DIを使用している場合(ここに証拠が表示されませんか?)、DIコンテナーにコンテキストの有効期間を管理させる必要があります。おそらく、要求ごとに行う必要があります。
ケーシー

3
これは悪いです。悪い。悪い。悪い。悪い。特にこれがWebアプリケーションの場合、静的オブジェクトはすべてのスレッドとユーザー間で共有されるためです。これは、Webサイトの複数の同時ユーザーがデータコンテキストを踏みつけ、データコンテキストを破壊し、意図しない変更を保存したり、ランダムクラッシュを作成したりすることを意味します。DbContextsはスレッド間で共有しないでください。次に、静力学が破壊されないという問題があります。そのため、
静寂が止まらず

3

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

from メソッドApplicationUserのみで新しいエンティティを作成し、その新しいエンティティを参照することで、これを解決しました。IdUserManager


1

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


サンプルコードを手伝っていただけませんか。?だから、あなたが何を言おうとしているのかは明らかです
BJ Patel

1

この場合、エラーは非常に明確であることがわかります。EntityFramework IEntityChangeTrackerは、の複数のインスタンス、または通常はの複数のインスタンスを使用してエンティティを追跡できませんDbContext。解決策は次のとおりDbContextです。の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つのオプションがあります。DbContextの注入を変更して、呼び出しごとに1回だけ新しいインスタンスを作成します。

// 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

これが私がこの問題に遭遇した方法です。まずOrderApplicationUserテーブルへの参照が必要なを保存する必要があります。

  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

問題は、新しいOrderエンティティを保存するために新しいApplicationDbContextを初期化していることです。

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

そのため、問題を解決するために、ASP.NET MVCの組み込みのUserManagerを使用する代わりに、同じApplicationDbContextを使用しました。

これの代わりに:

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.