XML в MS SQL Server 2000 и технологиях доступа к данным - SQL Server как Web-сервис

ОГЛАВЛЕНИЕ

SQL Server как Web-сервис

Web-сервис - это компонент, к которому можно обращаться с помощью XML. Результаты вызова метода также оборачиваются в XML. Протокол устройства XML-сообщений, которыми обмениваются клиент и Web-сервис, называется SOAP. В отличие от RPC и других бинарных, зависимых от платформы протоколов, ориентированных на плотное взаимодействие, SOAP предусматривает работу в слабосвязанных средах (Интернет) и, будучи основан на XML, позволяет общаться компонентам независимо от операционной системе или инструмента разработки, в которых они были созданы. Потребность в удаленном взаимодействии компонентов через Интернет назрела давно. В свое время HTML-формы и ASP превратили посетителя Web-узла из пассивного зрителя, просматривающего заранее подготовленные странички, в активного участника, формирующего страницу по информации из базы на основе своих критериев. Однако эта информация доставлялась потребителю по-прежнему в HTML-формате, что, может быть, удобно с точки зрения визуального восприятия, но никуда не годится, если вы хотите, например, выцепить из полученной странички котировки акций, превратить их в recordset и передать своему приложению. Web-сервисы публикуют описания своих интерфейсов в wsdl-файлах (аналог библиотеки типов в СОМ). Например, методу, возвращающему котировки, нужно подать на вход интересующую дату и биржевой символ. Чтобы найти подходящий Web-сервис или выяснить назначение найденного, можно использовать универсальный реестр UDDI и протокол DISCO.

В Visual Studio.Net входят средства создания как самих Web-сервисов, так и клиентов, их использующих. Например, не составляет труда создать Web-сервис, который будет принимать имя и параметры хранимой процедуры на SQL Server, выполнять ее и отсылать результаты клиенту. Словом, типичная трехуровневая архитектура, только клиентская компонента, компонента бизнес-логика и сервер теперь не ограничены пределами локальной сети, а находятся где угодно в Интернете. Начиная с SQLXML 3.0 в роли Web-сервиса может выступать SQL Server 2000, так что надобность в промежуточной компоненте теперь отпадает. Присутствие ПО промежуточного слоя наиболее часто оправдывают требованиями безопасности, масштабируемости и программируемости. Последние два, вероятно, наиболее честные, потому что я с трудом понимаю, как толщина middleware может влиять на устойчивость сервера к попыткам несанкционированного проникновения. Если вы дыряво настроили политики безопасности на сервере, то злоумышленник все равно на него придет независимо от того, 5 шагов ему перед этим придется сделать или 10. Вот программируемость - это уже серьезней. Т-SQL до сих пор остается странной смесью мощи и ограничений. Отсутствие многих обыденных для современных языков конструкций, не говоря уже об ООП, приводило к тому, что народ утешал себя тем, что SQL не для того предназначался, и уходил писать логику на С++, Visual Basic, Delphi и т.д. В последнее время круг задач, решаемых SQL Server, очень сильно расширился за рамки пресловутого SQL, следовательно, возможности языка программирования на серверной стороне, как бы он ни назывался, должны им соответствовать. И они будут соответствовать в следующей версии.

Пункт третий - масштабируемость. С целью повышения масштабируемости и распределения нагрузки в SQL Server 2000, как известно, были введены распределенные фрагментированные представления. Несмотря на то, что благодаря этому механизму SQL Server занимает в настоящий момент первые места в рейтингах производительности, следует понимать, что в действительности это лишь первый этап на пути к базе данных, автоматически распределяемой по многим серверам в сети, когда каждый узел, получивший запрос, разбивает его на подзапросы к тем серверам, где в действительности лежат нужные данные (а если они продублированы, то к наименее загруженным). Таким образом, понятно, что все три критерия обособления являются достаточно условными, и не всегда компоненты бизнес-логики будут выноситься за пределы SQL Server. Следовательно, тот факт, что клиентское приложение сможет общаться с ним напрямую как с Web-сервисом, на самом деле очень важен. Вернемся к Скрипту 14.

Для запросов к SQL Server по SOAP мы создаем еще один подкаталог websvc виртуальной директории и присваиваем ему тип vtSOAP. Его необходимо проассоциировать с физической поддиректорией (\WebService), т.к. в ней будет храниться wsdl и др. файлы, описывающие данный Web-сервис. Не мудрствуя лукаво, обзовем его SQLSoapSample. Методами могут выступать хранимые процедуры / функции, либо хранимые шаблоны. Первые добавляются как AddStoredProcMethod. Параметрами выступают имя метода, под которым он будет виден и вызываться с клиента, имя хранимой процедуры или функции, если в SELECT участвуют несколько таблиц, соединенных оператором JOIN, то должны ли они на клиенте получаться как вложенные элементы в случае XML-вывода / связанные таблицы в случае DataSet-вывода (1 - да, 0 - как независимые), должны ли ошибки выполнения возвращаться как ошибки SOAP (1 - да, 0 - нет) и что является результатом работы метода Web-сервиса: массив элементов XML, единственный DataSet или массив DataSet'ов. В данном случае выбран второй вариант. Массив DataSet'ов имеет смысл использовать в том случае, когда внутри процедуры выполняются несколько независимых SELECT'ов. Хранимая процедура CustomerOrdersForYear состоит из одного параметризованного запроса, с которым мы работали еще со Скрипта 3.


CREATE PROCEDURE CustomerOrdersForYear @Name nvarchar(20), @Year smallint AS
SELECT
c.ContactName, c.ContactTitle, o.OrderDate, o.Freight FROM Customers c
INNER JOIN Orders o ON c.CustomerID = o.CustomerID
WHERE c.ContactName like @Name + '%' and year(o.OrderDate) = @Year
Скрипт 19

В качестве второго метода Web-сервиса выбираем шаблон из п.10 (рис.6). Он добавляется вызовом AddTemplateMethod. Параметрами служат внешнее имя метода, местоположение шаблона относительно данного скрипта и способ передачи ошибок (SOAP - да/нет). Результат шаблона можно передавать только как набор XML-элементов. Строка sqlWebSvc.SoapMethods.GenerateWSDL(); производит автоматическую генерацию wsdl-файла, который кладется в физическую директорию, соответствующую виртуальному подкаталогу типа soap. После этого Web-сервис становится доступным для обращений. Как и в классическом DCOM, на машине клиента нужно создать прокси, содержащего сигнатуру методов Web-сервиса. В графической среде разработки Visual Studio это происходит автоматически при добавлении ссылки на wsdl-файл в раздел Web References проекта. Прокси можно также сгенерировать с помощью утилиты командной строки wsdl.exe:


wsdl.exe /l:CS /o:SQLSrvWebService_Proxy.cs /n:SQLSrvWebRef
http://localhost/SQLXML3/websvc?wsdl
В качестве параметров здесь передаются язык, на котором будет создан прокси, имя файла с прокси, его пространство имен и URI Web-сервиса с его описанием в виде wsdl. Обращение клиента к Web-сервису показано в Скрипте 20.

static void SQLSrvWebService_Client()
{
SQLSrvWebRef.SQLSoapSample proxy = new SQLSrvWebRef.SQLSoapSample();
proxy.Credentials = System.Net.CredentialCache.DefaultCredentials;

Console.WriteLine("----------------------------------------------------------------");
Console.WriteLine("Результаты выполнения шаблона (всегда как массив XML-элементов):");
object[] resultXml = proxy.ПримерВызоваШаблона("20", "1000");
foreach (XmlElement x in resultXml)
Console.WriteLine(x.OuterXml);

Console.WriteLine("\n----------------------------------------------------------------");
Console.WriteLine("Результаты выполнения хранимой процедуры (заказаны как DataSet):");
int retVal;
DataSet resultRel = proxy.ЗаказыКлиентаЗаГод("Maria", 1997, out retVal);

DataRelation rel = resultRel.Relations[0];
Console.WriteLine("\nТаблицы связаны отношением " + rel.RelationName);
Console.Write("Родительская таблица " + rel.ParentTable.TableName + ", родительский ключ - ");
foreach (DataColumn c in rel.ParentColumns) Console.Write(c.ColumnName + " ");
Console.Write("\nДочерняя таблица " + rel.ChildTable.TableName + ", дочерний ключ - ");
foreach (DataColumn c in rel.ChildColumns) Console.Write(c.ColumnName + " ");
Console.WriteLine("");

foreach (DataRow rParent in rel.ParentTable.Rows)
{
Console.WriteLine("\nТаблица " + rel.ParentTable.TableName);
foreach (DataColumn cParent in rel.ParentTable.Columns) Console.Write(cParent.Caption
+ ": " + rParent[cParent] + "; ");
Console.Write("\n\tСвязанные записи из таблицы " + rel.ChildTable.TableName);
foreach (DataRow rChild in rParent.GetChildRows(rel))
{
Console.Write("\n\t");
foreach (DataColumn cChild in rel.ChildTable.Columns)
Console.Write(cChild.Caption + ": " + rChild[cChild] + "; ");
}
Console.WriteLine("");
}
}
Скрипт 20

Результат выполнения шаблона получается в виде массива XmlElement, состоящего из единственного элемента, OuterXml которого показан ниже:


<Солянка xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<Employees EmployeeID="1" LastName="Davolio" FirstName="Nancy" Title="Sales Re
presentative" TitleOfCourtesy="Ms." ... />
<Customers ContactName="Horst Kloss" Кол-во="28" /><Customers ContactName="Jos
e Pavarotti" Кол-во="31" /><Customers ContactName="Roland Mendel" Кол-во="30" />
<Клиент Имя="Horst Kloss" Фирма="QUICK-Stop" Должность="Accounting Manager"><А
дрес><Страна>Germany</Страна><Город>Cunewalde</Город><Улица_дом>Taucherstra?e 10
</Улица_дом></Адрес><Заказы><Заказ Номер="10273"><Дата>1996-08-05</Дата><Стоимос
ть>76.07</Стоимость></Заказ><Заказ Номер="10285"><Дата>1996-08-20</Дата><Стоимос
ть>76.83</Стоимость></Заказ>...</Заказы></Клиент>
<Сотрудник Фамилия="Fuller" Имя="Andrew"><Сотрудник Фамилия="Davolio" Имя="Nan
cy" /><Сотрудник Фамилия="Leverling" Имя="Janet" /><Сотрудник Фамилия="Peacock"
Имя="Margaret" /><Сотрудник Фамилия="Buchanan" Имя="Steven"><Сотрудник Фамилия="
Suyama" Имя="Michael" /><Сотрудник Фамилия="King" Имя="Robert" /><Сотрудник Фами
лия="Dodsworth" Имя="Anne" /></Сотрудник><Сотрудник Фамилия="Callahan" Имя="Laur
a" /></Сотрудник>
</Солянка>
DataSet как результат второго метода состоит из двух таблиц: Customers и Orders. Выбор Row Formatting = Nested на стадии определения метода Web-сервиса приводит к тому, что условие соединения таблиц в запросе (JOIN) переходит в свойство Relations объекта DataSet и позволяет для каждого клиента выбрать сделанные им заказы:

Таблица Customers
ContactName: Maria Larsson; ContactTitle: Owner; Customers_Id: 0;
Связанные записи из таблицы Orders
OrderDate: 03.02.1997 0:00:00; Freight: 17.92; Customers_Id: 0;
OrderDate: 28.02.1997 0:00:00; Freight: 16.27; Customers_Id: 0;
OrderDate: 12.05.1997 0:00:00; Freight: 188.04; Customers_Id: 0;
OrderDate: 06.06.1997 0:00:00; Freight: 242.21; Customers_Id: 0;

Таблица Customers
ContactName: Maria Anders; ContactTitle: Sales Representative; Customers_Id: 1;
Связанные записи из таблицы Orders
OrderDate: 25.08.1997 0:00:00; Freight: 29.46; Customers_Id: 1;
OrderDate: 03.10.1997 0:00:00; Freight: 61.02; Customers_Id: 1;
OrderDate: 13.10.1997 0:00:00; Freight: 23.94; Customers_Id: 1;

Таблица Customers
ContactName: Maria Larsson; ContactTitle: Owner; Customers_Id: 2;
Связанные записи из таблицы Orders
OrderDate: 14.10.1997 0:00:00; Freight: 152.3; Customers_Id: 2;
OrderDate: 02.12.1997 0:00:00; Freight: 328.74; Customers_Id: 2;
OrderDate: 11.12.1997 0:00:00; Freight: 48.2; Customers_Id: 2;
The program '[2460] ConsoleApplication1.exe' has exited with code 0 (0x0).