EF,ASP MVC +依赖注入.多个并发请求和数据库连接的问题

Ste*_*oat 8 asp.net-mvc entity-framework dependency-injection autofac nopcommerce

我正在开发一个基于NopCommerce的项目,该项目使用ASP MVC,Autofac和Entity Framework.我在从MVC Route中调用服务上的方法时会发生异常,这将使用EF调用DB.

在开发期间,一切正常 - 但是在负载测试期间,当有并发用户时,1或2个请求将崩溃,并且以下错误之一将记录到ELMAH.

System.InvalidOperationException ExecuteReader需要一个开放且可用的连接.连接的当前状态是打开的.

System.InvalidOperationException ExecuteReader需要一个开放且可用的连接.连接的当前状态已关闭.

System.InvalidOperationException未关闭连接.连接的当前状态是连接.

System.ObjectDisposedException ObjectContext实例已被释放,不能再用于需要连接的操作.

System.ObjectDisposedException由于已释放DbContext,因此无法完成操作.

System.ArgumentException已添加具有相同键的项.

我通过打开网站上的许多链接,然后使用Chrome插件刷新所有标签,模拟同时点击网站的约25个请求来测试.

该服务有两个从路径内部调用的方法,然后这些相同方法中的一个可以从控制器动作调用50次以上.有时异常是从Route内部触发的,有时它来自控制器内部.意味着路径的GetRouteData已经完成,将'flow'传递给控制器​​,然后在那里失败.但是,大多数情况下,路由中会发生异常.当异常确实从控制器内部发生时,它位于异常发生的不同行.

有时一个方法会失败,另一个方法将运行正常,然后调用堆栈中的下一个方法失败.它每次都不同,但这些方法调用使用常用方法从数据库中检索.

在此之前注册的其他2个路由映射到*{ url},它们执行传入URL的数据库查找,并且异常从未发生过.因此,这种路由不是执行任何数据库工作的第一个操作.

该服务是依赖注册的: -

builder.RegisterControllers(typeFinder.GetAssemblies().ToArray());

builder.Register<IDbContext>(c => new NopObjectContext(DataSettings.DataConnectionString)).InstancePerLifetimeScope();

builder.RegisterGeneric(typeof(EfRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();

builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance();

builder.RegisterType<MyService>().As<IMySerivce>()    
               .WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"))    
               .InstancePerLifetimeScope(); 
Run Code Online (Sandbox Code Playgroud)

Controller通过构造函数注入接收服务: -

protected readonly IMyService _myService;

public MyController(IMyService myService)
{
    _myService = myService;
}
Run Code Online (Sandbox Code Playgroud)

该路线解决了以下服务:

public override RouteData GetRouteData(HttpContextBase httpContext)
{
    var _myService = EngineContext.Current.Resolve<IMyService>();
    myService.databaseOperation(); <--- falls over here w/ concurrency
} 
Run Code Online (Sandbox Code Playgroud)

有没有想过为什么会出现这些错误?以及如何解决它们?

根据我的理解,我们的DBContext似乎是在2个请求中共享的,但是在我的依赖注册中,我告诉它要解析为生命周期得分 - 这应该是每个请求都是唯一的.我已经阅读了很多关于这个异常的原因,以及它是如何依赖于依赖注入框架来控制依赖关系的生命周期以及管理其资源的处理 - 事情就是这样,事情似乎正在崩溃.

关于服务本身,它的工作方式与应用程序中的所有其他服务一样 - 没有什么特别突出和不同的.

我已经粘贴了完整的异常堆栈跟踪http://pastebin.com/XYEwRQsv以及令人讨厌的代码行.

编辑:我在连接字符串中使用MultipleActiveResultSets = True.该服务处理的实体是独立的,即它与其他实体没有关系,因此一旦执行了查询,就不应该有多个子实体迭代,因为有关这些异常的其他答案指出了.

编辑:这是抛出异常的行.

public string GetFriendlyUrlString(string originalUrl)
{
    var friendly =  _cacheManager.Get(originalUrl.ToLower(), () =>
            (from f in _friendlyUrlRepository.ReadOnlyTable      <--------- Here
             where f.OriginalUrl == originalUrl
             select f.Url).ToList().SingleOrDefault());

    return friendly ?? originalUrl;
}
Run Code Online (Sandbox Code Playgroud)

例外是

ExecuteReader requires an open and available Connection. The connection's current state is closed.
Run Code Online (Sandbox Code Playgroud)

这太奇怪了.在我的路线中,有4个地方可以进行数据库调用.我的例外是95%的时间来自这4次呼叫中的一次 - 通常是第一次失败,但有时第一次数据库呼叫将是正常的,而其他呼叫将通过.我很少看到异常来自控制器内部,这条路线呃...路线到.再次,使用该控制器异常,然后实际的数据库连接问题发生在5行代码中的一行 - 再次表明它已经使许多数据库调用失败了.

Jam*_*ail 5

Autofac 的 InstancePerLifetimeScope 不保证每个 http 请求的唯一范围。它保证的是,在解析该组件的生命周期范围内只会有一个组件实例。\n因此,例如,如果您从根容器解析 InstancePerLifetimeScope 组件,则该组件本质上将充当单例申请的过程。

\n

如果您正在单个注册的服务(例如,全局操作过滤器)中解决您的依赖关系,那么您的依赖关系(dbContext 或其他)将不会在每个请求上得到处理 - 它只会保持打开状态,从而泄漏内存并释放连接,直到它被处理或让您的应用程序崩溃。

\n

假设您正在使用 Autofac Mvc 集成作为实验,也许尝试暂时将您的 dbContext 注册为 InstancePerMatchingLifetimeScope("AutofacWebRequest") ——这实际上相当于完全按照 Will Appleby 所说的操作,但与更新的版本没有关联Autofac 的版本比您可能使用的版本高。

\n

如果这解决了您的问题,那么基本上,@Will Appleby 是对的。您需要将所有 InstancePerLifetimeScope 组件注册为 InstancePerRequest(从版本 3.4.0 开始新添加到 Autofac 核心)或 InstancePerHttpRequest(在 3.4.0 之前的 Autofac MVC 集成中可用)。

\n

否则,如果我猜对了,你可能会得到类似这样的异常:

\n
DependencyResolutionException: No scope with a Tag matching \'AutofacWebRequest\' is visible from the scope in which the instance was requested.\n
Run Code Online (Sandbox Code Playgroud)\n

如果发生这种情况,您仍然希望将注册更改为 InstancePerRequest/InstancePerHttpRequest,但现在您手头有更深入的任务。您可能有一个单例(或其他比 httpRequest 寿命更长的对象)将您的 dbContext 劫持。您需要识别寿命较长的组件,并找出如何释放该强制依赖关系 - 这可能需要重构该组件以更好地符合 Autofac 的作用域行为。

\n

您需要参考此处的故障排除部分:\n http://autofac.readthedocs.org/en/latest/faq/per-request-scope.html#troubleshooting-per-request-dependency

\n

这是其中的摘录:

\n
\n

造成这种情况的常见原因包括:

\n
    \n
  • 应用程序注册正在跨应用程序类型共享。
  • \n
  • 单元测试正在使用真实的应用程序注册运行,但不是\xe2\x80\x99t 模拟每个请求的生命周期。
  • \n
  • 您有一个组件的生命周期长于一个请求,但它需要一种仅生命周期为一个请求的依赖项。例如,一个单例组件根据请求注册一个服务。
  • \n
  • 代码在应用程序启动期间运行(例如,在 ASP.NET Global.asax 中),当尚不存在\xe2\x80\x99t 活动请求时,该代码使用依赖项解析。
  • \n
  • 代码正在 \xe2\x80\x9c 后台线程 \xe2\x80\x9d 中运行(其中 \xe2\x80\x99s 没有请求语义),但正在尝试调用 ASP.NET MVC DependencyResolver 来执行服务定位。
  • \n
\n
\n


Ste*_*oat 2

最终的解决办法是我在 Route 类中有一个私有变量。我没有意识到 Route 对象并不是在每个请求时实例化的,并且我每次在“GetRouteData”内都初始化这个私有变量。因此,在对站点的 2 个并发请求期间,请求 A 将实例化该变量并继续执行其方法,而请求 B 进来,重新设置私有变量,这会弄乱请求 A 的一切。

我现在已经在本地限定了这个变量的范围,并将其传递给任何其他需要它的方法,我的所有问题都消失了。所以,这并不是真正的 Autofac,也不是实体框架在起作用——我没有考虑 MVC 如何处理它的路由。

感谢大家的帮助并朝着问题所在的正确方向努力。