objek entitas tidak dapat dirujuk oleh beberapa instance IEntityChangeTracker. sambil menambahkan objek terkait ke entitas dalam Entity Framework 4.1

165

Saya mencoba menyimpan detail Karyawan, yang memiliki referensi dengan City. Tetapi setiap kali saya mencoba untuk menyimpan kontak saya, yang divalidasi saya mendapatkan pengecualian "ADO.Net Framework Entity Objek entitas tidak dapat dirujuk oleh beberapa contoh IEntityChangeTracker"

Saya telah membaca begitu banyak posting tetapi masih belum mendapatkan ide yang tepat tentang apa yang harus dilakukan ... kode klik tombol Simpan saya diberikan di bawah ini

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

dan Kode Layanan Karyawan

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

Jawaban:

241

Karena dua garis ini ...

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

... jangan mengambil parameter di konstruktor, saya kira Anda membuat konteks di dalam kelas. Ketika Anda memuat city1...

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

... Anda lampirkan city1ke konteks di CityService. Kemudian Anda menambahkan city1sebagai referensi ke yang baru Employee e1dan menambahkan e1 termasuk referensi inicity1 ke konteks di EmployeeService. Akibatnya, Anda telah city1terikat pada dua konteks yang berbeda yang merupakan keluhan dari pengecualian.

Anda bisa memperbaikinya dengan membuat konteks di luar kelas layanan dan menyuntikkan dan menggunakannya di kedua layanan:

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

Kelas layanan Anda terlihat sedikit seperti repositori yang hanya bertanggung jawab atas satu jenis entitas. Dalam kasus seperti itu, Anda akan selalu mengalami masalah segera setelah hubungan antar entitas terlibat ketika Anda menggunakan konteks terpisah untuk layanan.

Anda juga dapat membuat layanan tunggal yang bertanggung jawab untuk satu set entitas yang terkait erat, seperti EmployeeCityService(yang memiliki konteks tunggal) dan mendelegasikan seluruh operasi dalam Button1_Clickmetode Anda ke metode layanan ini.

Slauma
sumber
4
Saya suka cara Anda mengetahuinya meskipun jawaban tidak menyertakan beberapa informasi latar belakang.
Daniel Kmak
Sepertinya ini akan menyelesaikan masalah saya, saya hanya tidak tahu bagaimana menulis contoh konteks baru :(
Ortund
12
Mengabstraksikan ORM seperti menempatkan lipstik kuning pada kotoran.
Ronnie Overby
Saya mungkin kehilangan sesuatu di sini, tetapi dalam beberapa ORM (terutama EntityFramework) konteks data harus selalu singkat. Memperkenalkan konteks statis atau digunakan kembali akan memperkenalkan serangkaian tantangan dan masalah lainnya.
Maritim
@Maritim tergantung pada penggunaan. Di Aplikasi Web, biasanya satu perjalanan pulang pergi. Di Aplikasi Desktop, Anda mungkin juga menggunakan satu per Form(apa pun, itu hanya mewakili satu unit kerja) per Thread(karena DbContexttidak dijamin aman untuk threads).
LuckyLikey
30

Langkah-langkah mereproduksi dapat disederhanakan menjadi ini:

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

Kode tanpa kesalahan:

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

Hanya menggunakan satu yang EntityContextbisa menyelesaikan ini. Lihat jawaban lain untuk solusi lain.

Pavel Shkleinik
sumber
2
katakanlah Anda ingin menggunakan contextTwo? (mungkin karena masalah ruang lingkup atau sesuatu) bagaimana Anda melepaskan diri dari contextOne dan melampirkan konteksTwo?
NullVoxPopuli
Jika Anda perlu melakukan sesuatu seperti ini, kemungkinan besar Anda melakukan ini dengan cara yang salah ... Saya sarankan menggunakan satu konteks.
Pavel Shkleinik
3
Ada beberapa contoh di mana Anda ingin menggunakan contoh yang berbeda, seperti ketika menunjuk ke database yang berbeda.
Jay
1
Ini adalah penyederhanaan masalah yang bermanfaat; tetapi itu tidak memberikan jawaban nyata.
BrainSlugs83
9

Ini adalah utas lama, tetapi solusi lain, yang saya lebih suka, hanya memperbarui cityId dan tidak menetapkan model lubang Kota untuk Karyawan ... untuk melakukan itu Karyawan akan terlihat seperti:

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

Maka itu cukup menetapkan:

e1.CityId=city1.ID;
pengguna3484623
sumber
5

Atau untuk menyuntikkan dan lebih buruk lagi Singleton, Anda dapat memanggil metode Lepaskan sebelum Tambah.

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

EntityFramework 4: cs.Detach(city1);

Masih ada cara lain, jika Anda tidak perlu objek DBContext pertama. Hanya bungkus dengan menggunakan kata kunci:

Payroll.Entities.City city1;
using (CityService cs = new CityService())
{
  city1 = cs.SelectCity(Convert.ToInt64(cmbCity.SelectedItem.Value));
}
Roman O
sumber
1
Saya menggunakan yang berikut: dbContext1.Entry(backgroundReport).State = System.Data.Entity.EntityState.Detached'untuk melepaskan dan kemudian dapat digunakan dbContext2.Entry(backgroundReport).State = System.Data.Entity.EntityState.Modified;untuk memperbarui. Bekerja seperti mimpi
Peter Smith
Ya, Peter. Saya harus menyebutkan untuk menandai Status sebagai Dimodifikasi.
Roman O
Dalam logika startup aplikasi saya (global.asax), saya memuat daftar widget .. daftar objek referensi sederhana yang saya simpan di memori. Karena saya sedang melakukan konteks EF saya di dalam Menggunakan pernyataan, saya pikir tidak akan ada masalah nanti ketika controller saya berkeliling untuk menetapkan objek-objek itu ke dalam grafik bisnis (hei, konteks lama itu hilang, kan?) - jawaban ini menyelamatkan saya .
bkwdesign
4

Saya memiliki masalah yang sama tetapi masalah saya dengan solusi @ Slauma (walaupun hebat dalam hal tertentu) adalah merekomendasikan agar saya meneruskan konteks ke layanan yang menyiratkan bahwa konteks tersedia dari controller saya. Itu juga memaksa kopling ketat antara controller dan lapisan layanan saya.

Saya menggunakan Dependency Injection untuk menyuntikkan lapisan layanan / repositori ke dalam pengontrol dan karenanya tidak memiliki akses ke konteks dari pengontrol.

Solusi saya adalah membuat lapisan layanan / repositori menggunakan instance konteks yang sama - Singleton.

Kelas Singleton Konteks:

Referensi: http://msdn.microsoft.com/en-us/library/ff650316.aspx
dan 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;
    }
  }
}  

Kelas Repositori:

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

Solusi lain memang ada seperti instantiating konteks sekali dan meneruskannya ke konstruktor lapisan layanan / repositori Anda atau yang saya baca tentang yang menerapkan pola Unit Kerja. Saya yakin masih ada lagi ...

kmullings
sumber
9
... bukankah ini mogok segera setelah Anda mencoba dan menggunakan multithreading?
CaffGeek
8
Konteks tidak boleh tetap terbuka lebih lama dari yang diperlukan, menggunakan Singleton untuk membuatnya tetap terbuka selamanya adalah hal terakhir yang ingin Anda lakukan.
enzi
3
Saya telah melihat implementasi yang baik dari ini per permintaan. Menggunakan kata kunci Statis itu salah, tetapi jika Anda membuat pola ini untuk membuat instance konteks di awal permintaan dan membuangnya di akhir permintaan, itu akan menjadi solusi yang sah.
Aidin
1
Ini saran yang sangat buruk. Jika Anda menggunakan DI (saya tidak melihat buktinya di sini?) Maka Anda harus membiarkan wadah DI Anda mengelola konteks seumur hidup dan mungkin harus sesuai permintaan.
Casey
3
Ini buruk. BURUK. BURUK. BURUK. BURUK. Terutama jika ini adalah aplikasi web, karena objek statis dibagikan antara semua utas dan pengguna. Ini berarti bahwa beberapa pengguna situs web Anda secara simultan akan menginjak konteks data Anda, berpotensi merusaknya, menyimpan perubahan yang tidak Anda inginkan, atau bahkan hanya membuat crash secara acak. DbContexts TIDAK PERNAH dibagikan di seluruh utas. Lalu ada masalah bahwa statika tidak pernah dihancurkan, sehingga statik akan duduk dan terus menggunakan memori yang semakin banyak ...
Erik Funkenbusch
3

Dalam kasus saya, saya menggunakan ASP.NET Identity Framework. Saya telah menggunakan UserManager.FindByNameAsyncmetode bawaan untuk mengambil ApplicationUserentitas. Saya kemudian mencoba merujuk entitas ini pada entitas yang baru dibuat pada entitas yang berbeda DbContext. Ini menghasilkan pengecualian yang awalnya Anda lihat.

Saya memecahkan ini dengan membuat ApplicationUserentitas baru dengan hanya Iddari UserManagermetode dan referensi entitas baru.

Justin Skiles
sumber
1

Saya memiliki masalah yang sama dan saya bisa memecahkan membuat contoh baru dari objek yang saya coba perbarui. Kemudian saya menyerahkan objek itu ke repositori saya.

karolanet333
sumber
Bisakah Anda membantu dengan kode sampel. ? jadi akan jelas apa yang ingin Anda katakan
BJ Patel
1

Dalam kasus ini, ternyata kesalahannya sangat jelas: Entity Framework tidak dapat melacak entitas menggunakan beberapa instance IEntityChangeTrackeratau biasanya, multiple instance dari DbContext. Solusinya adalah: gunakan satu instance dari DbContext; mengakses semua entitas yang diperlukan melalui repositori tunggal (tergantung pada satu instance dari DbContext); atau mematikan pelacakan untuk semua entitas yang diakses melalui repositori selain yang melempar pengecualian khusus ini.

Saat mengikuti inversi pola kontrol di .Net Core Web API, saya sering menemukan bahwa saya memiliki pengontrol dengan dependensi seperti:

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

dan penggunaan suka

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

Karena ketiga repositori bergantung pada DbContextinstance berbeda per permintaan, saya memiliki dua opsi untuk menghindari masalah dan mempertahankan repositori terpisah: ubah injeksi DbContext untuk membuat instance baru hanya sekali per panggilan:

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

atau, jika entitas anak digunakan dengan cara baca-saja, matikan pelacakan pada contoh itu:

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

Gunakan objek DBContext yang sama sepanjang transaksi.

Nalan Madheswaran
sumber
0

Saya mendapatkan masalah yang sama setelah menerapkan IoC untuk sebuah proyek (ASP.Net MVC EF6.2).

Biasanya saya akan menginisialisasi konteks data dalam konstruktor controller dan menggunakan konteks yang sama untuk menginisialisasi semua repositori saya.

Namun menggunakan IoC untuk membuat repositori menyebabkan mereka semua memiliki konteks yang terpisah dan saya mulai mendapatkan kesalahan ini.

Untuk sekarang saya sudah kembali ke hanya memperbaharui repositori dengan konteks umum sementara saya memikirkan cara yang lebih baik.

Richard Moore
sumber
0

Ini adalah bagaimana saya mengalami masalah ini. Pertama saya harus menyimpan yang saya Orderbutuhkan referensi ke ApplicationUsermeja saya :

  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

Masalahnya adalah saya menginisialisasi ApplicationDbContext baru untuk menyimpan Orderentitas baru saya :

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

Jadi untuk mengatasi masalah, saya menggunakan ApplicationDbContext yang sama daripada menggunakan UserManager built-in ASP.NET MVC.

Alih-alih ini:

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

Saya menggunakan instance ApplicationDbContext yang ada:

//db instance here is the same instance as my db on my code above.
user = db.Users.Find(User.Identity.GetUserId()); 
Willy David Jr
sumber
-2

Sumber kesalahan:

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

Semoga seseorang menghemat waktu yang berharga

Bourne Kolo
sumber
Saya tidak yakin ini menjawab pertanyaan. Mungkin beberapa konteks akan membantu.
Stuart Siegler