EF – 在HTTP请求期间创建模型exception时,不能使用上下文

我收到“在创建模型时无法使用上下文。” 在我的一个网页中的Web应用程序中出现问题。 此特定网页每2-3秒POST一次,以刷新屏幕。 根据我的测试,我发现如果我有2个或更多的浏览器实例打开到这个页面,几分钟后我收到一个“创建模型时不能使用上下文”来自存储库深处的exception。

此代码调用“服务”来检索所需的数据。 此代码在MVC Controller类的自定义授权属性中执行。

// 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 Stations { get; set; } } 

以下是上述服务调用的代码:

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

这是存储库….发生错误的地方

  public class Repository : IRepository where TObject : class { protected MyContext Context = null; public Repository(IDataContext context) { Context = context as MyContext; } protected DbSet DbSet { get { return Context.Set(); } } public virtual TObject FirstOrDefault(Expression<Func> 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(predicate); return objectSet.Where(predicate).AsNoTracking().FirstOrDefault(predicate); } } 

错误的屏幕截图: 发生错误的屏幕截图

Stacktrace

  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(代码优先)

您的存储库是短暂的(您为每次调用GetRoomStation()创建它,但您的实际上下文似乎是长期存在的( RoomServiceStation.Context属性)。这意味着对您的Web应用程序的每次调用都将使用相同的上下文。

这是“N层中的EF”场景,您尝试在Web应用程序的架构无状态模型中保留有状态(上下文)。 所有这些请求都被引导到不同线程上的相同上下文中,并且您正在获得竞争条件。

一个线程可以启动您的上下文的第一次初始化以响应请求,另一个线程可以尝试使用上下文。 第二个请求认为上下文已准备就绪,您将获得此exception。 如果你有多个上下文试图在另一个SO线程中建议的同时“旋转”,你甚至可能得到这个。

你可以做一些事情。 您可以尝试使用悲观锁定来访问您的上下文,但是您会遇到不必要的瓶颈。 您可以尝试创建某种“在客户端调用我之前,初始化上下文”代码,但您必须找到一个好的地方来执行此操作,可能使用MSDN线程中建议的“powershell”方法。

更好的办法是简单地为后端服务的每个请求创建一个新的上下文。 有一些开销,是的,但很少。 与悲观锁定相比,开销可能不太可能导致性能下降,并且不会受到应用程序池回收事件的影响,这些事件会扩展到服务器场上的Web应用程序等等。

如果您依赖于更改跟踪或上下文的其他有状态性质,您将失去此权益。 在这种情况下,您将不得不提出一种不同的机制来跟踪和最小化数据库命中。

从MSDN文章中总结出来(强调我的):

如果将实体从一个层序列化到另一个层,建议的模式是将中间层上下文保持足够长的时间以便进行单个服务方法调用。 后续调用将启动上下文的新实例以完成每个任务。

EF / WCF / N-tier上的一个post也可能会给你一些见解 ,Jorge的博客文章#5讨论了N-Tiers中的EF(整个系列可能是一个很好的阅读)。 顺便说一下,我遇到了同样的事情:许多客户同时触及上下文,导致了这个问题。

我遇到了这个错误,似乎已经通过为控制器中的Dispose()方法提供覆盖来解决它。 看起来强制在尝试打开新数据库之前关闭数据库连接会破坏此错误。

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

这似乎是两件事之一,某种竞争条件或“背景范围”问题。 您应该确保以线程安全的方式初始化上下文,并且不同的线程不访问上下文以防止竞争条件。 很难捕获这个错误的原因也是在OnModelCreation覆盖中访问模型本身。

我今天遇到了这个问题。 问题是我不小心在请求中使用了相同的DbContext实例。 第一个请求将创建实例并开始构建模型,第二个请求将进入并尝试在数据仍在构建时检索数据。

我的错误是愚蠢的。 我不小心使用了HttpContext.Current.Cache而不是HttpContext.Current.Items 🙂