Смекни!
smekni.com

MSSQL 2005 (Yukon) – работа с очередями и асинхронная обработка данных (стр. 5 из 7)

Каждому вызову Begin* обязательно должен соответствовать вызов метода End*, небрежность может привести к утечке ресурсов.

Если на сервер будет передан ошибочный запрос, который распознается как ошибочный до начала выполнения, то исключение будет выброшено методом Begin*, в противном случае, запрос считается выполненным, и исключение выбрасывается при вызове метода End*. К этому надо быть готовым.

В текущей бета-версии метод SqlCommand.Cancel() в асинхронном режиме не поддерживается, и неизвестно, будет ли поддерживаться в релизе.

Извещение об изменениях в результатах запроса (Query Notification)

Довольно часто возникает желание уведомить клиентское приложение о том, что в базе произошли некие изменения. На самом деле такое желание возникает гораздо чаще, чем встречается реальная необходимость в подобной функциональности. Но, тем не менее, бывают случаи, когда это действительно нужно. Поэтому в MS SQL Server 2005 и ADO.Net 2.0 была реализована поддержка подобного сценария.

Со стороны SQL Server в этом предприятии участвуют собственно ядро сервера (Sql Engine), Service Broker и специальная хранимая процедура sp_DispatcherProc. Со стороны ADO.Net участвуют классы SqlNotificationRequest и SqlDependency из пространства имен System.Data.SqlClient. Кеш ASP.Net также поддерживает эту функциональность. Для этого используется класс System.Web.Caching.Cache.

В общем виде сценарий использования уведомлений об изменении запрошенных данных выглядит примерно так:

У объекта SqlCommand, который содержит запрос, в процессе его инициализации заполняется свойство Notification, которое содержит подписку на оповещение об изменениях запрошенного набора данных (это свойство (Notification) передается на сервер вместе с запросом).

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

Ядро сервера следит за всеми DML-операциями, которые могут привести к изменению результата запроса, и если сервер подозревает, что результат был изменен, ServiceBroker-у посылается специальное сообщение об этом.

Далее, в зависимости от применяемого сценария, сообщение может быть сразу отправлено клиентскому приложению или же помещено в очередь, чтобы клиентское приложение забрало его само.

ПРИМЕЧАНИЕЧтобы все это великолепие работало, необходим, во-первых, включенный Service Broker, во-вторых, пользователь, от имени которого выполняется запрос, должен обладать правами SUBSCRIBE QUERY NOTIFICATIONS, и в третьих, клиентский код должен выполняться с правами System.Data.SqlClient.SqlNotificationPermission.

SqlDependency

Для начала рассмотрим реализацию самого простого сценария, когда все работает «по умолчанию», и сервер сам извещает клиентское приложение о том, что произошли некие изменения:

using System;using System.Data;using System.Data.SqlClient;namespace Rsdn.AsyncDemo{ class DependencyTest { public void GetData() { using (SqlConnection connection = new SqlConnection( "Data Source=localhost&bsol;ctpapril;Initial Catalog=cavy;" + "Integrated Security=SSPI;")) { SqlCommand cmd = new SqlCommand("SELECT ID, [Time], + "Data FROM dbo.AsyncTest", connection);// создаем объект SqlDependency, и регистрируем его в SqlCommand// SqlDependency depend = new SqlDependency(cmd);// подписываем обработчик события на оповещение об изменениях в // результатахзапроса, выполненногочерез SqlCommand depend.OnChange += new OnChangeEventHandler(OnDataChange); connection.Open(); SqlDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) Console.WriteLine(rdr[0] + "&bsol;t" + rdr[2] + "&bsol;t" + rdr[1]); } Console.WriteLine("Press Enter to continue");Console.ReadLine(); } /// <summary> /// Обработчик события изменения данных на сервере в запрошенном наборе./// </summary> public void OnDataChange(object sender, SqlNotificationEventArgs e) { Console.WriteLine(String.Format( "{0}Result has changed{0}Source {1}{0}Type {2}{0}Info {3}{0}", Environment.NewLine, e.Source, e.Type, e.Info));// Если не случилось ошибки, то обработчик надо зарегистрировать заново // и получить новый набор данных.if (e.Info != SqlNotificationInfo.Invalid) GetData(); else Console.WriteLine("The query is invalid for notification");} }}

В данном случае после каждого изменения данных в запрошенном наборе он будет автоматически обновляться. Этот пример полностью функционален и практически готов к использованию. Обратите внимание, что вся магия, по сути, заключена в двух выделенных строчках, ну и, естественно, обработчике события изменения данных, во всем остальном пример ничем не отличается от самого простого варианта использования SqlCommand.

Как все это работает

Сначала, как уже было вкратце описано, сервер получает стандартный пакет с текстом запроса и небольшим довеском. Этот довесок содержит имя сервиса ServiceBroker-а (который будет использоваться для доставки), строку, являющуюся идентификатором извещения и величину таймаута извещения. Всех этих параметров в примере нет, они задаются неявно, при создании экземпляров класса SqlDependency. Текст запроса может содержать несколько T-SQL-запросов. Запросы могут также находиться внутри процедуры или функции. Изменения в результатах всех этих запросов будут отслеживаться. Механизм отслеживания изменений, как это ни странно, совсем не нов, он присутствует в SQL Server с прошлой версии и используется для индексированных представлений. Точно так же, как индексированное представление узнает об изменениях данных в таблицах, из которых она состоит, механизм извещения узнает о том, что изменились данные результата запроса. Механизм хороший и проверенный, но, к сожалению, обладающий рядом довольно серьезных ограничений. Практически все ограничения, накладываемые на индексированные представления, справедливы и для механизма извещений. Обратите внимание, что в примере имя таблицы в запросе включает еще и имя схемы, а имена полей перечислены явно, иначе пример не заработал бы.

ПРИМЕЧАНИЕПолный список ограничений можно найти здесь: http://msdn2.microsoft.com/library/aewzkxxh(en-us,vs.80).aspx

После того, как сервер определит, что произошли изменения, затрагивающие данные в результате запроса, в отличие от механизма индексированных представлений, копия измененных данных не создается. Вместо этого формируется сообщение для ServiceBroker-а, который использует для доставки этого сообщения адресату специально для этого созданный контракт [http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification] и сервис [http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService]. Адресатом является хранимая процедура sp_DispatcherProc, которая смотрит, кто именно на это сообщение подписан, и рассылает подписчикам извещения об изменении данных.

ПРИМЕЧАНИЕПоскольку процедура sp_DispatcherProc реализована на .Net, то для того, чтобы данный способ извещения работал, на сервере должно быть разрешено выполнение .Net-процедур.

Вся прелесть заключается в том, что клиенту нет никакой необходимости держать постоянное соединение с сервером (что хорошо видно на примере). Это извещение доставляется подписчику отдельно от подключения к базе, по HTTP или TCP/IP. В принципе протокол можно задать явно, но по умолчанию, если клиентская ОС поддерживает HTTP (как Windows 2003 или Windows XP SP2), то используется HTTP, в противном случае – TCP. Естественно, для того чтобы это работало, клиент должен быть доступен серверу по сети, что надо учитывать при развертывании подобных систем. Для более тонкой настройки при инициализации SqlDependency можно указать параметры соединения, как уже упоминалось, протокол, а также тип аутентификации (на данный момент none или Integrated) и таймаут подписки. Сейчас эти настройки передаются в конструкторе, впоследствии будут сделаны соответствующие свойства, и количество настроек, возможно, будет увеличено.

Следует учитывать, что обработчик события об изменении данных (OnDataChange() в данном примере) будет вызван из другого потока, так что надо быть готовым к тому, что вызов произойдет еще в момент получения данных основным потоком. Как только сервер доберется до клиента и доставит ему сообщение, подписка на изменения удаляется. По этой причине в данном примере подписка реализована внутри метода GetData(), который вызывается каждый раз, когда приходит извещение.

Сообщение об изменениях приходит только один раз, вне зависимости от того, сколько строк было изменено, удалено или добавлено. Сообщение также не содержит никакой информации об измененных строках и их количестве. Единственное, что известно об изменениях – в свойстве Info объекта SqlNotificationEventArgs содержится информация о том, какие именно действия привели к посылке сообщения об изменении данных. При посылке извещения сервер предпочитает подстраховаться и послать сообщение лишний раз, чем не послать его вообще. Сообщение будет послано не только в случае реального изменения данных, но и если одна из таблиц, участвующих в запросе, была удалена, изменена или обрезана, и даже в том случае, когда выполнение DML-оператора над запрошенным набором не привело к реальному изменению данных, например: UPDATE tbl SET a = a WHERE b = @XСтоит сказать пару слов об обработке ошибок. Дело в том, что если на сервер будет послан корректный с точки зрения T-SQL запрос, отследить изменения для которого по каким-либо причинам невозможно (например, запрос не удовлетворяет строгим ограничениям внутреннего механизма извещения), то исключение сгенерировано не будет, а просто немедленно будет вызван обработчик изменения данных с признаком Invalid Query. Поэтому в реальных приложениях обработчик обязательно должен учитывать подобный вариант развития событий.

Полный список возможных причин посылки извещения и вероятных ошибок можно увидеть в содержимом enum-а SqlNotificationInfo. При формировании запроса, с целью экономии ресурсов и повышения производительности механизма извещений, рекомендуется использовать параметры, так как в этом случае сервер может сформировать один запрос на извещение для нескольких одинаковых запросов с разными параметрами. Это не означает, что клиенты будут получать извещения при изменении данных, затрагивающих чужой запрос, они по-прежнему будут получать извещения об изменениях, которые затрагивают только их данные. Например, есть два клиента, сформировавших два запроса и пославших их серверу одновременно: