Poznáváme C# a Microsoft. NET 66. díl – XML a ADO. NET

Bližší pohled na to jakým způsobem je realizováno opěvované spojení hierarchického světa XML a relačního světa databází na úrovní ADO .NET je tématem tohoto dílu, který mimo jiné ukáže jak jsou v XML dokumentu představovány datové relace mezi tabulkami.

Zápis a načtení schéma DataSetu

V minulém díle jsme se dozvěděli, že je možné pomocí metody WriteXml objektu DataSet zapsat obsah tohoto objektu do XML a také, že je možné pomocí metody ReadXml objekt DataSet daty z XML naplnit. Spolu s daty objektu DataSet je možné zapsat/načíst i XSD definující jeho schéma. Schématem objektu DataSet je myšleno schéma jednotlivých tabulek, které daný DataSet obsahuje. Schéma tabulky je tvořeno názvy a datovými typy jednotlivých sloupců, pro něž mohou být definována různá omezení. V případě, že naším požadavkem je zapsat pouze informace o daném schématu objektu DataSet do XSD můžeme využít instanční metody třídy DataSet, kterou je metoda WriteXmlSchema.

DataSet ds = new DataSet("EmployeesDataSet");
//vytvoreni tabulky o zamestnancich
DataTable employees = CreateEmployessTable();
ds.Tables.Add(employees);
//zapis schema do XSD
ds.WriteXmlSchema("EmployeesDataSet.xsd");

Do výsledného XSD jsou zapsány jednak názvy a datové typy sloupců, ale také je v XSD zaznamenáno jedno omezení, které omezuje hodnoty sloupce ID na unikátní hodnoty a to, že hodnoty tohoto sloupce mají být automaticky inkrementovány.

<?xml version="1.0" standalone="yes"?>
<xs:schema id="EmployeesDataSet" xmlns="" xmlns:xs="
http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="EmployeesDataSet" msdata:IsDataSet="true" msdata:Locale="cs-CZ">
    <xs:complexType>
      <xs:choice maxOccurs="unbounded">
        <xs:element name="Employees">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" msdata:AutoIncrement="true" type="xs:int" />
              <xs:element name="FirstName" type="xs:string" minOccurs="0" />
              <xs:element name="SurName" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="Constraint1">
      <xs:selector xpath=".//Employees" />
      <xs:field xpath="ID" />
    </xs:unique>
  </xs:element>
</xs:schema>

Pochopitelně je možný i postup opačným směrem, tedy že lze schéma objektu DataSet z XSD načíst a to pomocí metody ReadXmlSchema.

//vytvoreni noveho DataSetu
DataSet ds = new DataSet();
//bude 0
Console.WriteLine(ds.Tables.Count);
//nacteni schema z XSD
ds.ReadXmlSchema("EmployeesDataSet.xsd");
//bude jedna
Console.WriteLine(ds.Tables.Count);
//vypise Employees
Console.WriteLine(ds.Tables[0].TableName);

Po úspěšném vykonání této metody jsou do objektu DataSet vytvořeny tabulky (v našem příkladu pouze jedna) podle daného XSD včetně všech omezení a tudíž s nimi můžeme bez problémů pracovat, jako kdybychom je vytvořili „ručně“ pomocí našeho kódu nebo pomocí datového adaptéru.

Datové relace a XML

Jak jsme během našeho poměrně podrobného seznamování se s komponentou ADO .NET zjistili tak lze na úrovni objektu DataSet definovat datové relace mezi jednotlivými tabulkami, což jsme činili pomocí objektů typu DataRelation. Pojďme se podívat, jak se nám definice datové relace projeví do hierarchické (tedy XML) reprezentace DataSetu.

//vytvoreni DataSetu
DataSet ds = new DataSet("ProductsDataSet");
//vytvoreni tabukly o kategoriich
DataTable categories = CreateCategoriesTable();
//vytvoreni tabulky o produktech
DataTable products = CreateProductsTable();
//naplneni tabulek daty
CreateProducts(products);
CreateCategories(categories);
ds.Tables.Add(products);
ds.Tables.Add(categories);
//vytvoreni datove relace
DataRelation relation = new DataRelation("ProductCategory", categories.Columns["ID"], products.Columns["CategoryID"]);
ds.Relations.Add(relation);
//vypiseme XML reprezentaci DataSetu
ds.WriteXml(Console.Out);

Tento zdrojový kód by nám měl být známý. Jsou v něm vytvořeny dvě tabulky (Products, Categories) mezi kterými je vytvořena datová relace, protože každý objekt spadá do nějaké kategorie. Pokud si prohlédnete výstup tohoto kódu, který vypíše XML reprezentaci objektu DataSet s datovou relací zjistíte, že to do jaké kategorie daný objekt patří, není z této hierarchické reprezentace vůbec čitelné.

<ProductsDataSet>
  <Products>
    <ID>0</ID>
    <Name>Product0</Name>
    <Price>0</Price>
    <CategoryID>1</CategoryID>
  </Products>
  <Products>
    <ID>1</ID>
    <Name>Product1</Name>
    <Price>1000</Price>
    <CategoryID>1</CategoryID>
  </Products>
  <Products>
    <ID>2</ID>
    <Name>Product2</Name>
    <Price>2000</Price>
    <CategoryID>1</CategoryID>
  </Products>
  <Products>
    <ID>3</ID>
    <Name>Product3</Name>
    <Price>3000</Price>
    <CategoryID>2</CategoryID>
  </Products>
  <Products>
    <ID>4</ID>
    <Name>Product4</Name>
    <Price>4000</Price>
    <CategoryID>2</CategoryID>
  </Products>
  <Categories>
    <ID>1</ID>
    <Name>Category ONE</Name>
  </Categories>
  <Categories>
    <ID>2</ID>
    <Name>Category TWO</Name>
  </Categories>
</ProductsDataSet>

Takže význam datové relace není do světa XML promítnut. Jak toto napravit? Řešení je velmi jednoduché a nabízí se ve formě vnořených relací. K tomu nám stačí nastavit vlastnost Nested objektu Datové relace.

...
//vytvoreni datove relace
DataRelation relation = new DataRelation("ProductCategory", categories.Columns["ID"], products.Columns["CategoryID"]);
//oznacime relace jako vnorene
relation.Nested = true;
ds.Relations.Add(relation);
//vypiseme XML reprezentaci DataSetu
ds.WriteXml(Console.Out);

Po té co pomocí této vlastnosti nastavíme, že tato relace je vnořená, tak ono vnoření uvidíme právě na úrovní XML reprezentace objektu DataSet, který tuto relaci obsahuje. Po vypsání XML reprezentace DataSetu budou v každém elementu hlavní entity relace (v našem případě kategorie) vnořeny elementy představující entity vedlejší (produkt). Jak následek použití vnořených relací v XML vypadá, můžete vidět níže.

<ProductsDataSet>
  <Categories>
    <ID>1</ID>
    <Name>Category ONE</Name>
    <Products>
      <ID>0</ID>
      <Name>Product0</Name>
      <Price>0</Price>
      <CategoryID>1</CategoryID>
    </Products>
    <Products>
      <ID>1</ID>
      <Name>Product1</Name>
      <Price>1000</Price>
      <CategoryID>1</CategoryID>
    </Products>
    <Products>
      <ID>2</ID>
      <Name>Product2</Name>
      <Price>2000</Price>
      <CategoryID>1</CategoryID>
    </Products>
  </Categories>
  <Categories>
    <ID>2</ID>
    <Name>Category TWO</Name>
    <Products>
      <ID>3</ID>
      <Name>Product3</Name>
      <Price>3000</Price>
      <CategoryID>2</CategoryID>
    </Products>
    <Products>
      <ID>4</ID>
      <Name>Product4</Name>
      <Price>4000</Price>
      <CategoryID>2</CategoryID>
    </Products>
  </Categories>
</ProductsDataSet>

Třída XmlDataDocument

Jedním ze zajímavých prvků v ADO .NET spojující relační svět se světem hierarchickým je bez pochyb třída XmlDataDocument. Proč je tak zajímavá? Je tomu tak z důvodu, že díky ní je možné nad daty, které jsou obsaženy v objektu DataSet, provádět operace, které jsou možné provádět na XML daty.

Těmito operacemi nemám na mysli nic jiného než technologie XSL transformace či dotazy jazyka XPath. Třída XmlDataDocument je totiž odvozená od třídy XmlDocument se kterou jsme se již seznámili před nějakou dobou v tomto seriálu. To znamená, že s instancemi třídy XmlDataDocument jsme schopni provádět ty samé operace co s instancemi třídy XmlDocument s tím rozdílem, že jsme schopni asociovat objekt XmlDataDocument s objektem DataSet a tím využívat jeho data. Touto asociací tedy zařídíme, že objekt XmlDataDocument i objekt DataSet využívají jedna a ta samá data. To v praxi znamená, že pokud nějakým způsobem pozměníme data v objektu DataSet, tak se tato změna projeví i v XML, které představuje objekt XmlDataDocument.

//vytvoreni instance DataSetu
DataSet ds = new DataSet("EmployeesDataSet");
//vytvoreni tabulky se zamestnanci
DataTable employees = CreateEmployessTable();
//naplneni tabulky daty
CreateEmployees(employees);
ds.Tables.Add(employees);
//vytvoreni instance tridy XmlDataDocument asociovanou
//s nasim DataSetem
XmlDataDocument xmlDoc = new XmlDataDocument(ds);
//vypsani obsahu XML dokumentu
Console.WriteLine(xmlDoc.InnerXml);
//pridani noveho radku do tabulky
DataRow newRecord = employees.NewRow();
newRecord["firstName"] = "Novy";
newRecord["SurName"] = "Zamestnanec";
employees.Rows.Add(newRecord);
Console.WriteLine("Po zmene DataSetu :");
//pridany radek se projevi i v XML
Console.WriteLine(xmlDoc.InnerXml);

Asociaci s objektem DataSet můžeme provést buďto pomocí konstruktoru nebo také pomocí instanční vlastnosti DataSet třídy XmlDataDocument. A jak jsem psal o pár řádek výše, tak je možné nad objekty XmlDataDocument provádět například XSL transformace. Samozřejmě bychom mohli provádět XSL transformace například na souborem do kterého jsme zapsali XML reprezentaci objektu DataSet, ale instance třídy XmlDataDocument je objektem reprezentujícím tato XML data a tudíž je možné přímé provedené transformace.

//vytvoreni instance DataSetu
DataSet ds = new DataSet("EmployeesDataSet");
//vytvoreni tabulky se zamestnanci
DataTable employees = CreateEmployeeTable();
//naplneni tabulky daty
CreateEmployees(employees);
ds.Tables.Add(employees);
//vytvoreni instance tridy XmlDataDocument asociovanou
//s nasim DataSetem
XmlDataDocument xmlDoc = new XmlDataDocument(ds);
//provedeni XSL transformace nad dokumentem s daty o zamestnancich
XslTransform xslTrans = new XslTransform();
xslTrans.Load("stylesheet.xslt");
xslTrans.Transform(xmlDoc, null, Console.Out, null);

Příklady ke článku jsou k dispozici zde.

Diskuze (1) Další článek: Důkaz o vraždě Miloševiče skrývá virus

Témata článku: , , , , , , , ,