XML в MS SQL Server 2000 и технологиях доступа к данным - XML-представление наборов данных в ADO .NET
ОГЛАВЛЕНИЕ
XML-представление наборов данных в ADO .NET
На самом деле даже без провайдера SQLXMLOLEDB и SQLXML веб-релизов в Visual Studio .Net (точнее, в ADO.Net) имеются достаточно мощные средства для представления реляционных наборов данных в виде XML, и наоборот, XML в реляционном виде. Типовой сценарий работы выглядит следующим образом: получить внутри объекта DataSet таблицы как результаты запросов к источнику данных (возможно, к разным), связать их между собой на основе объектов DataRelation и создать XML-представление DataSet'a при помощи XmlDataDocument, как показано в Скрипте 6.
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);
...
}
Результирующий XML можно видеть на рис.2.
<Клиентская_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>
...
По умолчанию из 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-преобразования и т.д.
{
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 демонстрирует, что данные источника можно модифицировать не только напрямую через 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-документ, как если бы он был совокупностью связанных таблиц.
{
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;
...
}
Неплохим иллюстративным примером было бы приложение, которое документирует пользовательские библиотеки классов .Net в базе данных. Определения классов и объекты сохраняются в виде XSD-схем и XML-документов (см. System.Xml.Serialization), а на их основе, в свою очередь, при помощи рассмотренного соответствия реляционного и XML-представлений, которое обеспечивает ADO.Net, создается и наполняется БД. В качестве самостоятельного упражнения вы можете попробовать сами написать такое приложение и назвать его, скажем, Cheops.
Впрочем, я отвлекся. Чрезвычайно мощная и развитая функциональность ADO.Net по своей сути представляет собой результат эволюции простой возможности сохранения ADODB.Recordset в формате XML на стороне клиента, с которой начинался наш разговор (см. п.2). Вернемся, тем не менее, к основной теме - поддержке XML в SQL Server.