Смекни!
smekni.com

Менеджер подключений к базам данных (стр. 3 из 3)

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

Предположим теперь, что в нашем приложении мы создали командный объект (SqlCommand) и сохранили его для дальнейшего использования. Команда связана с определенным объектом подключения, а именно, с тем объектом, который был возвращен менеджером подключений в момент создания и первого выполнения команды. Не будем, однако, забывать, что данный объект HttpApplication при обслуживании следующего (в его хронологии) запроса, скорее всего, будет работать уже с другим рабочим потоком, а поэтому менеджер подключений вернет не то подключение, с которым связана наша команда. Хуже того, с возвращенным подключением, вероятно, будет связана аналогичная команда в другом объекте приложения.

Выход из описанной ситуации достаточно прост. Необходимо сделать такой модуль (HttpModule), который в начале обработки запроса будет связывать менеджер подключений, приписанный к данному объекту приложения, с потоком, который сейчас работает с этим приложением и со всеми подчиненными ему объектами. Это устранит все проблемы такого рода и позволит опять забыть про реальное положение дел с потоками в ASP.NET.

Код модуля предельно прост:

public class AspAdapter : IHttpModule{ private HttpApplication application; private DbManager manager; public void Init(System.Web.HttpApplication context) { application = context; manager = DbManager.Get(); application.BeginRequest += new EventHandler( OnBeginRequest ); } protected void OnBeginRequest( object sender, EventArgs e ) { manager.Init(); } public void Dispose() { application.BeginRequest -= new EventHandler( OnBeginRequest );}}

В последних примерах можно заметить ранее не упоминавшийся метод Init. Он служит для привязки данного экземпляра менеджера к вызывающему потоку.

Пожалуйста, закрывайте двери!

Общеизвестно, что при использовании пула подключений (connection pool) основополагающий принцип работы с подключениями гласит: открывай поздно, закрывай рано. Иными словами, открывать нужно перед самым использованием, а закрывать сразу после оного. При этом нужно помнить, что в блоке использования подключения к базе может произойти какая-нибудь ошибка (exception), которая помешает закрыть подключение.

Мы уже рассматривали обычный для ADO.NET способ работы с объектом подключения, примерно такой:

using( connection ) { connection.Open(); // Активнее используем подключение! Иначе, зачем открывали?! ...}

Он прост и удачен, если не жалко уничтожить объект подключения в конце блока, т.е. если каждый раз создается новый объект подключения. Если же хочется использовать один объект, код становится гораздо менее красивым:

bool wasOpened = false;if( connection.State == ConnectionState.Closed ){ connection.Open(); wasOpened = true;}try { // Используемподключение …}finally{ if( wasOpened ) connection.Close();}

Помимо общей сложности, он еще и затрудняет возможность переключения между двумя режимами работы менеджера подключений. Мы же не хотим, перекинув флаг, еще и править все блоки работы с базой.

Используем для решения тот же механизм детерминированной деструкции – интерфейс IDisposable, который столь упрощает использование подключения по стандартной схеме. Нам достаточно создать класс, который при конструировании открывал бы подключение, если это необходимо, а при уничтожении – закрывал бы, если открывал его сам. При этом мы можем сделать класс достаточно умным, чтобы он понимал, в каком режиме работает менеджер подключений. Если менеджер не кэширует объекты, наш сервисный класс будет удалять их в конце блока using.

В использовании это будет выглядеть примерно так:

using( new DbOpen( connection )) { // Используем подключение, раз открывали! …}

Отметим также, что при конструировании экземпляра класса подключение открывается автоматически. Таким образом, еще одна строка в стандартном сценарии становится ненужной.

Реализация

Данный подход уже реализован и неоднократно прошел полевые испытания. Более подробно о готовом модуле можно узнать на

http://www.byte-force.com/russian/products/tech/lsddatabase.html.

Ссылки

E. Gamma, et al., Design Pattern: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995

ПРИМЕЧАНИЕОт редакцииМногие участники редакционной коллегии, имеющие опыт работы с базами данных, неоднозначно расценивает данную статью. С одной стороны, идея инкапсуляции работы с подключениями, позволяющая получать подключения по логическим именам, хороша. Она упрощает код, тем самым снижая вероятность появления ошибок. С другой стороны, создание самодельного пула, а также реализация закрытия соединения и многопоточной работы, является успешным решением собственноручно созданной проблемы. Более того, так как кэш может возвращать один и тот же экземпляр подключения при разных вызовах, в программе может возникнуть ошибка из-за случайного (неявного) использования одного подключения в разных алгоритмах. То есть велика вероятность того, что программист в двух алгоритмах попытается создать две независимых транзакции, но поскольку соединение физически одно, это ему не удастся. Как, собственно, заметил сам автор, выигрыша в скорости такое решение не дает, и смысл кэширования просто непонятен.Таким образом, мы рекомендуем использовать на практике идею инкапсуляции работы с подключениями, но не кэширование подключений. Однако изучение этой части статьи интересно, так как в ней используются методы оптимизации, вполне применимые в других случаях.