ไม่สามารถอ้างอิงเอนทิตีวัตถุโดย IEntityChangeTracker หลายอินสแตนซ์ ขณะที่เพิ่มวัตถุที่เกี่ยวข้องกับเอนทิตีใน Entity Framework 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";
        }

คำตอบ:


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

ดูเหมือนว่าสิ่งนี้จะช่วยแก้ปัญหาของฉันฉันไม่รู้ว่าจะเขียนตัวอย่างบริบทใหม่ :(
Ortund

12
การสรุป ORM เปรียบเสมือนการวางลิปสติกสีเหลืองบนพื้น
Ronnie Overby

ฉันอาจจะหายไปบางสิ่งบางอย่างที่นี่ แต่ในบาง ORMs (โดยเฉพาะ EntityFramework) บริบทของข้อมูลควรสั้น การแนะนำบริบทแบบสแตติกหรือที่ใช้ซ้ำจะเป็นการแนะนำชุดของความท้าทายและปัญหาอื่น ๆ
Maritim

@Maritim มันขึ้นอยู่กับการใช้งาน ในเว็บแอปพลิเคชันโดยทั่วไปจะเป็นหนึ่งไปกลับ ในแอปพลิเคชันบนเดสก์ท็อปคุณอาจใช้หนึ่งรายการForm( ต่อเมื่อมันแสดงถึงหนึ่งหน่วยของการทำงาน) ต่อThread(เนื่องจากDbContextไม่รับประกันว่าจะเป็น threadsafe)
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

3
มีอินสแตนซ์ที่คุณต้องการใช้อินสแตนซ์อื่นเช่นเมื่อชี้ไปที่ฐานข้อมูลอื่น
Jay

1
นี่เป็นปัญหาที่ทำให้เข้าใจง่าย แต่มันไม่ได้ให้คำตอบที่แท้จริง
BrainSlugs83

9

นี่เป็นเธรดเก่า แต่อีกวิธีที่ฉันชอบคืออัปเดต cityId และไม่ได้กำหนดโมเดลโมเดล City ให้กับพนักงาน ... เพื่อทำเช่นนั้นพนักงานควรมีลักษณะดังนี้:

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

จากนั้นก็เป็นการมอบหมายที่เพียงพอ:

e1.CityId=city1.ID;

5

อีกทางเลือกหนึ่งสำหรับการฉีดและแม้แต่ซิงเกิลที่แย่ลงคุณสามารถเรียกวิธีการแยกออกก่อนเพิ่ม

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;เพื่ออัปเดตได้ ทำงานเหมือนฝัน
ปีเตอร์สมิ ธ

ใช่ปีเตอร์ ฉันควรพูดถึงเพื่อทำเครื่องหมายสถานะเป็น Modified
Roman O

ในแอปพลิเคชันของฉันเริ่มต้น (global.asax) ลอจิกฉันกำลังโหลดรายการวิดเจ็ต .. รายการวัตถุอ้างอิงที่เรียบง่ายที่ฉันซ่อนอยู่ในหน่วยความจำ เนื่องจากฉันใช้บริบทของ EF ในการใช้คำสั่งฉันคิดว่าจะไม่มีปัญหาในภายหลังเมื่อผู้ควบคุมของฉันเข้ามากำหนดวัตถุเหล่านั้นให้เป็นกราฟธุรกิจ .
bkwdesign

4

ฉันมีปัญหาเดียวกัน แต่ปัญหาของฉันกับโซลูชันของ @ 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;
  [...]

มีวิธีแก้ไขปัญหาอื่น ๆ เช่นการทำให้อินสแตนซ์บริบทหนึ่งครั้งและส่งผ่านไปยัง constructors ของเลเยอร์บริการ / พื้นที่เก็บข้อมูลของคุณหรืออื่น ๆ ที่ฉันอ่านเกี่ยวกับการใช้รูปแบบหน่วยการทำงาน ฉันแน่ใจว่ามีเพิ่มเติม ...


9
... นี่ไม่พังทันทีที่คุณลองใช้มัลติเธรด?
CaffGeek

8
บริบทไม่ควรเปิดนานเกินความจำเป็นการใช้ซิงเกิลตันเพื่อให้เปิดตลอดไปเป็นสิ่งสุดท้ายที่คุณต้องการทำ
enzi

3
ฉันเห็นการใช้งานที่ดีของสิ่งนี้ต่อคำขอ การใช้คำสำคัญคงที่ผิด แต่ถ้าคุณทำรูปแบบนี้เพื่อยกตัวอย่างบริบทที่จุดเริ่มต้นของการร้องขอและกำจัดมันในตอนท้ายของการร้องขอมันจะเป็นทางออกที่ถูกต้อง
Aidin

1
นี่เป็นคำแนะนำที่ไม่ดีจริงๆ หากคุณใช้ DI (ฉันไม่เห็นหลักฐานที่นี่?) คุณควรปล่อยให้คอนเทนเนอร์ DI ของคุณจัดการกับอายุการใช้งานบริบทและน่าจะเป็นคำขอต่อ
Casey

3
นี้ไม่ดี. BAD BAD BAD BAD โดยเฉพาะอย่างยิ่งหากเป็นเว็บแอปพลิเคชันเนื่องจากมีการแชร์วัตถุคงที่ระหว่างเธรดและผู้ใช้ทั้งหมด ซึ่งหมายความว่าผู้ใช้เว็บไซต์ของคุณพร้อมกันหลายคนจะกระทืบในบริบทข้อมูลของคุณอาจทำให้เว็บไซต์เสียหายบันทึกการเปลี่ยนแปลงที่คุณไม่ได้ตั้งใจหรือแม้แต่เพียงแค่สร้างข้อขัดข้องแบบสุ่ม DbContexts ไม่ควรแชร์ข้ามเธรด จากนั้นก็มีปัญหาที่สถิตไม่เคยถูกทำลายดังนั้นมันจะนั่งและใช้หน่วยความจำมากขึ้นเรื่อย ๆ ...
Erik Funkenbusch

3

ในกรณีของฉันฉันใช้ ASP.NET Identity Framework ฉันใช้UserManager.FindByNameAsyncวิธีการในตัวเพื่อดึงข้อมูลApplicationUserเอนทิตี DbContextจากนั้นผมก็พยายามที่จะอ้างอิงนิติบุคคลนี้เป็นกิจการที่สร้างขึ้นใหม่ในที่แตกต่างกัน สิ่งนี้ส่งผลให้เกิดข้อยกเว้นที่คุณเห็นในตอนแรก

ฉันแก้ไขปัญหานี้โดยการสร้างApplicationUserเอนทิตีใหม่โดยใช้เพียงIdจากUserManagerเมธอดและอ้างอิงถึงเอนทิตีใหม่นั้น


1

ฉันมีปัญหาเดียวกันและฉันสามารถแก้ไขการสร้างอินสแตนซ์ใหม่ของวัตถุที่ฉันพยายามอัปเดต จากนั้นฉันก็ส่งวัตถุนั้นไปยัง reposotory ของฉัน


คุณกรุณาช่วยด้วยรหัสตัวอย่างได้ไหม ? ดังนั้นจะชัดเจนว่าคุณกำลังพยายามจะพูดอะไร
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

ฉันประสบปัญหาเดียวกันนี้หลังจากใช้งาน IoC สำหรับโครงการ (ASP.Net MVC EF6.2)

ฉันมักจะเริ่มต้นบริบทข้อมูลในตัวสร้างของตัวควบคุมและใช้บริบทเดียวกันเพื่อเริ่มต้นที่เก็บทั้งหมดของฉัน

อย่างไรก็ตามการใช้ 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 เดียวกันแทนการใช้ UserManager ในตัวของ ASP.NET MVC

แทนสิ่งนี้:

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.