19

「モデルの作成中はコンテキストを使用できません」というメッセージが表示されます。私のWebページの1つにある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);
    }
}

エラーのスクリーンショット: エラー発生時のスクリーンショット

スタックトレース:

  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 (コードファースト)

4

4 に答える 4

30

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

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

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

あなたはいくつかのことをすることができます。コンテキストへのアクセスを悲観的にロックすることもできますが、不必要なボトルネックが発生しています。「クライアントが私を呼び出す前に、コンテキストを初期化する」コードを作成してみることができますが、MSDNスレッドで提案されている「ブルートフォース」メソッドを使用して、これを行うのに適した場所を見つける必要があります。

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

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

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

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

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

于 2012-08-28T19:52:17.490 に答える
1

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

protected override void Dispose(bool disposing)
{
   if(disposing)
   {
        _fooRepository.Dispose();
   }
   base.Dispose(disposing);
}
于 2015-12-16T03:05:40.010 に答える
0

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

于 2012-08-28T06:37:00.697 に答える
0

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

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

于 2016-12-08T13:03:08.663 に答える