如何避免EF“在创建模型时无法使用上下文”的错误?

30

看着我的 Elmah 错误日志,我发现了一些来自 Entity Framework 的 InvalidOperationException,它们涉及到:

The context cannot be used while the model is being created.

这是使用来自Nuget的最新EF CodeFirst库。我在网上找到的唯一信息是,它是由于将数据上下文设置为单例而导致的,但这绝对不是我的情况。在我的Windsor安装程序中,我的EF工作单元结构被注册为:

container.Register(Component.For<IUnitOfWork>()
                            .ImplementedBy<EFUnitOfWork>()
                            .LifeStyle
                            .PerWebRequest);

我可以通过在VS中按下F5开始调试会话并在IIS正在启动时加载第二个网页来重现此错误。

我怀疑是因为当Asp.net由于缺乏活动而被卸载时,用户正在尝试访问系统,这很有道理,因为我的产品目前处于非常小的beta测试阶段。但是,由于真正的用户正在使用具有实时数据的网站,因此我需要尽可能减少错误发生的次数。

有人有什么办法可以防止这种情况发生吗?


编辑: 我已更新我的windsor控制器,现在包含以下代码:

        container.Register(Component.For<IUnitOfWork>().ImplementedBy<EFUnitOfWork>().LifeStyle.PerWebRequest);
        using (var context = new MyJobLeadsDbContext())
        {
            context.Set<UnitTestEntity>().Any();
        }

然而,当我尝试在IIS加载应用程序时执行第二个Web请求时,之前的错误仍然会发生。


编辑2: 如要求所示,以下是堆栈跟踪:

   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.get_InternalContext()
   at System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
   at System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
   at MyApp.DomainModel.Queries.Users.UserByEmailQuery.Execute() in C:\Users\KallDrexx\Documents\Projects\MyApp\MyApp.DomainModel\Queries\Users\UserByEmailQuery.cs:line 44
   at MyApp.Infrastructure.MyAppMembershipProvider.GetUser(String email, Boolean userIsOnline) in C:\Users\KallDrexx\Documents\Projects\MyApp\MyApp\Infrastructure\MyAppMembershipProvider.cs:line 102
   at System.Web.Security.Membership.GetUser(String username, Boolean userIsOnline)
   at System.Web.Security.Membership.GetUser()
   at MyApp.MyAppBaseController.Initialize(RequestContext requestContext) in C:\Users\KallDrexx\Documents\Projects\MyApp\MyApp\MyAppBaseController.cs:line 23
   at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
   at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__5()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.<MakeVoidDelegate>b__0()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
   at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
   at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
   at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
   at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

这很有趣。能找到更多关于问题的细节吗?一旦模型被创建,处理请求的线程应该被阻塞,因此上下文不应该在使用中。 - Ladislav Mrnka
我不知道如何查找有关此问题的更多详细信息。在Visual Studio的调试线程完成加载后,我不再看到这个错误。它似乎只会在网页的第一次加载时出现,如果同时发生两个并行调用。 - KallDrexx
两个同时的调用?您能验证每个请求都有新的上下文实例吗? - Ladislav Mrnka
混合生活方式在多任务场景下可能会有所帮助。http://stackoverflow.com/questions/7203745/windsor-composite-lifestyle-for-asp-net-process - Soren
1
如果数据库无法访问(例如未启动),您可能会收到此错误。 - Matthew
我也遇到过类似的错误。我的unitOfWork类和ABC类都在通过windsor castle DI容器实例化。ABC类通过unitOfWork类使用dbContext对象。UnitOfWork类的注册方式与你的完全相同,即LifeStyle.PerWebRequest,但问题出在我在DI容器初始化时注册ABC类的方式上。我写的代码是像这样的 Register(Component.For<IABC>().ImplementedBy<ABC>() .LifeStyle.Transient ),我忽略了加粗的代码所以导致了这个问题。 - RBT
3个回答

17

最终我弄清了这个问题的真正原因,至少对我来说是这样。

问题在于,在我的自定义Asp.Net会员提供程序中,我从Windsor检索了一个DbContext。这引起了问题,因为成员资格提供程序的生命周期为整个应用程序,而所有其他检索数据库上下文的调用都是特定Web请求的新数据库上下文。这意味着同时“启动”了两个数据库上下文,因此抛出了这个错误。

这也导致了许多难以调试的实体缓存问题,因此任何在其成员资格提供程序中使用EF的人都需要非常小心地处理上下文生命周期。


编辑:针对DotNetWise的回答,我通过将Windsor连接工厂存储在我的构造函数中并始终从工厂中检索我的EF数据上下文来强制使我的自定义会员提供者始终使用一个EF连接来解决这个问题。

例如:

public class CustomMembershipProvider : MembershipProvider
{
    private IServiceFactory _serviceFactory;

    public CustomMembershipProvider() : this(null) { }

    public CustomMembershipProvider(IServiceFactory factory)
    {
        // IF no factory was provided, we need to get one from the bootstrapper
        if (factory == null)
            _serviceFactory = new WindsorServiceFactory(Bootstrapper.WindsorContainer);
        else
            _serviceFactory = factory;
    }

    public override string ResetPassword(string email, string answer)
    {
        var unitOfWork = GetUnitOfWork();
        return new ResetUserPasswordCommand(unitOfWork).WithUserEmail(email).Execute();
    }

    private IUnitOfWork GetUnitOfWork()
    {
       return _serviceFactory.GetService<IUnitOfWork>();
    }
}

这个想法是,成员提供程序执行的任何操作都会从Windsor获得UnitOfWork类,并使用它来执行操作(在这种情况下,我的UnitOfWork类是一个存储库持有者,用于封装我的EF数据上下文)。


3
很抱歉我之前没写清楚。我解决了这个问题,方法是让我的每一个会员提供者方法都向Windsor发出一个新的请求来获取我的DbContext,并且从未在该方法之外使用相同的DBContext。 在此之前,我是通过我的会员提供者构造函数请求DbContext并将其存储以备后用,这导致了问题。 - KallDrexx
1
不好意思打扰大家。它失败了,因为我的 POCO 类的某个属性上有错误属性 [Column("ToDoItem", TypeName = "NVARCHAR(2000)")]。varchar 和 nvarchar 数据类型的长度需要通过 stringLength 属性明确指定。如果不是 MAX 长度,则不能在 Column 属性内联指定。 - RBT

7

我在一个多线程的WPF应用程序中遇到了同样的问题。

我的解决方法是从Windsor安装程序强制进行DbContext初始化:

container.Register(Component.For(TheDbContext.Blah.Blah));
using (var context = new TheDbContext())
      context.Set<SomeRandomEntity>().Any();

我认为这可以算作EF的一个bug:他们应该使用线程安全的(通过锁或其他方式)代码进行DbContext初始化。

当然,更好的解决方案是NHibernate所做的:SessionFactory是一个明确创建的、与Session分开的对象。


调用 container.Resolve<IUnitOfWork>() 并进行数据库调用不幸不能解决这个问题。我同意你的观点,EF 应该在初始化时是线程安全的,但它并不是真正的线程安全,这真的很烦人。 - KallDrexx
好的,我尝试不使用我的工作单元而是直接与 DbContext 交互。现在我的第二个请求在相同的代码行上出现了 The underlying provider failed on Open. 的错误,所以这对我仍然没有用 :( - KallDrexx
@KallDrexx:请发布完整的异常信息和堆栈跟踪。 - Diego Mijelshon
我已经发布了“在创建模型时无法使用上下文”的完整堆栈跟踪异常。 - KallDrexx
我刚刚像Diego一样强制容器初始化,一切都很顺利,不同的是我使用了一个假查询:dbcontext.Set<User>().Select(q => q.Id) - Soren
显示剩余7条评论

3

当我遇到这个问题时,发现是数据库连接出了问题。

我纠正了我的EntityFramework数据库连接字符串,一切都好了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接