На самом деле даже без провайдера SQLXMLOLEDB и SQLXML
веб-релизов в Visual Studio .Net (точнее, в ADO.Net) имеются
достаточно мощные средства для представления реляционных
наборов данных в виде XML, и наоборот, XML в реляционном виде.
Типовой сценарий работы выглядит следующим образом: получить
внутри объекта DataSet таблицы как результаты запросов к
источнику данных (возможно, к разным), связать их между собой
на основе объектов DataRelation и создать XML-представление
DataSet'a при помощи XmlDataDocument, как показано в Скрипте
6.
using
System.Data;
using
System.Data.OleDb;
using
System.Xml;
...
static void
Transform_ADONetDataSet_Xml()
{
DataSet
ds =
new DataSet("Новый набор данных
на клиенте");
(
new OleDbDataAdapter("SELECT CustomerID,
ContactName, ContactTitle FROM Customers",
ConstDeclarations.ConnectionString)).Fill(ds, "Клиентская
копия табл.клиентов");
(
new OleDbDataAdapter("SELECT OrderID,
CustomerID, OrderDate FROM Orders",
ConstDeclarations.ConnectionString)).Fill(ds, "Клиентская
копия
табл.заказов");
ds.Relations.Add("Джойн
двух копий на
клиенте",
ds.Tables["Клиентская копия
табл.клиентов"].Columns["CustomerID"],
ds.Tables["Клиентская
копия табл.заказов"].Columns["CustomerID"])
.Nested =
true;
XmlDataDocument
xml =
new
XmlDataDocument(ds);
FileInfo f =
new
FileInfo("..\\Results\\ADONetDataSet.xml");
xml.Save(f.FullName);
...
}
Скрипт 6
Результирующий XML можно видеть на рис.2.
<Новый_x0020_набор_x0020_данных_x0020_на_x0020_клиенте>
<Клиентская_x0020_копия_x0020_табл.клиентов>
<CustomerID>ALFKI
</CustomerID>
<ContactName>Maria Anders
</ContactName>
<ContactTitle>Sales Representative
</ContactTitle>
<Клиентская_x0020_копия_x0020_табл.заказов>
<OrderID>10643
</OrderID>
<CustomerID>ALFKI
</CustomerID>
<OrderDate>1997-08-25T00:00:00.0000000+04:00
</OrderDate>
</Клиентская_x0020_копия_x0020_табл.заказов>
<Клиентская_x0020_копия_x0020_табл.заказов>
<OrderID>10692
</OrderID>
...
Рис. 2
По умолчанию из DataSet будет сгенерирован документ, в
котором каждой записи DataRow соответствует элемент с именем
DataTable. Значения полей присутствуют в виде подэлементов
DataRow с названиями соответствующих полей DataColumns.
Поскольку DataSet предполагает отсоединенный режим работы,
отношения между таблицами в источнике (в БД на SQL Server) не
принимаются во внимание. Так, несмотря на связывание таблиц в
запросе:
(new OleDbDataAdapter("SELECT c.ContactName,
c.ContactTitle, o.OrderDate FROM Customers c INNER JOIN Orders
o ON c.CustomerID = o.CustomerID",
Connection)).Fill(ds);
с точки зрения DataSet это плоское множество записей,
потому что связи отработал сервер и прислал в DataSet готовый
табличный результат. Для образования иерархического
XML-документа, где записи дочерней таблицы являются вложенными
элементами родительской, отношение между таблицами нужно
указывать явно в DataSet.Relations, при этом свойство .Nested
объекта DataRelation должно быть выставлено в true. (Иначе
записи из родительской и дочерней таблиц будут перечислены
друг за другом на одном и том же уровне иерархии). Класс
XmlDataDocument является производным от DOMовского
XmlDocument, поэтому с его помощью над DataSet'ом можно
выполнять все стандартные XML-операции: XPath-запросы,
XSL-преобразования и т.д.
static void
Update_ADONetDataSet_Xml()
{
OleDbConnection
cn =
new
OleDbConnection("Provider=SQLOLEDB;...");
DataSet
ds =
new
DataSet();
OleDbDataAdapter daCust =
new OleDbDataAdapter("SELECT
CustomerID, ContactName, ContactTitle FROM Customers",
cn);
//Создаем
UpdateCommand
вручную daCust.
UpdateCommand
=
new OleDbCommand("UPDATE Customers
SET ContactName = ?, ContactTitle = ? WHERE CustomerID = ?",
cn);
daCust.UpdateCommand.Parameters.Add("@ContactName",
OleDbType.VarChar, 40,
"ContactName");
daCust.UpdateCommand.Parameters.Add("@ContactTitle",
OleDbType.VarChar, 40,
"ContactTitle");
daCust.UpdateCommand.Parameters.Add("@CustomerID",
OleDbType.Char, 5,
"CustomerID");
daCust.Fill(ds,
"Cust");
OleDbDataAdapter daOrds =
new OleDbDataAdapter("SELECT OrderID,
CustomerID, OrderDate FROM Orders",
cn);
//Создаем
UpdateCommand
автоматически OleDbCommandBuilder
cbOrds =
new
OleDbCommandBuilder(daOrds);
daOrds.Fill(ds,
"Ords");
ds.Relations.Add("Джойн двух
копий на
клиенте",
ds.Tables["Cust"].Columns["CustomerID"],
ds.Tables["Ords"].Columns["CustomerID"]).Nested
=
true;
ds.EnforceConstraints
=
false;
XmlDataDocument
xml =
new
XmlDataDocument(ds);
//Эквивалентно
ds.Tables["Cust"].Select("CustomerID =
'ALFKI'")[0]["ContactName"] = "Maria
Anders"; xml.
SelectSingleNode("//Cust[CustomerID='ALFKI']/ContactName").InnerText
= "Maria
Anders";
xml.
SelectSingleNode("//Cust[CustomerID='ALFKI']/Ords[OrderID=10643]/OrderDate").InnerText
=
"1997-08-25T00:00:00.0000000+04:00";
daCust.
Update(ds.Tables[1]);
daCust.
Update(ds.Tables[0]);
...
}
Скрипт 7
Скрипт 7 демонстрирует, что данные источника можно
модифицировать не только напрямую через DataSet
(ds.Tables[<Имя или номер таблицы в
коллекции>].Rows[<Номер записи в коллекции>][<Имя
или номер поля в коллекции>] = …), но и через его
XML-представление. В примере изменяются значений некоторых
XPath-узлов объекта XmlDataDocument. Эти изменения отражаются
в DataSet'е, над которым построен данный XmlDataDocument, а
поскольку у соответствующих DataAdapter'ов таблиц определены
UpdateCommand'ы, то они будут транслированы далее в источник
данных (SQL Server). Обратное тоже верно. Т.е. в DataSet можно
подгрузить XML-документ, который затем читать и модифицировать
реляционными операциями. В Скрипте 8 мы получаем схему
построенного в предыдущем примере XML-файла при помощи утилиты
xsd.exe, входящей в состав .NET Framework, читаем ее в
XmlDataDocument и загружаем туда данные из этого документа. На
основе XSD-схемы ADO.Net создает DataSet эквивалентной
реляционной структуры, так что становится возможным обращаться
и модифицировать XML-документ, как если бы он был
совокупностью связанных таблиц.
static void
Update_XML_ADONetDataset()
{
FileInfo
f =
new
FileInfo("..\\Results\\ADONetDataSet.xml");
Process.Start("xsd.exe",
f.FullName + "
/o:..\\Results");
XmlDataDocument xml =
new
XmlDataDocument();
xml.DataSet.ReadXmlSchema(Path.ChangeExtension(f.FullName,
".xsd"));
xml.Load(f.FullName);
xml.DataSet.Tables["Cust"].Select("CustomerID='ALFKI'")[0]["ContactName"]
= "Абра
Кадабра";
xml.DataSet.Tables["Ords"].Select("OrderID=10643")[0]["OrderDate"]
= DateTime.Now;...
}
Скрипт 8
Неплохим иллюстративным примером было бы приложение,
которое документирует пользовательские библиотеки классов .Net
в базе данных. Определения классов и объекты сохраняются в
виде XSD-схем и XML-документов (см. System.Xml.Serialization),
а на их основе, в свою очередь, при помощи рассмотренного
соответствия реляционного и XML-представлений, которое
обеспечивает ADO.Net, создается и наполняется БД. В качестве
самостоятельного упражнения вы можете попробовать сами
написать такое приложение и назвать его, скажем,
Cheops.
Впрочем, я отвлекся. Чрезвычайно мощная и развитая
функциональность ADO.Net по своей сути представляет собой
результат эволюции простой возможности сохранения
ADODB.Recordset в формате XML на стороне клиента, с которой
начинался наш разговор (см. п.2). Вернемся, тем не менее, к
основной теме - поддержке XML в SQL
Server.