具有持久HTTP连接的IDbConnection生命周期管理

时间:2022-05-22 03:52:20

I have a problem managing the lifetime of open database connections with StructureMap scoped to HttpContext when there are persistent HTTP connections in my ASP.NET MVC application, like SignalR hubs.

当我的ASP.NET MVC应用程序中存在持久HTTP连接(如SignalR集线器)时,我在管理具有作用于HttpContext的StructureMap的开放数据库连接的生命周期时遇到问题。

My DI container, StructureMap, injects an open IDbConnection into several services. To ensure that these database connections are closed and properly disposed of, I call ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects() on the EndRequest event.

我的DI容器StructureMap将一个打开的IDbConnection注入到多个服务中。为确保关闭并正确处理这些数据库连接,我在EndRequest事件上调用ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects()。

This works great for MVC controllers until a service requiring a database connection is injected into a SignalR hub, which keeps a persistent HTTP connection open for each client and eventually saturates the connection pool.

这对于MVC控制器非常有用,直到需要数据库连接的服务注入到SignalR集线器中,从而为每个客户端保持持久的HTTP连接,并最终使连接池饱和。

If I scope IDbConnection to a singleton, only one connection is ever opened per-application and the pool doesn't saturate, but this is a bad idea in case the connection is ever locked or times out.

如果我将IDbConnection范围限定为单例,则每个应用程序只打开一个连接,并且池不会饱和,但如果连接被锁定或超时,这是一个坏主意。

So maybe there is a way to customise the scope of database connections for my SignalR hubs? I tried resolving a service instance in each Hub method, but this still instantiates a database connection at the HttpContext scope and keeps it open for the duration of the calling client's hub connection.

那么也许有一种方法可以自定义SignalR集线器的数据库连接范围?我尝试在每个Hub方法中解析服务实例,但这仍然在HttpContext范围内实例化数据库连接,并在调用客户端的集线器连接期间保持打开状态。

How should I manage the lifetime of database connections with StructureMap in an HTTP-scoped context when there are persistent HTTP connections around?

当存在持久的HTTP连接时,如何在HTTP范围的上下文中管理与StructureMap的数据库连接的生命周期?

Example Code

Typical Service

public class MyService
{
    private IDbConnection _con;
    public MyService(IDbConnection con)
    {
        _con = con;
    }

    public IEnumerable<string> GetStuff()
    {
        return _con.Select<string>("SELECT someString FROM SomeTable").ToList();
    }
}

Typical SignalR Hub

public class MyHub : Hub
{
    private MyService _service;
    public MyHub(MyService service)
    {
        _service = service; // Oh Noes! This will open a database connection
                            // for each Client because of HttpContext scope
    }

    public Task AddMessage()
    {
        var result = _service.GetStuff();
        // ...
    }
}

StructureMap Configuration

For<IDbConnection>()
    .HybridHttpOrThreadLocalScoped()
    .Use(() => BaseController.GetOpenConnection(MyConnectionString));

Global.asax.cs

public class GlobalApplication : System.Web.HttpApplication
{
    public GlobalApplication()
    {
        EndRequest += delegate
        {
            ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
        };
    }
    // ...
 }

2 个解决方案

#1


1  

In SignalR 1.0.0 Alpha, Hub's implement IDisposable. SignalR Hub instances are ephemeral unlike the HttpContext, so if you close your your IDbConnection in the Hub's Dispose method, you shouldn't unnecessarily saturate your connection pool.

在SignalR 1.0.0 Alpha中,Hub的实现IDisposable。与HttpContext不同,SignalR Hub实例是短暂的,因此如果您在Hub的Dispose方法中关闭IDbConnection,则不应该不必要地使连接池饱和。

#2


1  

Solution using transient database connection and nested StructureMap container

First, configure a named, transient database connection instance in StructureMap:

首先,在StructureMap中配置命名的瞬态数据库连接实例:

For<IDbConnection>()
    .Transient() // scope
    .Add(x => BaseController.GetOpenConnection(connectionString, IsDebugging()))
    .Named("Transient");

Make sure you configure this before your default instance, or it will override the default instance.

确保在默认实例之前配置它,否则它将覆盖默认实例。

Secondly, inject an IContainer into your SignalR hub so you can build a nested StructureMap container:

其次,将IContainer注入SignalR中心,以便构建嵌套的StructureMap容器​​:

public class JobHub : Hub
{
    private readonly IContainer _container;

    public JobHub(IContainer container)
    {
        _container = container;
    }

    public Task DoStuff(string input)
    {
        // ...

Instantiate a nested container in your SignalR method and resolve your named transient database connection:

在SignalR方法中实例化嵌套容器并解析命名的瞬态数据库连接:

        using (var httpRequestScope = _container.GetNestedContainer())
        {
            var transientConnection =
                    httpRequestScope.GetInstance<IDbConnection>("Transient");

Use .With<IDbConnection>(transientConnection) to ensure services and repositories instantiated by your nested container use this connection:

使用。使用 (transientConnection)确保嵌套容器实例化的服务和存储库使用此连接:

            var myService = httpRequestScope
                .With<IDbConnection>(transientConnection)
                .GetInstance<MyService>();

            var result = myService.DoStuff(input);

            return Clients.addResult(result);
        }
    }
}

Finally, the scoped using (...) statement will ensure that your nested container cleans up after itself, including the database connection.

最后,使用作用域(...)语句将确保嵌套容器自行清理,包括数据库连接。

The downside here is that you are opening and closing a database connection for every SignalR method call, but since connections are pooled, releasing early may not be so bad. Your mileage should depend on your SignalR request volume.

这里的缺点是你为每个SignalR方法调用打开和关闭数据库连接,但由于连接是合并的,因此提前释放可能不会那么糟糕。您的里程数应取决于您的SignalR请求量。

You may be able to ditch the nested container and just ask DependencyResolver.Current for the named connection instance, but then you may have to remember to explicitly close each connection to prevent a leak.

您可以抛弃嵌套容器,只需要为命名连接实例询问DependencyResolver.Current,但是您可能必须记住明确关闭每个连接以防止泄漏。

#1


1  

In SignalR 1.0.0 Alpha, Hub's implement IDisposable. SignalR Hub instances are ephemeral unlike the HttpContext, so if you close your your IDbConnection in the Hub's Dispose method, you shouldn't unnecessarily saturate your connection pool.

在SignalR 1.0.0 Alpha中,Hub的实现IDisposable。与HttpContext不同,SignalR Hub实例是短暂的,因此如果您在Hub的Dispose方法中关闭IDbConnection,则不应该不必要地使连接池饱和。

#2


1  

Solution using transient database connection and nested StructureMap container

First, configure a named, transient database connection instance in StructureMap:

首先,在StructureMap中配置命名的瞬态数据库连接实例:

For<IDbConnection>()
    .Transient() // scope
    .Add(x => BaseController.GetOpenConnection(connectionString, IsDebugging()))
    .Named("Transient");

Make sure you configure this before your default instance, or it will override the default instance.

确保在默认实例之前配置它,否则它将覆盖默认实例。

Secondly, inject an IContainer into your SignalR hub so you can build a nested StructureMap container:

其次,将IContainer注入SignalR中心,以便构建嵌套的StructureMap容器​​:

public class JobHub : Hub
{
    private readonly IContainer _container;

    public JobHub(IContainer container)
    {
        _container = container;
    }

    public Task DoStuff(string input)
    {
        // ...

Instantiate a nested container in your SignalR method and resolve your named transient database connection:

在SignalR方法中实例化嵌套容器并解析命名的瞬态数据库连接:

        using (var httpRequestScope = _container.GetNestedContainer())
        {
            var transientConnection =
                    httpRequestScope.GetInstance<IDbConnection>("Transient");

Use .With<IDbConnection>(transientConnection) to ensure services and repositories instantiated by your nested container use this connection:

使用。使用 (transientConnection)确保嵌套容器实例化的服务和存储库使用此连接:

            var myService = httpRequestScope
                .With<IDbConnection>(transientConnection)
                .GetInstance<MyService>();

            var result = myService.DoStuff(input);

            return Clients.addResult(result);
        }
    }
}

Finally, the scoped using (...) statement will ensure that your nested container cleans up after itself, including the database connection.

最后,使用作用域(...)语句将确保嵌套容器自行清理,包括数据库连接。

The downside here is that you are opening and closing a database connection for every SignalR method call, but since connections are pooled, releasing early may not be so bad. Your mileage should depend on your SignalR request volume.

这里的缺点是你为每个SignalR方法调用打开和关闭数据库连接,但由于连接是合并的,因此提前释放可能不会那么糟糕。您的里程数应取决于您的SignalR请求量。

You may be able to ditch the nested container and just ask DependencyResolver.Current for the named connection instance, but then you may have to remember to explicitly close each connection to prevent a leak.

您可以抛弃嵌套容器,只需要为命名连接实例询问DependencyResolver.Current,但是您可能必须记住明确关闭每个连接以防止泄漏。