web-dev-qa-db-ja.com

EF-モデルがHTTPリクエスト中に例外を作成している間はコンテキストを使用できません

「モデルの作成中はコンテキストを使用できません」というメッセージが表示されます。いずれかのWebページのWebアプリケーションで問題が発生します。この特定のWebページは、画面を更新するために2〜3秒ごとにサーバーにPOSTします。テストの結果、このページに2つ以上のブラウザインスタンスを開いていると、数分後にリポジトリの深い場所から「モデルの作成中はコンテキストを使用できません」という例外が発生することがわかりました。

このコードは「サービス」を呼び出して、必要なデータを取得します。このコードは、MVCコントローラークラスのカスタム認証属性で実行されます。

// Code in custom "Authorization" attribute on the controller
int? stationId = stationCookieValue;  // Read value from cookie
RoomStationModel roomStationModel = RoomStationService.GetRoomStation(stationId); // Error occurs inside this call

こちらが「RoomStationModel」

public class RoomStationModel
{
    [Key]
    public int RoomStationId { get; set; }

    public int? RoomId { get; set; }
    [ForeignKey("RoomId")]
    public virtual RoomModel Room { get; set; }
    /* Some other data properties.... */
 }

public class RoomModel
{
    [Key]
    public int RoomId { get; set; }

    public virtual ICollection<RoomStationModel> Stations { get; set; }
}

上記のサービス呼び出しのコードは次のとおりです。

public RoomStationModel GetRoomStation(int? roomStationId)
{
    RoomStationModel roomStationModel = null;
    if (roomStationId.HasValue)
    {
        using (IRepository<RoomStationModel> roomStationRepo = new Repository<RoomStationModel>(Context))
        {
            roomStationModel = roomStationRepo.FirstOrDefault(rs => rs.RoomStationId == roomStationId.Value, false, new string[] { "Room" });
        }
    }

    return roomStationModel;
}

ここにリポジトリがあります...エラーが発生する場所

    public class Repository<TObject> : IRepository<TObject> where TObject : class
    {
        protected MyContext Context = null;

        public Repository(IDataContext context)
        {
            Context = context as MyContext;
        }

        protected DbSet<TObject> DbSet { get { return Context.Set<TObject>(); } }

    public virtual TObject FirstOrDefault(Expression<Func<TObject, bool>> predicate, bool track = true, string[] children = null)
    {
        var objectSet = DbSet.AsQueryable();

        if (children != null)
            foreach (string child in children)
                objectSet = objectSet.Include(child);

        if (track)
            return objectSet.Where(predicate).FirstOrDefault<TObject>(predicate);

        return objectSet.Where(predicate).AsNoTracking().FirstOrDefault<TObject>(predicate);
    }
}

エラーのスクリーンショット: Screenshot of error occurring

スタックトレース

  at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.Initialize()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.Include(String path)
   at System.Data.Entity.Infrastructure.DbQuery`1.Include(String path)
   at System.Data.Entity.DbExtensions.Include[T](IQueryable`1 source, String path)
   at Vanguard.AssetManager.Data.Repository`1.FirstOrDefault(Expression`1 predicate, Boolean track, String[] children) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Data\Repository.cs:line 100
   at Vanguard.AssetManager.Services.Business.RoomStationService.GetRoomStation(Nullable`1 roomStationId) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Services\Business\RoomStationService.cs:line 61
   at Vanguard.AssetManager.Web.Attributes.RoomStationAuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext) in C:\Work\VanguardAssetManager\Main\Vanguard.AssetManager.Web\Attributes\RoomStationAuthorizeAttribute.cs:line 52
   at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
   at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)

EFバージョン:4.1(コードが最初)

19
contactmatt

リポジトリは短命です(GetRoomStation()を呼び出すたびに作成しますが、実際のコンテキストは長命(RoomServiceStation.Contextプロパティ)。これは、Webアプリケーションへのすべての呼び出しが同じコンテキストを使用することを意味します。

これは、「N層のEF」シナリオであり、Webアプリケーションのアーキテクチャ的にステートレスなモデルで、ステートフルなもの(コンテキスト)を維持しようとしています。これらのリクエストはすべて、異なるスレッドの同じコンテキストに送信され、競合状態が発生しています。

1つのスレッドが要求に応じてコンテキストの最初の初期化を開始する可能性があり、別のスレッドがコンテキストを使用しようとする場合があります。 2番目のリクエストは、コンテキストを使用する準備ができていると考え、この例外を受け取ります。提案されているように、同時に「スピンアップ」しようとしている複数のコンテキストがある場合、これを取得することもできます 別のSOスレッド

いくつかのことができます。コンテキストへのアクセスに関してペシミスティックなロックを試みることもできますが、不必要なボトルネックを抱えています。ある種の「クライアントが私に電話をかける前に、コンテキストを初期化する」コードを作成してみることができますが、おそらく「ブルートフォース」メソッドを使用して、これを行うのに適した場所を見つける必要があります MSDNスレッドで推奨

より良いことは、単にバックエンドサービスへのeveryリクエストの新しいコンテキストを作成することです。オーバーヘッドはありますが、最小限です。オーバーヘッドはおそらく悲観的ロックよりもパフォーマンスを低下させる可能性が低く、ファームなどでWebアプリをスケールアウトするアプリプールリサイクルイベントの影響を受けません。

変更の追跡やコンテキストのその他のステートフルな性質に依存している場合、この利点は失われます。この場合、データベースヒットを追跡して最小化するための別のメカニズムを考え出す必要があります。

MSDNの記事 からこれは要約されます(私の強調):

エンティティをある層から別の層にシリアル化する場合、推奨されるパターンは、単一のサービスメソッド呼び出しに十分な長さだけ中間層でコンテキストを維持することです後続の呼び出しは、コンテキストの新しいインスタンスを起動して、各タスクを完了します。

EF/WCF/N層のスレッドもいくつかの洞察を提供する可能性があります 、そしてJorgeの ブログ投稿#5 N層のEFについて話します(シリーズ全体がよく読んでください)。ちなみに、私はまったく同じことに遭遇しました。多くのクライアントが同時にコンテキストにアクセスして、この問題が発生しました。

30
Kit

このエラーが発生し、コントローラーのDispose()メソッドをオーバーライドすることで解決したようです。新しい接続を開こうとする前にデータベース接続を強制的に閉じると、このエラーが発生します。

protected override void Dispose(bool disposing)
{
   if(disposing)
   {
        _fooRepository.Dispose();
   }
   base.Dispose(disposing);
}
1
Matt Broyles

今日、この問題を経験しました。問題は、リクエスト間で誤ってDbContextの同じインスタンスを使用していたことでした。最初のリクエストはインスタンスを作成し、モデルの構築を開始します。2番目のリクエストは、構築中にデータを取得しようとします。

私の間違いはばかげていた。 HttpContext.Current.Itemsの代わりにHttpContext.Current.Cacheを誤って使用しました:)

0
Peter Morris

これは、ある種の競合状態または「コンテキストスコーピング」の問題の2つのうちの1つに見えます。競合状態を防ぐために、コンテキストがスレッドセーフな方法で初期化されていること、およびコンテキストが別のスレッドによってアクセスされていないことを確認する必要があります。このエラーの原因を把握するのが難しいのは、OnModelCreationオーバーライドでモデル自体にアクセスすることでもあります。

0
JTMon