การแก้ปัญหา“ อินสแตนซ์ ObjectContext ถูกกำจัดและไม่สามารถใช้สำหรับการดำเนินการที่ต้องใช้การเชื่อมต่อได้อีกต่อไป” InvalidOperationException


123

ฉันพยายามเติมข้อมูลGridViewโดยใช้ Entity Frameworkm แต่ทุกครั้งฉันได้รับข้อผิดพลาดต่อไปนี้:

"ผู้เข้าถึงคุณสมบัติ" LoanProduct "บนออบเจ็กต์" COSIS_DAL.MemberLoan "มีข้อยกเว้นต่อไปนี้: อินสแตนซ์ ObjectContext ถูกกำจัดและไม่สามารถใช้สำหรับการดำเนินการที่ต้องใช้การเชื่อมต่อได้อีกต่อไป"

รหัสของฉันคือ:

public List<MemberLoan> GetAllMembersForLoan(string keyword)
{
    using (CosisEntities db = new CosisEntities())
    {
        IQueryable<MemberLoan> query = db.MemberLoans.OrderByDescending(m => m.LoanDate);
        if (!string.IsNullOrEmpty(keyword))
        {
            keyword = keyword.ToLower();
            query = query.Where(m =>
                  m.LoanProviderCode.Contains(keyword)
                  || m.MemNo.Contains(keyword)
                  || (!string.IsNullOrEmpty(m.LoanProduct.LoanProductName) && m.LoanProduct.LoanProductName.ToLower().Contains(keyword))
                  || m.Membership.MemName.Contains(keyword)
                  || m.GeneralMasterInformation.Description.Contains(keyword)

                  );
        }
        return query.ToList();
    }
}


protected void btnSearch_Click(object sender, ImageClickEventArgs e)
{
    string keyword = txtKeyword.Text.ToLower();
    LoanController c = new LoanController();
    List<COSIS_DAL.MemberLoan> list = new List<COSIS_DAL.MemberLoan>();
    list = c.GetAllMembersForLoan(keyword);

    if (list.Count <= 0)
    {
        lblMsg.Text = "No Records Found";
        GridView1.DataSourceID = null;
        GridView1.DataSource = null;
        GridView1.DataBind();
    }
    else
    {
        lblMsg.Text = "";
        GridView1.DataSourceID = null;   
        GridView1.DataSource = list;
        GridView1.DataBind();
    }
}

ข้อผิดพลาดกล่าวถึงLoanProductNameคอลัมน์ของไฟล์Gridview. กล่าวถึง: ฉันใช้ C #, ASP.net, SQL-Server 2008 เป็นฐานข้อมูลส่วนหลัง

ฉันค่อนข้างใหม่สำหรับ Entity Framework ฉันไม่เข้าใจว่าทำไมฉันจึงได้รับข้อผิดพลาดนี้ ใครสามารถช่วยฉันได้บ้าง?


1
คุณกำลังเข้าถึงคุณสมบัติการนำทางใด ๆ ใน gridview ในกรณีนี้คุณต้องรวมตารางการนำทางเหล่านั้นไว้ในแบบสอบถามด้วย Likequery.Include("SomeOtherTable")
Nilesh

ลองสร้างคลาสพร็อกซีเพื่อโฮสต์เอนทิตีของคุณหรืออย่างน้อยก็ส่งคืนอ็อบเจ็กต์ที่ไม่ระบุตัวตน จากมุมมองของฉันการใช้ ef จำเป็นต้องสร้างคลาสพร็อกซีเพื่อใช้งานลอจิกของคุณใช้ edmx เช่นเดียวกับชั้นการเข้าถึงฐานข้อมูลไม่ใช่ธุรกิจ
Gonzix

ใช่ใน gridview ฉันได้รับคอลัมน์ตารางอื่นด้วย ซึ่งก็คือ LoanProviderName
baran

1
ลองdb.MemberLoans.Include("LoanProduct").OrderByDescending()ตรวจสอบสาเหตุทางไวยากรณ์ที่ฉันไม่มี VS อยู่ข้างหน้าฉัน
Nilesh

3
db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable)คุณเพียงแค่ต้องที่จะไปรวมทั้งคุณสมบัตินำทางที่คุณกำลังเข้าถึงนอกบริบทเช่น ตรวจสอบคำตอบโดย @Tarageian และ @lazyberezovsky
Nilesh

คำตอบ:


175

โดยค่าเริ่มต้น Entity Framework ใช้การโหลดแบบขี้เกียจสำหรับคุณสมบัติการนำทาง นั่นเป็นเหตุผลที่คุณสมบัติเหล่านี้ควรถูกทำเครื่องหมายเป็นเสมือน - EF สร้างคลาสพร็อกซีสำหรับเอนทิตีของคุณและแทนที่คุณสมบัติการนำทางเพื่อให้ขี้เกียจโหลด เช่นถ้าคุณมีเอนทิตีนี้:

public class MemberLoan
{
   public string LoandProviderCode { get; set; }
   public virtual Membership Membership { get; set; }
}

Entity Framework จะส่งคืนพร็อกซีที่สืบทอดมาจากเอนทิตีนี้และจัดเตรียมอินสแตนซ์ DbContext ให้กับพร็อกซีนี้เพื่อให้สามารถโหลดการเป็นสมาชิกได้ในภายหลัง:

public class MemberLoanProxy : MemberLoan
{
    private CosisEntities db;
    private int membershipId;
    private Membership membership;

    public override Membership Membership 
    { 
       get 
       {
          if (membership == null)
              membership = db.Memberships.Find(membershipId);
          return membership;
       }
       set { membership = value; }
    }
}

ดังนั้นเอนทิตีจึงมีอินสแตนซ์ของ DbContext ซึ่งใช้สำหรับโหลดเอนทิตี นั่นคือปัญหาของคุณ คุณได้usingบล็อกการใช้งาน CosisEntities ซึ่งกำจัดบริบทก่อนที่จะส่งคืนเอนทิตี เมื่อบางโค้ดพยายามใช้คุณสมบัติการนำทางที่โหลดแบบขี้เกียจในภายหลังจะล้มเหลวเนื่องจากบริบทถูกกำจัดในขณะนั้น

ในการแก้ไขพฤติกรรมนี้คุณสามารถใช้การโหลดคุณสมบัติการนำทางอย่างกระตือรือร้นซึ่งคุณจะต้องใช้ในภายหลัง:

IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);

ซึ่งจะโหลดการเป็นสมาชิกล่วงหน้าทั้งหมดและจะไม่ใช้การโหลดแบบขี้เกียจ โปรดดูรายละเอียดในการโหลดบทความเอนทิตีที่เกี่ยวข้องบน MSDN


ขอบคุณมากสำหรับคำอธิบายและคำตอบที่เป็นประโยชน์ของคุณ ที่จริงแล้วฉันมีสามตารางดังนั้นฉันไม่รู้ว่าฉันจะเพิ่มสามตารางด้วย INCLUDE ได้อย่างไร คุณช่วยฉันเรื่องนี้ได้ไหม
baran

8
@barsan เพียงรวมคุณสมบัติการนำทางทั้งหมดทีละรายการ เช่นdb.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);ที่จะสร้างแบบสอบถาม JOIN และส่งคืนข้อมูลทั้งหมดในครั้งเดียว
Sergey Berezovskiy

1
ขอบคุณมาก lazyberezovsky ฉันรู้สึกขอบคุณคุณ คุณช่วยฉันเกือบวัน จากคำอธิบายของคุณฉันกำลังเรียนรู้เพิ่มเติมเกี่ยวกับ Entity Framework ขอบคุณเพื่อนของฉัน
baran

ขอบคุณเพื่อนที่สมบูรณ์แบบ ฉันมีคำสั่งใช้ที่ จำกัด การโหลดที่ขี้เกียจ คำตอบที่ดี
ncbl

4
จะเกิดอะไรขึ้นถ้าฉันไม่ต้องการรวมเอนทิตีที่เกี่ยวข้องเหล่านั้นในแบบสอบถามของฉันเลย?
Ortund

32

ชั้นของคุณCosisEntities DbContextเมื่อคุณสร้างบริบทในusingบล็อกคุณกำลังกำหนดขอบเขตสำหรับการดำเนินการที่มุ่งเน้นข้อมูลของคุณ

ในโค้ดของคุณคุณกำลังพยายามส่งผลลัพธ์ของคิวรีจากเมธอดจากนั้นจบบริบทภายในเมธอด การดำเนินการที่คุณส่งผ่านผลลัพธ์ไปแล้วพยายามเข้าถึงเอนทิตีเพื่อเติมข้อมูลในมุมมองกริด บางแห่งในกระบวนการเชื่อมโยงกับกริดคุณสมบัติที่โหลดแบบขี้เกียจกำลังถูกเข้าถึงและ Entity Framework กำลังพยายามทำการค้นหาเพื่อรับค่า ล้มเหลวเนื่องจากบริบทที่เกี่ยวข้องได้สิ้นสุดลงแล้ว

คุณมีปัญหาสองประการ:

  1. คุณขี้เกียจโหลดเอนทิตีเมื่อคุณเชื่อมโยงกับกริด ซึ่งหมายความว่าคุณกำลังดำเนินการสืบค้นแยกต่างหากจำนวนมากไปยัง SQL Server ซึ่งจะทำให้ทุกอย่างช้าลง คุณสามารถแก้ไขปัญหานี้ได้โดยการทำให้คุณสมบัติที่เกี่ยวข้องโหลดโดยค่าเริ่มต้นหรือขอให้ Entity Framework รวมไว้ในผลลัพธ์ของแบบสอบถามนี้โดยใช้Includeวิธีการส่วนขยาย

  2. คุณกำลังสิ้นสุดบริบทของคุณก่อนเวลาอันควร: a DbContextควรจะพร้อมใช้งานตลอดหน่วยงานที่กำลังดำเนินการกำจัดทิ้งเมื่อคุณทำงานเสร็จแล้วเท่านั้น ในกรณีของ ASP.NET โดยทั่วไปหน่วยงานคือการจัดการคำขอ HTTP


ขอบคุณมากสำหรับข้อมูลที่เป็นประโยชน์และคำอธิบายปัญหาที่ดี จริงๆแล้วฉันยังใหม่มากใน Entity Framework เช่นเดียวกับ Linq ดังนั้นข้อมูลนี้จึงเป็นบทเรียนที่ยอดเยี่ยมสำหรับฉันที่จะเรียนรู้
baran

20

บรรทัดล่าง

โค้ดของคุณได้ดึงข้อมูล (เอนทิตี) ผ่านเอนทิตีเฟรมเวิร์กที่เปิดใช้งานการโหลดแบบเกียจคร้านและหลังจากที่ DbContext ถูกกำจัดแล้วโค้ดของคุณกำลังอ้างอิงคุณสมบัติ (เอนทิตีที่เกี่ยวข้อง / ความสัมพันธ์ / การนำทาง) ที่ไม่ได้ร้องขออย่างชัดเจน

โดยเฉพาะอย่างยิ่ง

InvalidOperationExceptionกับข้อความนี้มักจะหมายถึงสิ่งเดียวกัน: คุณกำลังขอข้อมูล (หน่วยงาน) จากนิติบุคคลกรอบหลังจาก DbContext ได้รับการจำหน่าย

กรณีง่ายๆ:

(คลาสเหล่านี้จะถูกใช้สำหรับตัวอย่างทั้งหมดในคำตอบนี้และถือว่าคุณสมบัติการนำทางทั้งหมดได้รับการกำหนดค่าอย่างถูกต้องและมีตารางที่เกี่ยวข้องในฐานข้อมูล)

public class Person
{
  public int Id { get; set; }
  public string name { get; set; }
  public int? PetId { get; set; }
  public Pet Pet { get; set; }
}

public class Pet 
{
  public string name { get; set; }
}

using (var db = new dbContext())
{
  var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
}

Console.WriteLine(person.Pet.Name);

บรรทัดสุดท้ายจะโยนInvalidOperationExceptionเนื่องจาก dbContext ไม่ได้ปิดใช้งานการโหลดแบบขี้เกียจและรหัสกำลังเข้าถึงคุณสมบัติการนำทางของสัตว์เลี้ยงหลังจากที่บริบทถูกกำจัดโดยคำสั่งใช้

แก้จุดบกพร่อง

คุณจะหาที่มาของข้อยกเว้นนี้ได้อย่างไร? นอกเหนือจากการดูข้อยกเว้นซึ่งจะถูกโยนตรงตำแหน่งที่เกิดขึ้นแล้วกฎทั่วไปของการดีบักใน Visual Studio จะใช้: วางจุดพักเชิงกลยุทธ์และตรวจสอบตัวแปรของคุณไม่ว่าจะโดยการวางเมาส์เหนือชื่อเปิด ( Quick) ดูหน้าต่างหรือใช้แผงการดีบักต่างๆเช่น Locals และ Autos

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

วิธีหลีกเลี่ยง

ปิดการใช้งาน Lazy-Loading

public class MyDbContext : DbContext
{
  public MyDbContext()
  {
    this.Configuration.LazyLoadingEnabled = false;
  }
}

ข้อดี: แทนที่จะโยน InvalidOperationException คุณสมบัติจะเป็นโมฆะ การเข้าถึงคุณสมบัติของโมฆะหรือความพยายามที่จะเปลี่ยนแปลงคุณสมบัติของคุณสมบัตินี้จะโยนNullReferenceException

วิธีขอวัตถุอย่างชัดเจนเมื่อจำเป็น:

using (var db = new dbContext())
{
  var person = db.Persons
    .Include(p => p.Pet)
    .FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name);  // No Exception Thrown

ในตัวอย่างก่อนหน้านี้ Entity Framework จะทำให้สัตว์เลี้ยงเป็นจริงนอกเหนือจากบุคคล ซึ่งอาจเป็นประโยชน์เนื่องจากเป็นการเรียกฐานข้อมูลเพียงครั้งเดียว (อย่างไรก็ตามอาจมีปัญหาด้านประสิทธิภาพอย่างมากขึ้นอยู่กับจำนวนผลลัพธ์ที่ส่งคืนและจำนวนคุณสมบัติการนำทางที่ร้องขอในกรณีนี้จะไม่มีการลงโทษด้านประสิทธิภาพเนื่องจากอินสแตนซ์ทั้งสองเป็นเพียงเรกคอร์ดเดียวและการรวมครั้งเดียว)

หรือ

using (var db = new dbContext())
{
  var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);

  var pet = db.Pets.FirstOrDefaultAsync(p => p.id == person.PetId);
}
Console.WriteLine(person.Pet.Name);  // No Exception Thrown

ในตัวอย่างก่อนหน้านี้ Entity Framework จะทำให้สัตว์เลี้ยงเป็นจริงโดยไม่ขึ้นกับบุคคลโดยการโทรเพิ่มเติมไปยังฐานข้อมูล โดยค่าเริ่มต้นเพลง Entity Framework วัตถุจะมีการเรียกข้อมูลจากฐานข้อมูลและหากพบคุณสมบัติที่ตรงกับลูกศรมันจะอัตโนมัติอย่างน่าอัศจรรย์เติมหน่วยงานเหล่านี้ ในกรณีนี้เนื่องจากPetIdบนPersonวัตถุตรงกับPet.IdEntity Framework จะกำหนดPerson.Petให้กับPetค่าที่ดึงมาก่อนที่จะกำหนดค่าให้กับตัวแปรสัตว์เลี้ยง

ฉันมักจะแนะนำแนวทางนี้เนื่องจากบังคับให้โปรแกรมเมอร์เข้าใจว่าเมื่อใดและอย่างไรรหัสขอข้อมูลผ่าน Entity Framework เมื่อโค้ดแสดงข้อยกเว้นการอ้างอิงที่เป็นโมฆะในคุณสมบัติของเอนทิตีคุณสามารถมั่นใจได้ว่าคุณไม่ได้ร้องขอข้อมูลนั้นอย่างชัดเจนเกือบตลอดเวลา


13

เป็นคำตอบที่ล่าช้ามาก แต่ฉันแก้ไขปัญหาได้โดยปิดการโหลดแบบขี้เกียจ

db.Configuration.LazyLoadingEnabled = false;

สำหรับฉันแล้ว StackOverflow ทำงานได้อย่างมหัศจรรย์ด้วยหนึ่งสมุทร และสิ่งนี้ทำให้ฉันขอชื่นชมคุณ!
Harold_Finch

ข้อเสียคือคุณต้องใช้. รวมและสิ่งต่างๆเช่นนั้นเพื่อโหลดคุณสมบัติการนำทาง
boylec1986

1

ในกรณีของฉันฉันกำลังส่ง 'ผู้ใช้' ทุกรุ่นไปยังคอลัมน์และมันไม่ได้รับการแมปอย่างถูกต้องดังนั้นฉันจึงส่งผ่าน 'Users.Name' และได้รับการแก้ไขแล้ว

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users)
             .Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users,*** ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();

var data = db.ApplicationTranceLogs 
             .Include(q=>q.Users).Include(q => q.LookupItems) 
             .Select(q => new { Id = q.Id, FormatDate = q.Date.ToString("yyyy/MM/dd"), ***Users = q.Users.Name***, ProcessType = q.ProcessType, CoreProcessId = q.CoreProcessId, Data = q.Data }) 
             .ToList();

1

คำตอบอื่น ๆ ส่วนใหญ่ชี้ไปที่การโหลดอย่างกระตือรือร้น แต่ฉันพบวิธีแก้ปัญหาอื่น

ในกรณีของฉันฉันมีออบเจ็กต์ EF ที่InventoryItemมีคอลเล็กชันของInvActivityวัตถุลูก

class InventoryItem {
...
   // EF code first declaration of a cross table relationship
   public virtual List<InvActivity> ItemsActivity { get; set; }

   public GetLatestActivity()
   {
       return ItemActivity?.OrderByDescending(x => x.DateEntered).SingleOrDefault();
   }
...
}

และเนื่องจากฉันดึงจากคอลเล็กชันอ็อบเจ็กต์ลูกแทนที่จะเป็นคิวรีบริบท (ด้วยIQueryable) Include()ฟังก์ชันนี้จึงไม่สามารถใช้งานการโหลดอย่างกระตือรือร้นได้ ดังนั้นวิธีแก้ปัญหาของฉันคือการสร้างบริบทจากที่ที่ฉันใช้GetLatestActivity()และattach()วัตถุที่ส่งคืน:

using (DBContext ctx = new DBContext())
{
    var latestAct = _item.GetLatestActivity();

    // attach the Entity object back to a usable database context
    ctx.InventoryActivity.Attach(latestAct);

    // your code that would make use of the latestAct's lazy loading
    // ie   latestAct.lazyLoadedChild.name = "foo";
}

ดังนั้นคุณจะไม่ติดอยู่กับการโหลดอย่างกระตือรือร้น


โดยพื้นฐานแล้วนี่เป็นการโหลดที่กระตือรือร้นคุณได้โหลดวัตถุผ่านบริบท มีเพียงสองทางเลือกเท่านั้น โหลดอย่างกระตือรือร้นและขี้เกียจโหลด
Erik Philips

@ErikPhilips ใช่มันขี้เกียจโหลดด้วยบริบทข้อมูลใหม่
Zorgarath

1
@ErikPhilips - มี Explicit Loading - docs.microsoft.com/en-us/ef/ef6/querying/…
Dave Black

1

หากคุณใช้ ASP.NET Core และสงสัยว่าเหตุใดคุณจึงได้รับข้อความนี้ในวิธีการควบคุมแบบ async ของคุณตรวจสอบให้แน่ใจว่าคุณส่งคืนTaskมากกว่าvoid- ASP.NET Core ทิ้งบริบทที่ถูกแทรก

(ฉันกำลังโพสต์คำตอบนี้เนื่องจากคำถามนี้มีอยู่สูงในผลการค้นหาสำหรับข้อความข้อยกเว้นนั้นและเป็นปัญหาที่ละเอียดอ่อน - อาจเป็นประโยชน์สำหรับผู้ที่ใช้ Google)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.