Розв’язання "Екземпляр ObjectContext розміщено і більше не може використовуватися для операцій, які потребують з'єднання" InvalidOperationException


123

Я намагаюся заповнити GridViewEntity 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
Ви маєте доступ до будь-яких властивостей навігації в режимі перегляду сітки У цьому випадку вам також потрібно включити ці навігаційні таблиці до запиту. Якquery.Include("SomeOtherTable")
Нілеш

Спробуйте створити проксі-клас для розміщення вашої сутності або принаймні повернути анонімний об'єкт. З моєї точки зору, використання ef вимагає створення проксі-класів для реалізації вашої логіки, використовуйте edmx так само, як рівень доступу db не як бізнес.
Gonzix

так, в gridview я отримую ще одну колонку таблиці. Що таке LoanProviderName.
барсан

1
Спробуйте db.MemberLoans.Include("LoanProduct").OrderByDescending()перевірити синтаксис, тому що я не маю перед собою VS.
Нілеш

3
Вам просто потрібно продовжити, включаючи всі властивості навігації, до яких ви звертаєтесь поза контекстом db.MemberLoans.Include("LoanProduct").Include("SomeOtherTable). Перевірте відповіді від @ Tragedian та @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. чи можете ви мені допомогти з цього приводу.
барсан

8
@barsan просто включіть усі навігаційні властивості по черзі. Наприклад db.MemberLoans.Include(m => m.Membership).Include(m => m.LoanProduct).OrderByDescending(m => m.LoanDate);, це генерує JOIN-запит та повертає всі дані одразу.
Сергій Березовський

1
Велике спасибі лазиберезовському. Я так вам вдячний. Ти врятував мене майже за день. З вашого пояснення я дізнаюся більше про Entity Framework. Дякую тобі друже.
барсан

Спасибі товариш, ідеально. У мене був оператор, який обмежував ледачу завантаження. Чудова відповідь.
ncbl

4
Що робити, якщо я взагалі не хочу включати ці пов'язані об’єкти у свій запит?
Ортунд

32

CosisEntitiesКлас СВІЙ DbContext. Коли ви створюєте контекст у usingблоці, ви визначаєте межі для своєї операції, орієнтованої на дані.

У своєму коді ви намагаєтеся випромінувати результат запиту з методу, а потім закінчити контекст у межах методу. Операція, якій ви передаєте результат, потім намагається отримати доступ до об'єктів, щоб заповнити вигляд сітки. Десь у процесі прив’язки до сітки доступ до ліниво завантаженого властивості та Entity Framework намагається здійснити пошук для отримання значень. Це не вдається, оскільки пов'язаний контекст вже закінчився.

У вас є дві проблеми:

  1. Ви ліниво завантажуєте особи, коли прив'язуєтесь до сітки. Це означає, що ви виконуєте безліч окремих операцій із запитом до SQL Server, які збираються сповільнити все. Виправити цю проблему можна, зробивши відповідні властивості завантаженими за замовчуванням, або попросивши Entity Framework включити їх до результатів цього запиту, використовуючи Includeметод розширення.

  2. Ви закінчуєте свій контекст передчасно: a DbContextмає бути доступним у всій одиниці роботи, яка виконується, і розпоряджатися ним лише тоді, коли ви закінчите роботу. У випадку з ASP.NET, одиниця роботи, як правило, обробляє запит HTTP.


Дуже дякую за корисну інформацію та приємне пояснення проблеми. Насправді я настільки новий в Entity Framework, як і в Linq, тому ця інформація справді чудовий урок для мене.
барсан

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 не відключив ледачу завантаження і код отримує доступ до властивості навігації Pet після того, як Контекст був утилізований оператором use.

Налагодження

Як ви знаходите джерело цього винятку? Окрім перегляду самого винятку, яке буде кинуто саме в тому місці, де воно відбувається, застосовуються загальні правила налагодження у Visual Studio: розміщуйте стратегічні точки перерви та перевіряйте ваші змінні , або наведення курсором миші на їх імена, відкриваючи ( Швидко) Переглядайте вікно або використовуючи різні панелі налагодження, такі як Locals та Autos.

Якщо ви хочете дізнатися, де посилання знаходиться чи не встановлено, клацніть правою кнопкою миші його ім'я та виберіть "Знайти всі посилання". Потім ви можете розмістити точку перерви в будь-якому місці, яке запитує дані, та запустити свою програму із доданим налагоджувачем. Кожен раз, коли налагоджувач переходить на таку точку розриву, вам потрібно визначити, чи слід було б заповнити вашу навігаційну властивість чи потрібні запитувані дані.

Способи уникнути

Вимкнути ледачий завантаження

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

Плюси: Замість того, щоб викидати InvalidOperationException, властивість буде недійсною. Доступ до властивостей null або спроба змінити властивості цього властивості викличе 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.Id, Entity Framework присвоює отримане значення Person.Petдо того 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";
}

Таким чином, ви не застрягли з нетерплячим завантаженням.


Це в основному нетерпляче завантаження, ви завантажили об'єкт через контекст. Є лише два варіанти; прагнення до навантаження та ледачого навантаження.
Ерік Філіпс

@ErikPhilips справа, ледаче завантажується новий контекст даних
Zorgarath

1
@ErikPhilips - є також явне завантаження - docs.microsoft.com/en-us/ef/ef6/querying/…
Дейв Блек

1

Якщо ви використовуєте ASP.NET Core і цікавитеся, чому ви отримуєте це повідомлення одним із методів контролера асинхронізації, переконайтеся, що ви повернетеся, Taskа неvoid - ASP.NET Core розпоряджається введеними контекстами.

(Я публікую цю відповідь, оскільки це запитання є результатом пошуку в цьому повідомленні про виключення, і це тонка проблема - можливо, це корисно людям, які Google для цього.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.