XML в MS SQL Server 2000 и технологиях доступа к данным | UpdateGrams | Дальше » |
До сих пор под XML-взаимодействием с SQL Server понималось, в основном, чтение данных с сервера в XML-формате с помощью SELECT ... FOR XML или XPath. Возникает вопрос: можно ли их модифицировать в рамках XML-представления. Про возможность модификации косвенно упоминалось пару раз: когда мы говорили про возможности ADO.Net (п.7) и про шаблоны (п.10). Первый способ предусматривает связь с сервером через DataAdapter и работу с DataSet в рамках его XMLной ипостаси. Второй можно реализовать, поместив запросы на обновление в секцию <sql:query> шаблона: <Root xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="Имя"></sql:param> <sql:param name="Фамилия"></sql:param> </sql:header> <sql:query> UPDATE Customers SET ContactName = 'Maria Anders' WHERE CustomerID = 'ALFKI' INSERT Employees (FirstName, LastName) Values (@Имя, @Фамилия) </sql:query> </Root> Его выполнение:
static void Execute_UpdateTemplate_SQLXML() { ... cmd.CommandText = "..\\Templates\\XMLTemplate3.xml"; cmd.CommandType = SqlXmlCommandType.TemplateFile; SqlXmlParameter prm = cmd.CreateParameter(); prm.Name = "@Имя"; prm.Value = "ааа"; prm = cmd.CreateParameter(); prm.Name = "@Фамилия"; prm.Value = "ббб"; cmd.ExecuteNonQuery(); } равнозначно тому, как если бы эти запросы были выполнены
обычным путем. Возникает вопрос: можно ли модифицировать
данные непосредственно на сервере (не кэшируя их
предварительно в DataSet) и работая с ними, как с XML, а не
через реляционные операторы. Такой способ предоставляет
UpdateGrams. <?xml version="1.0" encoding="utf-8" ?> <ROOT xmlns:updg="urn:schemas-microsoft-com:xml-updategram"> <updg:header> <updg:param name="Дата"/> <updg:param name="Стоимость" /> </updg:header> <updg:sync mapping-schema="..\Schemas\SQLSchema1.xsd"> <updg:before> <Клиент Имя="Ana Trujillo" updg:id="x" /> <Клиент Имя="Antonio Moreno" updg:id="y" /> </updg:before> <updg:after> <Клиент updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" /> </updg:after> <updg:before> <Клиент CustomerID="ALFKI" /> </updg:before> <updg:after> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ> <Дата> $Дата </Дата> <Стоимость> cast(@Стоимость as money) </Стоимость> </Заказ> </Заказы> </Клиент> </updg:after> </updg:sync> <updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="x" /> </Сотрудник> </updg:after> </updg:sync> </ROOT> Поскольку я постарался напихать в него по
максимуму показательных вещей, разберем данный updategrams по
частям. Начнем с того, что это XML-файл, структура которого
довольно близка к уже рассмотренным нами шаблонам. В </updg:sync>
может задаваться аннотированная схема, которая отображает
реляционную схему на XSD и благодаря которой мы можем работать
с информацией в БД как с XML. Если схема не указана,
предполагается отображение по умолчанию: каждая запись таблицы
- элемент, поля в ней - атрибуты. По большому счету
updategrams состоит из секций трех типов: <updg:header> -
в ней передаются возможные параметры; <updg:before> и
<updg:after>.
Если запись фигурирует только в <updg:before>,
она удаляется; если только в <updg:after> -
вставляется; если и там, и там - обновляется. Рассмотрим,
например, ситуацию, когда весь updategrams состоит только из
<updg:sync mapping-schema="SQLSchema1.xsd"> <updg:before> <Клиент Имя="Ana Trujillo" /> </updg:before> <updg:after> </updg:after> </updg:sync> В процессе его выполнения на SQL Server
происходит следующее:
SET XACT_ABORT ON
BEGIN TRAN DECLARE @eip INT, @r__ int, @e__ int SET @eip = 0 DELETE Customers WHERE ( ContactName=N'Ana Trujillo' ) ; SELECT @e__ = @@ERROR, @r__ = @@ROWCOUNT IF (@e__ != 0 OR @r__ != 1) SET @eip = 1 IF (@r__ > 1) RAISERROR ( N'SQLOLEDB Error Description: Ambiguous delete, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r__ < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty delete, no deletable rows found Transaction aborted ', 16, 1) IF (@eip != 0) ROLLBACK ELSE COMMIT SET XACT_ABORT OFF Из этого сразу видно несколько важных
вещей. Первое: каждая секция <updg:sync>
открывает отдельную транзакцию (см. BEGIN TRAN). Второе:
описание модифицируемого элемента (<Клиент Имя="Ana Trujillo"
/>) в <updg:before>
и/или <updg:after>
должно соответствовать одной записи, неважно, идет ли
маппирование по дефолту или через аннотированную схему. Если
записей, отвечающих эквивалентному условию WHERE не находится
(@r__ < 1) или больше одной (@r__ > 1), будет
сгенерирована ошибка (RAISERROR) и транзакция откатится
(ROLLBACK).
Поскольку в каждой секции <updg:before> и <updg:after> может находиться несколько записей, то необходимо как-то сопоставить их друг другу в случае обновления. Например, при парсинге этого шаблона <updg:before> <Клиент Имя="Ana Trujillo" updg:id="x" /> <Клиент Имя="Antonio Moreno" updg:id="y" /> </updg:before> <updg:after> <Клиент updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" /> </updg:after> SqlXml должен понимать, что клиента по
имени Ana Trujillo мы хотим просто удалить, а клиента по имени
Antonio Moreno обновляем, поэтому строка <Клиент
updg:id="y" Фирма="Рога&Копыта" Имя="Дон Педро" />
в <updg:after> соответствует именно ему. Это можно
сделать двумя способами. Первый - пометить их одним и тем же
updg:id.
Второй способ - однозначно идентифицировать записи при помощи
первичного ключа таблицы. Для этого нужно а) определить ключ в
аннотированной схеме (вспоминайте аннотацию ms:key-fields) и
б) явно сослаться на него в <updg:before>/<updg:after>
(скажем, <Сотрудник ID_Сотрудника="..." />).
Следующая часть <updg:before> <Клиент CustomerID="ALFKI" /> </updg:before> <updg:after> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ> <Дата> $Дата </Дата> <Стоимость> cast(@Стоимость as money) </Стоимость> </Заказ> </Заказы> </Клиент> </updg:after> производит обновление и вставку
одновременно. В заказы, сделанные клиентом с идентификатором
ALFKI добавляется еще один
заказ. При этом SQL Server сам распознает, что новую запись в
таблице Orders нужно подчинить данному клиенту в таблице
Customers и автоматически устанавливает для нее CustomerID в
"ALFKI".
exec sp_executesql N'... INSERT Orders (OrderDate, Freight, CustomerID) VALUES (@Дата, cast(@Стоимость as money), N''ALFKI'') ... ', N'@Дата nvarchar(19),@Стоимость sql_variant', N'08.04.2002 18:41:34', 100 Это происходит благодаря тому, что поле
CustomerID указано в качестве связующего в <ms:relationship> схемы
SQLSchema1.xsd. Обратите внимание, что несмотря на то, что в
аннотирующей схеме ему явно не соответствует никакой
элемент/атрибут, на него можно ссылаться в updategrams-файле.
Для автоматического подчинения родительскую запись в <updg:before>/<updg:after>
необходимо идентифицировать именно по CustomerID, которое
значится как parent-key в схеме (parent-key="CustomerID"). Определение записи по другим
атрибутам, пусть даже однозначно ее идентифицирующим
(например, <Клиент Имя="Maria
Anders" Фирма="Alfreds Futterkiste"
...>), к такому эффекту не приводит. Следующая часть
updategrams-файла:
<updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="x" /> </Сотрудник> </updg:after> </updg:sync> открывает другую транзакцию и работает с
другой аннотирующей схемой, которая, как вы помните,
превращает parent-child таблицу в XML-иерархию. Здесь
демонстрируется не автоматическое, а "ручное" подчинение. Мы
вставляем одновременно две записи. Чтобы подчинить вторую
первой, нужно значение поля ReportsTo
для второй установить в первичный ключ первой. Положение
осложняется тем, что первичный ключ - это identity, и его
значение априори неизвестно. Выйти из положения позволяет
аннотация updg:at-identity. Кстати, здесь мы снова
обращаемся к полю (ReportsTo), которое нигде в схеме не
засвечено, а используется только в описании отношения (<ms:relationship>).
Дата и стоимость заказа передаются в виде параметров. Если параметр подставляется standalone, можно использовать XPath-обозначение ($Дата), если же над ним по ходу выполняются какие-то SQL-преобразования - то SQLное (@Стоимость). Я не стал дальше усложнять пример, но хотел бы отметить еще две полезных аннотации. <updg:nullvalue> позволяет оговорить SQLный NULL: <updg:sync mapping-schema="SQLSchema2.xsd" updg:nullvalue="Пусто"> <updg:after> <Сотрудник updg:at-identity="x" Имя="Альбус" Фамилия="Дамблдор" > <Сотрудник Имя="Минерва" Фамилия="МакГонагалл" ReportsTo="Пусто" /> </Сотрудник> </updg:after> </updg:sync> В этом случае второй сотрудник не будет
подчинен первому, т.к. несмотря на то, что в XML эта запись
вложена, при занесении ее в таблицу ей не будет назначено
никакого руководителя (ReportsTo=NULL).
Аннотация ms:inverse применяется не в UpdateGrams, а при описании аннотированной схемы. Мы не рассматривали ее в п.9, потому что она имеет смысл только тогда, когда та используется затем в updategrams. Дело в том, что SQL Server считает, что иерархия "родитель-потомок" в XML соответствует ограничению первичный - внешний для ключей связанных таблиц. Рассмотрим updategrams вида <updg:sync mapping-schema="..\Schemas\SQLSchema2.xsd"> <updg:before> <Клиент CustomerID="ALFKI"> <Заказы> <Заказ /> </Заказы> </Клиент> </updg:before> </updg:sync> Сразу ясно, что этот пример вызовет ошибку,
поскольку данный клиент сделал более одного заказа, а условием
UpdateGrams является однозначная идентификация записи. Но дело
не в этом. На сервере будут выполнены действия в следующем
порядке: DELETE Orders WHERE CustomerID = N'ALFKI'; DELETE
Customers WHERE CustomerID = N'ALFKI'. (Использование
атрибутов, не входящих в определение <ms:relationship>, - напр.,
<Клиент Имя="Maria Anders"> приведет
к полному DELETE Orders). Из этой последовательности видно,
что SqlXml сначала пытается произвести обновления / удаления в
дочерней таблице, а уже потом из родительской, чтобы по
возможности не противоречить referential constraints. В жизни
бывают ситуации, когда схема может задавать вложенность
элементов, противоположную направлению действия ограничения
primary key/foreign key в таблицах. Тогда SqlXml, предполагая,
что вложенный элемент соответствует внешнему ключу, полезет на
самом деле в первичную таблицу, получит от SQL Server по рукам
и откатит транзакцию. Чтобы побороть такую ситуацию, в <ms:relationship> нужно
поставить ms:inverse="true".
Полный список аннотаций, как всегда, можно найти в документации к SQLXML 3.0. Вызов UpdateGrams из приложения происходит аналогично вызову шаблона через файл (см. Скрипт 12) или Stream. При его тестировании в БД Northwind предварительно нужно превратить связь Orders -> [Order Details] из строгой в каскадную. Поскольку [Order Details] не участвует в updategrams, ее FK будет препятствовать обновлению Orders (в отличие от Customer -> Orders, где это учитывается автоматически за счет указания в relationship в аннотированной схеме). ...
cmd.CommandText = "..\\Templates\\UpdateGrams1.xml"; cmd.CommandType = SqlXmlCommandType.TemplateFile; SqlXmlParameter prm = cmd.CreateParameter(); prm.Name = "@Дата"; prm.Value = DateTime.Now.ToString(); prm = cmd.CreateParameter(); prm.Name = "@Стоимость"; prm.Value = 100; cmd.ExecuteNonQuery();
|
XML в MS SQL Server 2000 и технологиях доступа к данным | UpdateGrams | Дальше » |