web-dev-qa-db-ja.com

解決「ObjectContextインスタンスは破棄され、接続を必要とする操作に使用できなくなりました」InvalidOperationException

Entity Frameworkmを使用してGridViewを設定しようとしていますが、次のエラーが発生するたびに:

「オブジェクト 'COSIS_DAL.MemberLoan'のプロパティアクセサー 'LoanProduct'は、次の例外をスローしました: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();
    }
}

エラーは、LoanProductNameGridview列に言及しています。言及:バックエンドDBとしてC#、ASP.net、SQL-Server 2008を使用しています。

私はEntity Frameworkを初めて使用します。このエラーが発生する理由がわかりません。誰でも私を助けてくれますか?

106
barsan

デフォルトでは、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のインスタンスがあります。それはあなたの問題だ。 CosisEntitiesの使用の周りにusingブロックがあります。エンティティが返される前にコンテキストを破棄します。後でコードが遅延ロードされたナビゲーションプロパティを使用しようとすると、その時点でコンテキストが破棄されるため失敗します。

この動作を修正するには、後で必要になるナビゲーションプロパティの積極的な読み込みを使用できます。

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

これにより、すべてのメンバーシップが事前にロードされ、遅延ロードは使用されません。詳細については、MSDNの Loading Related Entities の記事を参照してください。

147

CosisEntitiesクラスはDbContextです。 usingブロックにコンテキストを作成すると、データ指向の操作の境界が定義されます。

コードでは、メソッドからクエリの結果を出力し、メソッド内のコンテキストを終了しようとしています。結果を渡す操作は、グリッドビューに入力するためにエンティティにアクセスしようとします。グリッドにバインドするプロセスのどこかで、遅延ロードされたプロパティにアクセスし、Entity Frameworkがルックアップを実行して値を取得しようとしています。関連付けられたコンテキストが既に終了しているため、失敗します。

次の2つの問題があります。

  1. グリッドにバインドすると、エンティティを遅延読み込みします。これは、SQL Serverに対して多くの個別のクエリ操作を実行していることを意味し、すべてが遅くなります。この問題を解決するには、デフォルトで関連プロパティを積極的にロードするか、Entity Frameworkに Include 拡張メソッドを使用して、このクエリの結果にそれらを含めるように依頼します。

  2. コンテキストを時期尚早に終了しています:DbContextは、実行中の作業単位全体で使用可能であり、手元の作業が完了したときにのみ破棄する必要があります。 ASP.NETの場合、作業単位は通常、処理されているHTTP要求です。

27
Paul Turner

ボトムライン

コードは、遅延読み込みが有効になっているエンティティフレームワークを介してデータ(エンティティ)を取得し、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は遅延読み込みを無効にしておらず、コードはusingステートメントによってコンテキストが破棄された後にPetナビゲーションプロパティにアクセスしているためです。

デバッグ

この例外の原因をどのように見つけますか?例外が発生した場所で正確にスローされる例外自体を確認する以外に、Visual Studioでのデバッグの一般的なルールが適用されます:戦略的なブレークポイントを配置し、マウスをホバーすることで 変数を検査 名前の上に、(クイック)ウォッチウィンドウを開くか、LocalsやAutosなどのさまざまなデバッグパネルを使用します。

参照が設定されているか設定されていないかを知りたい場合は、その名前を右クリックして「すべての参照を検索」を選択します。その後、データを要求するすべての場所にブレークポイントを配置し、デバッガをアタッチしてプログラムを実行できます。デバッガーがそのようなブレークポイントでブレークするたびに、ナビゲーションプロパティに値を設定する必要があるかどうか、または要求されたデータが必要かどうかを判断する必要があります。

回避する方法

遅延読み込みを無効にする

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

長所:InvalidOperationExceptionをスローする代わりに、プロパティはnullになります。 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はPersonに加えてPetを具体化します。これは、データベースへの単一の呼び出しであるため有利です。 (ただし、返される結果の数と要求されたナビゲーションプロパティの数によっては、パフォーマンスに大きな問題が発生する可能性があります。この場合、両方のインスタンスが単一のレコードと単一の結合であるため、パフォーマンスの低下はありません)。

または

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はデータベースに追加の呼び出しを行うことにより、Personから独立してPetを具体化します。既定では、Entity Frameworkはデータベースから取得したオブジェクトを追跡し、それに一致するナビゲーションプロパティが見つかった場合、これらのエンティティにauto-magicallyを追加します。この場合、PetIdオブジェクトのPersonPet.Idと一致するため、Entity Frameworkは、値がペット変数に割り当てられる前に、取得したPet値にPerson.Petを割り当てます。

いつ、どのようにコードがEntity Frameworkを介してデータを要求するかをプログラマーに理解させるため、このアプローチを常にお勧めします。コードがエンティティのプロパティでnull参照例外をスローする場合、ほとんどの場合、そのデータを明示的に要求していないことを確認できます。

15
Erik Philips

非常に遅い回答ですが、遅延読み込みをオフにする問題を解決しました:

db.Configuration.LazyLoadingEnabled = false;
6
Ricardo Pontual

ASP.NET Coreを使用していて、非同期コントローラーメソッドの1つでこのメッセージが表示される理由を知りたい場合は、Taskではなくvoidを返すようにしてください。ASP.NETCoreは注入されたコンテキストを破棄します。

(この質問はその例外メッセージに対する検索結果で高く、微妙な問題であるため、この回答を投稿しています-多分それはGoogleのために役立つでしょう。)

1
John

他の回答のほとんどは、積極的な読み込みを指しますが、別の解決策を見つけました。

私の場合、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";
}

したがって、熱心なロードにこだわることはありません。

1
Zorgarath

私の場合、すべてのモデル「ユーザー」を列に渡していましたが、正しくマップされていなかったため、「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();