私は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";
}
これら2行が...
EmployeeService es = new EmployeeService();
CityService cs = new CityService();
...コンストラクターでパラメーターを使用しないでください。クラス内でコンテキストを作成すると思います。 city1
...をロードすると.
Payroll.Entities.City city1 = cs.SelectCity(...);
...city1
をCityService
のコンテキストに添付します。後で、新しいEmployee
city1
への参照としてe1
を追加し、e1
city1
へのこの参照を含むを追加しますEmployeeService
のコンテキスト。その結果、city1
が2つの異なるコンテキストにアタッチされており、これが例外について不平を言っています。
これを修正するには、サービスクラスの外部でコンテキストを作成し、両方のサービスでそれを挿入して使用します。
EmployeeService es = new EmployeeService(context);
CityService cs = new CityService(context); // same context instance
サービスクラスは、1つのエンティティタイプのみを担当するリポジトリに少し似ています。そのような場合、サービスに個別のコンテキストを使用する場合、エンティティ間の関係が関係するとすぐに、常に問題が発生します。
EmployeeCityService
(単一のコンテキストを持つ)のような密接に関連するエンティティのセットを担当する単一のサービスを作成し、Button1_Click
メソッドの操作全体をこのメソッドに委任することもできます。サービス。
再現する手順はこれに簡略化できます。
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();
これは古いスレッドですが、私が好むもう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;
私は同じ問題を抱えていましたが、@ 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;
コンテキストを一度インスタンス化してそれをあなたのサービス/リポジトリ層のコンストラクタに渡すか、または私が読んだ別の作業単位パターンを実装しているかのような他の解決策もあります。きっともっとある….
インジェクションやさらに悪いシングルトンの代わりに、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));
}
私の場合は、ASP.NET Identity Frameworkを使用していました。 ApplicationUser
エンティティを取得するために、組み込みのUserManager.FindByNameAsync
メソッドを使用しました。次に、このエンティティを別のDbContext
に新しく作成されたエンティティで参照しようとしました。これはあなたが最初に見た例外をもたらしました。
ApplicationUser
メソッドからのId
のみを使用して新しいUserManager
エンティティを作成し、その新しいエンティティを参照することでこれを解決しました。
この場合、エラーが非常に明確であることがわかります。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);
私は同じ問題を抱えていて、更新しようとしていたオブジェクトの新しいインスタンスを作成することで解決できました。それから私はそのオブジェクトを私のリポジトリに渡しました。
トランザクション全体で同じDBContextオブジェクトを使用してください。