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;
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.