具有单例DbContext的自定义RoleProvider可能与MVC3网站中的PerRequest DbContext冲突

时间:2012-11-09 19:30:08

标签: asp.net-mvc entity-framework-4 database-connection dbcontext roleprovider

我有一个使用Entity Framework 4.1和Ninject的MVC3应用程序。它使用标准的Repository模式,在PerRequest的基础上接受IUnitOfWork / DbContext(来自Ninject)。

该网站在单用户测试方面表现良好。我们最近开始对2个以上的用户进行一些并发负载测试,并开始针对某些请求收到此错误:

连接未关闭。连接的当前状态是连接。

System.Data.EntityException: The underlying provider failed on Open. --->   
System.InvalidOperationException: The connection was not closed. The connection's current state is connecting.
at System.Data.ProviderBase.DbConnectionBusy.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
at System.Data.SqlClient.SqlConnection.Open()
at System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String attemptedOperation, Boolean& closeStoreConnectionOnFailure)
--- End of inner exception stack trace ---
at System.Data.EntityClient.EntityConnection.OpenStoreConnectionIf(Boolean openCondition, DbConnection storeConnectionToOpen, DbConnection originalConnection, String exceptionCode, String attemptedOperation, Boolean& closeStoreConnectionOnFailure)
at System.Data.EntityClient.EntityConnection.Open()
at System.Data.Objects.ObjectContext.EnsureConnection()
at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Store.Security.StoreSecurityService.GetUserGroupRoles(String username) in C:\Dev\Store2\Trunk\Store.Security\StoreSecurityService.cs:line 57
at Store.Security.StoreSecurityService.GetRolesForUser(String username) in C:\Dev\Store2\Trunk\Store.Security\StoreSecurityService.cs:line 23
at Store.Security.StoreRoleProvider.GetRolesForUser(String username) in C:\Dev\Store2\Trunk\Store.Security\StoreRoleProvider.cs:line 37
at System.Web.Security.RolePrincipal.IsInRole(String role)
at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)
at System.Web.Mvc.AuthorizeAttribute.AuthorizeCore(HttpContextBase httpContext)
at System.Web.Mvc.AuthorizeAttribute.OnAuthorization(AuthorizationContext filterContext)
at System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor)
at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
at System.Web.Mvc.Controller.ExecuteCore()
at System.Web.Mvc.ControllerBase.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.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

我想知道这个问题是否是由于实现了MVC RoleProvider类的自定义RoleProvider引起的,该类需要访问DbContext来调用数据库。

public class StoreRoleProvider : RoleProvider
{
    public IStoreSecurityService StoreSecurityService { get; set; }

    public StoreRoleProvider()
    {
        StoreSecurityService = DependencyResolver.Current.GetService(typeof(IStoreSecurityService));
    }

    public override string[] GetRolesForUser(string username)
    {
        return StoreSecurityService.GetRolesForUser(username);
    }
}

最初,我们解决了一个注入了DbContext的IStoreSecurityService实例(PerRequest),但我知道RoleProvider只在应用程序的开头创建一次,所以DbContext将在最后处理掉请求。

我在构造函数中尝试了一个特定的实例,如下所示:

public StoreRoleProvider()
{
   StoreSecurityService = new StoreSecurityService(new DbContext());
}

但这会产生类似的错误。

抛出错误的linq查询并不是特别困难......

public IEnumerable<string> GetRolesForUser(string username)
{
    var roles = (from user in _dbContext.Set<User>()
                 join userRole in _dbContext.Set<UserRole>() 
                     on user.Id equals userRole.IserId
                 join role in _dbContext.Set<Role>() 
                     on userRole.RoleId equals role.Id
                 where user.UserName == username && !userRole.IsDeleted 
                 select role.Name).ToList<string>();
    return roles;
}

我不明白为什么连接会如此改变状态。

任何想法都将不胜感激:)

1 个答案:

答案 0 :(得分:3)

经过多次测试,我认为我已经解决了这个问题。

由于某种原因,RoleProvider不喜欢使用Singleton DbContext。

因此,作为一个不整洁的修复,我在StoreSecurityService上有两个构造函数。一个构造函数接收IUnitOfWork(包含DbContext),正如使用Ninject以正常方式解析的那样。

第二个构造函数没有参数。在代码内部,每次linq查询需要DbContext时,它直接调用ninject来解析当前的IUnitOfWork实例(即PerRequest)。它为此上下文创建一个局部变量,然后正常使用它。

在类中使用DependencyResolver会使单元测试变得更加困难,因此我可以将此GetDbContext方法重构为一个单独的类,以使其更加透明。

public class SolvencySecurityService : ISolvencySecurityService
{
    private readonly IUnitOfWork _privateContext;

    private DbContext GetDbContext()
    {
        if (_privateContext != null)
        return _privateContext;

        return DependencyResolver.Current.GetService<IUnitOfWork>();
    }

    public StoreSecurityService()
    {

    }

    public StoreSecurityService(IUnitOfWork unitOfWork)
    {
        _privateContext = unitOfWork;
    }
}