19

我收到“创建模型时无法使用上下文”。我的一个网页中的 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 应用程序的架构无状态模型中保持某些状态(上下文)。所有这些请求都被引导到不同线程上的相同上下文,并且您正在获得竞争条件。

一个线程可能会启动上下文的首次初始化以响应请求,而另一个线程可能会尝试使用上下文。第二个请求认为上下文已准备好使用,并且您收到此异常。如果您有多个上下文尝试按照另一个 SO 线程中的建议同时“启动”,您甚至可能会得到这个。

你可以做几件事。您可以尝试对上下文的访问进行悲观锁定,但您会遇到不必要的瓶颈。您可以尝试创建某种“在客户呼叫我之前,初始化上下文”代码,但您必须找到一个好地方来执行此操作,也许使用MSDN 线程中建议的“蛮力”方法。

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

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

MSDN 文章中总结了这一点(强调我的):

如果您将实体从一层序列化到另一层,则推荐的模式是将上下文保留在中间层上的时间仅足够用于单个服务方法调用。随后的调用将启动上下文的新实例以完成每个任务。

一个关于 EF/WCF/N-tier 的帖子也可能会给您一些见解,并且 Jorge 的博客文章 #5讨论了 N-Tiers 中的 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

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

于 2012-08-28T06:37:00.697 回答
0

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

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

于 2016-12-08T13:03:08.663 回答