Управление жизненным циклом IDbConnection с постоянными HTTP-соединениями

У меня проблема с управлением временем жизни открытых соединений с базой данных с помощью StructureMap HttpContext когда в моем приложении ASP.NET MVC есть постоянные HTTP-соединения, такие как концентраторы SignalR.

Мой DI-контейнер, StructureMap, внедряет открытый IDbConnection на несколько услуг. Чтобы убедиться, что эти подключения к базе данных закрыты и правильно удалены, я звоню ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects() на EndRequest событие.

Это прекрасно работает для контроллеров MVC до тех пор, пока сервис, требующий подключения к базе данных, не будет внедрен в концентратор SignalR, который поддерживает постоянное соединение HTTP открытым для каждого клиента и в конечном итоге насыщает пул соединений.

Если я сфера IDbConnection для одного приложения только одно соединение открывается для каждого приложения, и пул не насыщается, но это плохая идея, если соединение когда-либо заблокировано или время ожидания истекло.

Так что, возможно, есть способ настроить область соединений с базой данных для моих хабов SignalR? Я попытался разрешить экземпляр службы в каждом методе Hub, но он все еще создает экземпляр подключения к базе данных в области HttpContext и сохраняет его открытым в течение всего времени подключения к концентратору вызывающего клиента.

Как мне управлять временем жизни соединений с базой данных с помощью StructureMap в контексте HTTP, когда вокруг существуют постоянные соединения HTTP?

Пример кода

Типичный Сервис

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();
    }
}

Типичный 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

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

Global.asax.cs

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

2 ответа

Решение

Решение с использованием временного подключения к базе данных и вложенного контейнера StructureMap

Сначала настройте именованный временный экземпляр подключения к базе данных в StructureMap:

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

Убедитесь, что вы настроили это перед вашим экземпляром по умолчанию, иначе он переопределит экземпляр по умолчанию.

Во-вторых, ввести IContainer в концентратор SignalR, чтобы вы могли построить вложенный контейнер StructureMap:

public class JobHub : Hub
{
    private readonly IContainer _container;

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

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

Создайте вложенный контейнер в методе SignalR и разрешите свое именованное временное соединение с базой данных:

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

использование .With<IDbConnection>(transientConnection) чтобы убедиться, что службы и репозитории, созданные вашим вложенным контейнером, используют это соединение:

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

            var result = myService.DoStuff(input);

            return Clients.addResult(result);
        }
    }
}

Наконец, область действия using (...) инструкция будет гарантировать, что ваш вложенный контейнер очищается после себя, включая соединение с базой данных.

Недостатком здесь является то, что вы открываете и закрываете соединение с базой данных для каждого вызова метода SignalR, но, поскольку соединения объединяются, раннее освобождение может быть не таким уж плохим. Ваш пробег должен зависеть от объема вашего запроса SignalR.

Вы можете отказаться от вложенного контейнера и просто спросить DependencyResolver.Current для именованного экземпляра соединения, но тогда вам может потребоваться явно закрыть каждое соединение, чтобы предотвратить утечку.

В SignalR 1.0.0 Alpha, Hubвнедрить IDisposable, SignalR Hub экземпляры эфемерны в отличие от HttpContextтак что если вы закроете свой IDbConnection в Hub"s Dispose метод, вы не должны излишне насыщать свой пул соединений.

Другие вопросы по тегам