Создание XML-схемы используя XSD

58

Язык XML тесно интегрирован в ADO.NET; на самом деле, он здесь является основным форматом для передачи данных между объектами. В исполняющей среде .NET класс DataTable можно описать внутри файла определения схемы XML (т.е. XSD). Более того, можно определить целый класс DataSet, с множеством классов DataTable и набором отношений между таблицами, а также включить прочие разнообразные детали для полного описания данных.

После определения файла XSD можно воспользоваться инструментом в исполняющей среде, который преобразует схему в соответствующие классы доступа к данным, такие как показанный ранее безопасный к типам класс DataTable. Давайте начнем с простого файла XSD (Products.xsd), описывающего ту же информацию, что и пример AutoLot, который был рассмотрен ранее, а затем расширим его, добавив некоторую дополнительную функциональность:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="AutoLot" targetNamespace="http://tempuri.org/XMLSchemal.xsd"
xmlns:mstns="http://tempuri.org/XMLSchemal.xsd"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="Product">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="CarID" msdata:ReadOnly="true"
            msdata:AutoIncrement="true" type="xs:int" />
        <xs:element name="Make" type="xs:string"></xs:element>
        <xs:element name="Color" type="xs:string"></xs:element>
        <xs:element name="PetName" type="xs:string"></xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

В этом файле, по сути, определяется схема с атрибутом id, установленным в AutoLot. Определен сложный тип по имени Product, содержащий ряд элементов — по одному для каждого из полей таблицы Products.

Элементы отображаются на классы данных следующим образом. Схема AutoLot отображается на класс, производный от DataSet. Сложный тип Product отображается на класс, унаследованный от DataTable. Каждый подэлемент отображается на класс, унаследованный от DataColumn. Коллекция всех столбцов отображается на класс, унаследованный от DataRow.

К счастью, в .NET Framework имеется инструмент, который производит код для этих классов с помощью входного файла XSD. Поскольку его единственным назначением является выполнение различных действий над файлами XSD, сам этот инструмент называется XSD.EXE.

Предполагая, что предыдущий файл сохранен как AutoLotXsd.xsd, его можно преобразовать в код, запустив следующую команду из командной строки:

xsd AutoLotXsd.xsd /d

В результате получится файл AutoLotXsd.cs. С утилитой xsd.exe могут применяться различные переключатели, с помощью которых изменяется сгенерированный вывод. Некоторые из наиболее часто используемых переключателей описаны ниже:

/database (/d)

Позволяет генерировать классы, унаследованные от DataSet, DataTable и DataRow.

/language: <язык>

Позволяет выбрать язык, на котором будет сгенерирован выходной файл. По умолчанию это C#, но можно указать vb для получения файла на Visual Basic .NET.

/namespace: <пространство_имен>

Позволяет определить пространство имен, в которое должен быть помещен сгенерированный код. По умолчанию пространства имен нет.

Ниже приведена сокращенная версия вывода утилиты XSD для схемы AutoLot:

//------------------------------------------------------------------------------
// <auto-generated>
//     Этот код создан программой.
//     Исполняемая версия:4.0.30319.1
//
//     Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
//     повторной генерации кода.
// </auto-generated>
//------------------------------------------------------------------------------

// 
// Этот исходный код был создан с помощью xsd, версия=4.0.30319.1.
// 


/// <summary>
///Represents a strongly typed in-memory cache of data.
///</summary>
[global::System.Serializable()]
[global::System.ComponentModel.DesignerCategoryAttribute("code")]
[global::System.ComponentModel.ToolboxItem(true)]
[global::System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedDataSetSchema")]
[global::System.Xml.Serialization.XmlRootAttribute("AutoLot")]
[global::System.ComponentModel.Design.HelpKeywordAttribute("vs.data.DataSet")]
public partial class AutoLot : global::System.Data.DataSet {
    
    private ProductDataTable tableProduct;
    
    private global::System.Data.SchemaSerializationMode _schemaSerializationMode = global::System.Data.SchemaSerializationMode.IncludeSchema;
    
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")]
    public AutoLot() {
        this.BeginInit();
        this.InitClass();
        global::System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler = new global::System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);
        base.Tables.CollectionChanged += schemaChangedHandler;
        base.Relations.CollectionChanged += schemaChangedHandler;
        this.EndInit();
    }
    
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")]
    protected AutoLot(global::System.Runtime.Serialization.SerializationInfo info, global::System.Runtime.Serialization.StreamingContext context) : 
            base(info, context, false) {
        if ((this.IsBinarySerialized(info, context) == true)) {
            this.InitVars(false);
            global::System.ComponentModel.CollectionChangeEventHandler schemaChangedHandler1 = new global::System.ComponentModel.CollectionChangeEventHandler(this.SchemaChanged);
            this.Tables.CollectionChanged += schemaChangedHandler1;
            this.Relations.CollectionChanged += schemaChangedHandler1;
            return;
        }
        string strSchema = ((string)(info.GetValue("XmlSchema", typeof(string))));
        if ((this.DetermineSchemaSerializationMode(info, context) == global::System.Data.SchemaSerializationMode.IncludeSchema)) {
            global::System.Data.DataSet ds = new global::System.Data.DataSet();
            ds.ReadXmlSchema(new global::System.Xml.XmlTextReader(new global::System.IO.StringReader(strSchema)));
            if ((ds.Tables["Product"] != null)) {
                base.Tables.Add(new ProductDataTable(ds.Tables["Product"]));
            }
            this.DataSetName = ds.DataSetName;
            this.Prefix = ds.Prefix;
            this.Namespace = ds.Namespace;
            this.Locale = ds.Locale;
            this.CaseSensitive = ds.CaseSensitive;
            this.EnforceConstraints = ds.EnforceConstraints;
            this.Merge(ds, false, global::System.Data.MissingSchemaAction.Add);
            this.InitVars();
        }
        else {
            this.ReadXmlSchema(new global::System.Xml.XmlTextReader(new global::System.IO.StringReader(strSchema)));
        }

Все приватные и защищенные члены удалены, чтобы сосредоточить внимание на общедоступном интерфейсе. Определения ProductDataTable и ProductRow показывают позицию двух вложенных классов, которые будут реализованы далее. Конструктор AutoLot() вызывает приватный метод InitClass(), который конструирует экземпляр производного от DataTable класса ProductDataTable и добавляет таблицу в коллекцию Tables класса DataSet.

Добавлением строк в таблицу занимается один из двух перегруженных (и существенно отличающихся) методов AddProductRow(). Первая перегрузка принимает уже сконструированный DataRow и возвращает void. Вторая берет набор значений, по одному для каждого столбца DataTable, конструирует новую строку, устанавливает в ней значения, добавляет строку к объекту DataTable и возвращает эту строку вызывающему коду. И настолько разные функции названы одинаково!

Подобно члену InitClass() производного от DataSet класса, который добавляет таблицу в класс DataSet, член InitMember() из ProductDataTable добавляет столбцы в класс DataTable. Свойства каждого столбца соответствующим образом устанавливаются, после чего столбец добавляется в коллекцию столбцов.

Последний класс, который мы обсудим — это ProductRow, унаследованный от DataRow. Этот класс используется для обеспечения безопасного к типам доступа ко всем полям таблицы данных. Он служит оболочкой для определенной строки и предоставляет члены для чтения (и записи) каждого поля таблицы.

В дополнение, для каждого допускающего значение null поля предусмотрены функции для установки поля в null и проверки значения поля на предмет равенства null. В следующем примере показаны функции для столбца CarID:

public partial class ProductRow : global::System.Data.DataRow {
        
        private ProductDataTable tableProduct;
        
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")]
        internal ProductRow(global::System.Data.DataRowBuilder rb) : 
                base(rb) {
            this.tableProduct = ((ProductDataTable)(this.Table));
        }
        
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Data.Design.TypedDataSetGenerator", "4.0.0.0")]
        public int CarID {
            get {
                return ((int)(this[this.tableProduct.CarIDColumn]));
            }
            set {
                this[this.tableProduct.CarIDColumn] = value;
            }
        }
Пройди тесты
Лучший чат для C# программистов