Технология Microsoft ADO.NET

         

Класс ArrayList


Класс ArrayList, подобно классу Hashtable, определенный в пространстве имен System.Collections, представляет собой один из чрезвычайно простых и удобных способов работы с наборами элементов. Объекты этого класса не имеют фиксированного размера и при необходимости могут менять его. Объект ArrayList при своем создании резервирует место в памяти для 16 элементов - указателей на тип object. При добавлении семнадцатого элемента размерность ArrayList увеличивается до 32 элементов. Обращение к объектам осуществляется аналогично обращению к элементам массива. Создайте новое консольное приложение, назовите его "ClassArrayList". В таблице 9.2 приводится полный его листинг:

Таблица 9.2. Приложение ClassArrayList

Листинг приложения ClassArrayList Результат работы приложения (рис. 9.21)
using System; using System.Collections;

namespace ClassArrayList { class Class1 {

[STAThread] static void Main(string[] args) { ArrayList ar = new ArrayList(); ar.Add("A"); ar.Add("AB"); ar.Add("ABC"); ar.Add("ABCD"); ar.Add("ABCDE"); ar.Add("ABCDEF"); ar.Add("ABCDEFG"); ar.Add("ABCDEFGH"); ar.Add(""); ar.Add("");

Console.WriteLine("Вывод элементов массива:\n"); foreach (object element in ar) { Console.WriteLine(element); } ar.Remove("ABCD"); Console.WriteLine("Удаление элемента:\n"); foreach (object element in ar) { Console.WriteLine(element); } ar.Insert(6, "XYZ"); Console.WriteLine("Вставка элемента \n на заданную позицию:\n"); foreach (object element in ar) { Console.WriteLine(element); } ar.Clear(); Console.WriteLine("Удаление всех элементов:\n"); foreach (object element in ar) { Console.WriteLine(element); } } } }

Из рис. 9.21 видно, что элементами ArrayList могут быть любые значения, поскольку он содержит в себе указатели на тип object. Для возвращения значений из массива необходимо осуществлять явное преобразование типов.

В программном обеспечении к курсу вы найдете приложение ClassArray List (Code\Glava4\ ClassArrayList).

  1)

  Дополнительные сведения о классе ArrayList см. в конце лекции.

Класс HashTable


Если вы подзабыли описание этого класса, давайте вспомним его. Hashtable - это структура данных, предназначенная для осуществления быстрого поиска. Это достигается за счет связывания ключа с каждым объектом, который сохраняется в таблице. Hashtable - это объект, в котором хранятся пары значений: так называемый ключ и само значение. Элементы каждой коллекции - и ключей (Keys), и значений (Values) - являются типом object, а это значит, что в качестве индекса элемента в привычном понимании теперь выступает не int, а именно object! Создайте новое консольное приложение и назовите его HashtableExample. Листинг этого приложения:

using System; using System.Collections;

namespace HashtableExample {

class Statistics { public Hashtable AbonentList; public Statistics() { AbonentList = new Hashtable(); } }

class Abonent { public string Name; public int Phone; public Abonent(string n, int p) { Name = n; Phone = p; } } class Class1 { [STAThread] static void Main(string[] args) { Abonent a1 = new Abonent("Иванов", 1234567); Abonent a2 = new Abonent("Николаев", 3216547); Abonent a3 = new Abonent("Андреева", 685472); Abonent a4 = new Abonent("Волков", 1234500); Abonent a5 = new Abonent("Кириллова", 3245637); Statistics myStatistics = new Statistics(); myStatistics.AbonentList.Add(a1.Phone, a1.Name); myStatistics.AbonentList.Add(a2.Phone, a2.Name); myStatistics.AbonentList.Add(a3.Phone, a3.Name); myStatistics.AbonentList.Add(a4.Phone, a4.Name); myStatistics.AbonentList.Add(a5.Phone, a5.Name);



Console.WriteLine(myStatistics.AbonentList[685472]); } } }

В методе Main создаются пять объектов класса Abonent, которые затем добавляются в Hashtable AbonentList (myStatistics.AbonentList) в коллекцию Values. Ключами для этих элементов будут служить значения их полей Phone. Обратите внимание, что метод Add() класса Hashtable требует два параметра: значение первого аргумента будет выступать в роли ключа для элемента, которым является значение второго аргумента.

Результатом выполнения программы будет вывод фамилии абонента, с заданным номером телефона (ключом) (рис. 9.20).


Рис. 9.20.  Приложение HashtableExample

В программном обеспечении к курсу вы найдете приложение Hashtable Example (Code\Glava4\ HashtableExample ).



Объект DataView. Фильтрация и сортировка данных


В объект DataSet можно загрузить большое количество данных и затем, отсоединившись от источника, использовать их по частям. Объект DataView предназначен для работы с упорядоченной определенным образом частью данных, загруженных в DataSet. Подобно всем объектам ADO .NET, с ним можно работать как при помощи визуальных средств среды, так и программно.

Скопируйте папку приложения VisualDataSQL из первой лекции и переименуйте ее в VisualDataView. Открываем проект и перетаскиваем на форму еще один элемент DataGrid, свойству Dock которого устанавливаем значение Bottom. Выделив добавленный элемент DataGrid, перетащим на форму элемент управления Splitter (разделитель), свойству Dock которого также установим значение Bottom. В результате на форме располагаются две таблицы, в режиме запуска размеры покрытия их можно будет изменять, передвигая разделитель. Переключаемся на вкладку Data панели инструментов Toolbox и пертаскиваем на форму элемент DataView (рис. 9.1).


Рис. 9.1.  Элемент управления DataView

Добавленный объект появляется на панели компонент. Прежде всего следует определить, что будет представлять источник данных для него. Переходим в окно Properties, в свойстве Table выбираем таблицу Customers объекта DataSet (рис. 9.2).


Рис. 9.2.  Определение таблицы объекта DataView

Теперь определим фильтр, который будет определять содержимое DataView. Выбираем свойство RowStateFilter, нажимаем на кнопку "Original Rows" (рис. 9.3, A), затем снимаем все галочки, кроме "Deleted" (рис. 9.3, Б).


Рис. 9.3.  Окно свойств объекта DataView

Кнопка "Original Rows" переключает на свойства первоначальных (оригинальных), исходных записей, загружаемых в DataSet. Выбрав пункт "Deleted", мы тем самым отобрали фильтр для удаленных записей.

В окне Properties второго элемента DataGrid в свойстве DataSource устанавливаем объект DataView в качестве источника данных (рис. 9.4).


Рис. 9.4.  Определение объекта DataView в качестве источника данных для объекта DataGrid


Запускаем приложение. Выделяем записи поля CustomerID со значениями "ANTON" и "BLAUS" и удаляем их - они немедленно появляются во втором элементе DataGrid (рис. 9.5).


увеличить изображение
Рис. 9.5.  Удаленные записи оказываются во второй таблице

Изменим фильтр - в поле свойства RowStateFilter нажимаем на кнопку "Current Rows" (Текущие записи) и оставляем галочки на пунктах "New" и "Current Modified" (рис. 9.6):


Рис. 9.6.  Установка нового фильтра

Запускаем приложение. Теперь во вторую таблицу будут помещаться текущие записи, в которые были внесены изменения - в первой записи в поле City было добавлено "123", а также новые записи - прокручиваем список до конца и вводим данные (рис. 9.7).


увеличить изображение
Рис. 9.7.  Применение фильтра по новым и измененным записям

Каждый объект DataSet поддерживает две версии - исходную, полученную при загрузке из базы данных, и текущую, в которую были внесены изменения. Данные, выведенные на форму в элемент управления DataGrid, можно изменять, при этом фиксируется статус записей объекта DataRow с помощью свойства RowState. Свойство RowStateFilter осуществляет фильтрацию по этому свойству, причем кнопке "Original" соответствуют исходные данные, а кнопке "Current" - текущие. Значения этого свойства приведены в таблице 9.1.

Таблица 9.1. Значения свойства RowStateFilter объекта DataViewСвойство Описание
Unchanged Записи без изменений
New Новые записи
Deleted Удаленные записи
Current Modified Измененные записи с их текущими значениями
Original Modified Измененные записи с их первоначальными значениями
Как мы уже видели, для одного объекта DataView в пределах одной версии объекта DataSet (исходной или текущей) возможно объединение фильтров - например, отображение новых и удаленных записей.

Свойство RowFilter предназначено для вывода записей, содержащих определенное значение заданного поля. Установим свойству RowStateFilter значение по умолчанию - CurrentRows, - а затем в свойстве RowFilter введем фильтрацию (рис. 9.8):



City = 'London'


Рис. 9.8.  Свойство RowFilter объекта DataView

Запускаем приложение. Во второй таблице выводятся все записи с названием города "London" (рис. 9.9, А). Установив в свойстве RowFilter другое значение - ContactName = 'Thomas Hardy', - получаем одну запись, содержащую это значение (рис. 9.9, Б):


увеличить изображение
Рис. 9.9.  Вывод записей со значением "London" поля City (А) и записи со значением "Thomas Hardy поля ContactName (Б)

Свойство Sort предназначено для вывода записей в порядке возрастания (ascending, ASC) или убывания (descending, DESC) по значениям заданного поля. В принципе, элемент DataGrid сам по себе поддерживает сортировку - достаточно просто щелкнуть по заголовку поля. Однако это требует действий от пользователя, тогда как объект DataView может предоставлять данные уже в готовом виде.

Удаляем значение свойства RowFilter, в поле свойства Sort вводим "Sity ASC" (рис. 9.10):


Рис. 9.10.  Сортировка по полю City

Запускаем приложение. Во второй таблице отображаются записи со значениями поля City в порядке возрастания (рис. 9.11, А). Введем другое значение поля Sort - "ContactName DESC" - в результате получаем сортировку записей в порядке убывания по полю CompanyName (рис. 9.11, Б):


Рис. 9.11.  Сортировка записей по полю City (А), сортировка записей по полю ContactName (Б)

В программном обеспечении к курсу вы найдете приложение VisualData View (Code\Glava4\ VisualDataView).

Приступим теперь к рассмотрению этих же свойств объекта DataView, создаваемых программным образом. Скопируйте папку приложения ProgrammDataSQL и переименуйте ее в ProgrammDataView. Добавляем на форму второй DataGrid и Splitter точно так же, как мы это сделали в предыдущем приложении. Переходим в конструктор формы и изменяем код следующим образом:

public Form1() { InitializeComponent(); SqlDataAdapter dataAdapter = new SqlDataAdapter(CommandText, ConnectionString); DataSet ds = new DataSet(); //Создаем экземпляр dtCustomer объекта DataTable DataTable dtCustomer = ds.Tables.Add("Customer"); dataAdapter.Fill(ds, "Customer"); dataGrid1.DataSource = ds.Tables["Customer"].DefaultView; //Создаем экземпляр myDataView объекта DataView, //передаем ему объект dtCustomer DataView myDataView = new DataView(dtCustomer); //Устанавливаем фильтр для удаленных записей myDataView.RowStateFilter = DataViewRowState.Deleted; //Для второго элемента DataGrid указываем myDataView //в качестве источника данных.


dataGrid2.DataSource = myDataView; }

Объект DataView представляет данные объекта DataTable - именно поэтому нам пришлось создать экземпляр dtCustomer. Запускаем приложение и проверяем его функциональность - результат должен быть в точности такой же, как и ранее.

Для вывода новых строк (DataViewRowState.Added) и (символ "|" ) измененных (DataViewRowState.ModifiedCurrent) фильтр будет выглядеть так:

myDataView.RowStateFilter = ((DataViewRowState)((DataViewRowState.Added | DataViewRowState.ModifiedCurrent)));

Закомментируем в коде свойство RowStateFilter. Для вывода фильтров по свойству RowFilter используем следующие фрагменты кода:

//Выводим все записи со значением 'London' поля City: myDataView.RowFilter = "City = 'London'";

Или

//Выводим все записи со значением 'Thomas Hardy' поля ContactName: myDataView.RowFilter = "ContactName = 'Thomas Hardy'";

Закомментируем после проверки в коде эти фрагменты и добавим сортировку записей:

// Сортируем записи по полю "City" в порядке возрастания myDataView.Sort = "City ASC";

Или

//Сортируем записи по полю ContactName в порядке убывания myDataView.Sort = "ContactName DESC";

В программном обеспечении к курсу вы найдете приложение Programm DataView (Code\Glava4\ ProgrammDataView).


Поиск данных


Технология ADO .NET предоставляет значительные возможности для поиска данных - такие объекты, как DataSet, DataTable, содержат специализированные методы для быстрого решения этой задачи. Между тем свойство RowFilter объекта DataView может применяться для создания простого и эффективного поиска. Запускаем Visual Studio .NET и создаем новый проект. В окне New Project выбираем тип Windows Control Library, называем его "FindControl" (рис. 9.16):


Рис. 9.16.  Выбор шаблона Windows Control Library

Появляется форма пользовательского (композитного) элемента управления. Устанавливаем следующие свойства:

UserControl1, свойство Значение
Name FindCheckBox
Size 230; 70

В окне Solution Explorer изменяем название "UserControl1.cs" на "FindCheckBox.cs". Перетаскиваем на форму из окна Toolbox текстовое поле, надпись и элемент CheckBox. Настраиваем их свойства:

CheckBox1, свойство ЗначениеtextBox1, свойство Значение
Name chbForSearching
Location 8; 32
Text
Name txtColumnValue
Location 32; 32
Size 184; 20
Text
label1, свойство Значение
Name lblColumnName
Location 40; 8
Text ColumnName

Переходим к коду. В конструкторе формы отключаем доступность текстового поля и привязываем обработчик события CheckedChanged для элемента CheckBox:

public FindCheckBox() { InitializeComponent(); txtColumnValue.Enabled = false; chbForSearching.CheckedChanged += new EventHandler(chbForSearching_CheckedChanged); }

В классе формы создаем обработчик события CheckedChanged для CheckBox и определяем действия для значений текстового поля, надписи и элемента CheckBox:

private void chbForSearching_CheckedChanged(object sender, EventArgs e) { txtColumnValue.Enabled = chbForSearching.Checked; } [Category("Appearance"), Description("Название поля, по которому будет осуществляться поиск")] public string ColumnName { get {return lblColumnName.Text;} set {lblColumnName.Text = value;} } [Category("Appearance"), Description("Ключевое слово для поиска")] public string ColumnValue { get {return txtColumnValue.Text;} set {txtColumnValue.Text = value;} }


[Category("Appearance"), Description("Включение поиска по заданному полю")] public bool SearchEnabled { get {return chbForSearching.Checked;} set {chbForSearching.Checked = value;} }

Свойства ColumnName, ColumnValue и SearchEnabled будут свойствами композитного элемента управления, которые будут отображаться в его окне Properties. В квадратных скобках указан атрибут для помещения свойства в заданную группу и описание, выводимое на информационную панель. Компилируем приложение (Ctrl+Shift+B) и закрываем его. Создаем новое Windows-приложение, называем его "FindCustomers". Перетаскиваем на форму следующие элементы управления: Panel (свойству Dock устанавливаем значение Left), Splitter и DataGrid (свойству Dock устанавливаем значение Fill). В окне Toolbox щелкаем правой кнопкой мыши на вкладке Windows Forms и в появившемся контекстном меню выбираем пункт "Add \ Remove Items_". В появившемся окне Customize Toolbox на вкладке .NET Framework Components нажимаем кнопку "Browse" (рис. 9.17, А) и переходим в директор ию, где расположена сборка композитного элемента FindControl (у меня это D:\Uchebnik\Code\Glava4\FindControl\bin\Debug, сборка FindControl.dll) (рис. 9.17, Б). После выбора сборки она появляется в окне Customize Toolbox (рис. 9.17, В).


увеличить изображение
Рис. 9.17.  Добавление композитного элемента. А - окно "Customize Toolbox", Б - Выбор сборки FindControl.dll, В - добавленная сборка на вкладке ".NET Framework Components"

Нажимаем кнопку "OK" и перетаскиваем на панель формы из окна Toolbox четыре копии элемента FindCheckBox. Добавляем также кнопку поиска (рис. 9.18). Устанавливаем следующие свойства элементов:

findCheckBox1, свойство Значение
ColumnName CustomerID
Name fcbCustomerID
findCheckBox2, свойство Значение
ColumnNameCompanyName
Name fcbCompanyName
findCheckBox3, свойство Значение
ColumnName ContactName
Name FcbContactName
findCheckBox4, свойство Значение
ColumnName Phone
Name fcbPhone
button1, свойство Значение
Name btnSearch
Text Поиск



увеличить изображение
Рис. 9.18.  Расположение элементов на форме. Окно Properties композитного элемента

Обратите внимание (рис. 9.18), что в окне Properties, при групповом расположении, свойство ColumnName находится в группе Appearance. На информационную панель выводится описание этого свойства на русском языке. Именно эти параметры мы и указывали при создании композитного элемента.

Для того чтобы упростить код, создадим подключение к базе данных при помощи визуальных средств студии. В окне Toolbox переходим на вкладку Data, перетаскиваем на форму SqlDataAdapter и настраиваем извлечение таблицы Customers базы данных NorthwindCS. Выделяем затем sqlDataAdapter1 на панели компонент, в окне Properties переходим по ссылке "Generate DataSet". Вводим имя "dsCustomers". В конструкторе формы добавляем код для заполнения элемента DataGrid данными при загрузке:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dsCustomers1); dataGrid1.DataSource = dsCustomers1.Tables["Customers"].DefaultView; }

В классе формы создаем метод FindCustomers, в котором будет осуществляться обработка1):

private void FindCustomers() { //Создаем экземпляр filteringFields класса ArrayList ArrayList filteringFields = new ArrayList(); //Если элемент fcbCustomerID доступен для поиска if (fcbCustomerID.SearchEnabled) //Добавляем в массив filteringFields значение текстового поля ColumnValue filteringFields.Add("CustomerID LIKE \'" + fcbCustomerID.ColumnValue + "%\'");

if (fcbCompanyName.SearchEnabled) filteringFields.Add("CompanyName LIKE \'" + fcbCompanyName.ColumnValue + "%\'");

if (fcbContactName.SearchEnabled) filteringFields.Add("ContactName LIKE \'" + fcbContactName.ColumnValue + "%\'");

if (fcbPhone.SearchEnabled) filteringFields.Add("Phone LIKE \'" + fcbPhone.ColumnValue + "%\'");

string filter = ""; //Комбинируем введенные в текстовые поля значения. //Для объединения используем логический оператор "ИЛИ" if (filteringFields.Count == 1) filter = filteringFields[0].ToString(); else if (filteringFields.Count > 1) { for(int i = 0; i < filteringFields.Count - 1; i++) filter += filteringFields[i].ToString() + " OR "; //Для объединения полей в запросе используем логический оператор "И" //for(int i = 0; i < filteringFields.Count - 1; i++) //filter += filteringFields[i].ToString() + " AND ";



filter += filteringFields[filteringFields.Count - 1].ToString(); } // Создаем экземпляр dvSearch класса DataView DataView dvSearch = new DataView(dsCustomers1.Customers); //Передаем свойству RowFilter объекта DataView скомбинированное значение filter dvSearch.RowFilter = filter; dataGrid1.DataSource = dvSearch; }

Добавляем обработчик кнопки "Поиск":

private void btnSearch_Click(object sender, System.EventArgs e) { try { FindCustomers(); } catch(Exception ex) { MessageBox.Show(ex.Message); } }

Запускаем приложение. При определении логического оператора "ИЛИ" в методе FindCustomers поисковый запрос выводит записи, содержащие хотя бы одно значение из введенных полей (рис. 9.19, А), при определении оператора "И" - запись, содержащую все эти значения (рис. 9.19, Б).


увеличить изображение
Рис. 9.19.  Готовое приложение "FindCustomers". А - Запрос с оператором "ИЛИ" возвращает две записи, содержащие в разных полях введенное значение. Б - Запрос с оператором "И" не возвращает ничего, поскольку в таблице нет ни одной записи, содержащей оба значения

В программном обеспечении к курсу вы найдете приложения FindControl и FindCustomers (Code\Glava4\ FindControl и FindCustomers).


Свойство PrimaryKey


Мы научились конструировать структуру таблицы в объекте DataSet, а также определять отношения между таблицами. Во всех случаях для выделения первичного ключа в таблице использовалось свойство Unique. Например, в самом последнем примере - проекте Programm2DataGrid2Table - первичный ключ "Код туриста" определялся так:

... DataColumn dcTouristID = new DataColumn("Код туриста", typeof(int)); dcTouristID.Unique = true; ...

DataColumn dcInfoTouristsID = new DataColumn("Код туриста", typeof(int)); dcInfoTouristsID.Unique = true; ...

Для вывода таблиц идентификации записей этого определения вполне хватало. Однако свойство Unique всего лишь указывает на уникальность заданного поля, т.е. на отсутствие повторяющихся записей. В самом деле, в таблице может быть несколько полей, которые должны быть уникальными и одно из них (или их комбинация) будут образовывать первичный ключ. Для указания именно первичного ключа используется свойство PrimaryKey объекта DataTable:

DataTable dtTourists = new DataTable("Туристы"); DataColumn dcTouristID = new DataColumn("Код туриста", typeof(int)); dtTourists.PrimaryKey = new DataColumn [] {dtTourists.Columns["Код туриста"]};

В сокращенной записи определение будет такое:

dtTourists.Columns.Add("Код туриста", typeof(int)); dtTourists.PrimaryKey = new DataColumn [] {dtTourists.Columns["Код туриста"]};

Можно применять комбинацию полей для задания первичного ключа:

DataTable dtTourists = new DataTable("Туристы"); DataColumn dcTouristID = new DataColumn("Код туриста", typeof(int)); DataColumn dcLastName = new DataColumn("Фамилия",typeof(string)); dtTourists.PrimaryKey = new DataColumn [] {dtTourists.Columns["Код туриста"], dtTourists.Columns["Фамилия"]};

Здесь первичным ключом будут значения поля "Код туриста" в сочетании со значением поля "Фамилия".

При конструировании объекта DataTable в редакторе Tables Collection Editor после создания объектов Columns в свойстве PrimaryKey можно выделять одно или несколько полей - они отмечаются числами и становятся первичным ключом (рис. 9.15):


Рис. 9.15.  Определение первичного ключа в редакторе Tables Collection Editor

После определения первичного ключа объекта DataTable для свойства AllowDBNull (разрешение значений null) объектов DataColumn, формирующих ключ, будет установлено значение false.

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



Наиболее часто встречаемая задача при


Наиболее часто встречаемая задача при разработке приложений, связанных с базами данных: одновременный вывод двух таблиц на форму, причем при перемещении по записям главной таблицы в дочерней автоматически отображаются связанные записи. Мы сделаем два приложения - одно будет создано с использованием визуальных инструментов студии, другое - полностью программно. Создайте новый Windows-проект и назовите его Visual2DataGrid2Table. Из окна Toolbox перетаскиваем на форму элемент DataGrid, свойству Dock этого элемента устанавливаем значение Bottom. Добавляем элемент Splitter, его свойству Dock также устанавливаем значение Bottom. Наконец, перетаскиваем второй DataGrid, свойству Dock устанавливаем значение Fill. Дополнительно определяем следующие свойства элементов:

dataGrid1, свойство Значение
Name dgInfoTourists
CaptionText Информация о туристах
dataGrid2, свойство Значение
Name dgTourists
CaptionText Туристы
Переходим на вкладку Data окна Toolbox, перетаскиваем на форму элемент DataSet, в поле его свойства Name вводим значение "dsTourists". В появившемся окне Add DataSet выбираем "Untyped DataSet". Выделяем объект dataSet1 и в окне Properties, в поле свойства Tables нажимаем на кнопку
(...). В редакторе Tables Collection Editor нажимаем кнопку "Add" для добавления таблицы "Туристы" (TableName - "Туристы", Name - "dtTourists"). В поле свойства Columns нажимаем на кнопку
(...) и создаем следующие столбцы:

Поле Column1, свойство Значение
ColumnName Код туриста
DataType System.Int32
Unique True
Name dcTouristID
Поле Column2, свойство Значение
ColumnName Фамилия
Name dcLastName
Поле Column3, свойство Значение
ColumnName Имя
Name dcFirstName
Поле Column4, свойство Значение
ColumnName Отчество
Name dcMiddleName
Аналогично, в редакторе Tables Collection Editor создаем таблицу "Информация о туристах", а затем в редакторе Columns Collection Editor создаем ее поля:

TableName Информация о туристах
Name dtInfoTourists
Поле Column1, свойство Значение
ColumnName Код туриста
DataType System.Int32
Unique True
Name dcInfoTouristsID
Поле Column2, свойство Значение
ColumnName Серия паспорта
Name dcPassport
Поле Column3, свойство Значение
ColumnName Город
Name dcCity
Поле Column4, свойство Значение
ColumnName Страна
Name dcCountry
Поле Column5, свойство Значение
ColumnName Телефон
Name dcPhone
Поле Column6, свойство Значение
ColumnName Индекс
Name dcIndex


Завершив настройку таблиц и колонок, переходим к определению связи между таблицами. Выделяем объект dataSet1, в окне Properties в поле свойства Relations щелкаем на кнопку
(...). В появившемся редакторе Relation Collection Editor нажимаем кнопку "Add" и создаем отношение "Дополнительная_информация" между таблицами по ключевому полю "Код туриста". Завершив работу с редактором, переходим в код формы. Подключаем пространство имен для работы с базой данных Microsoft Access:
using System.Data.OleDb;
Добавляем строки для подключения к базе и извлечения данных:
string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0""; Data Source=""D:\Uchebnik\Code\Glava1\BDTur_firm.mdb""; User ID=Admin;Jet OLEDB:Encrypt Database=False"; string commandText = "SELECT [Код туриста], Фамилия, Имя, Отчество FROM Туристы"; string commandText2 = "SELECT [Код туриста], [Серия паспорта], Город, Страна, Телефон, Индекс FROM [Информация о туристах]";
В конструкторе формы создаем объекты для заполнения данными DataSet:
public Form1() {
InitializeComponent();
OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand;
OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); dataAdapter2.SelectCommand = myCommand2;
conn.Open(); dataAdapter.Fill(dsTourists.Tables["Туристы"]); dataAdapter2.Fill(dsTourists.Tables["Информация о туристах"]); conn.Close();
dgTourists.DataSource = dsTourists; dgTourists.DataMember = "Туристы";
dgInfoTourists.DataSource = dsTourists; dgInfoTourists.DataMember = "Туристы.Дополнительная_информация"; }
Запускаем приложение (рис. 9.14):



При перемещении по записям таблицы "Туристы" автоматически выводятся связанные записи в таблице "Информация о туристах" ' width="620" height="384">

увеличить изображение
Рис. 9.14.  Готовое приложение Visual2DataGrid2Table. При перемещении по записям таблицы "Туристы" автоматически выводятся связанные записи в таблице "Информация о туристах"
Ключевым моментом здесь является определение связи "Дополнительная_информация" в содержимом элемента dgInfoTourists:
dgInfoTourists.DataMember = "Туристы.Дополнительная_информация";
Эта же связь доступна и в окне родительской таблицы - при нажатии на гиперссылку возвращается связанная запись.
В программном обеспечении к курсу вы найдете приложение Visual2 DataGrid2Table (Code\Glava4\ Visual2DataGrid2Table).
Рассмотрим теперь программную реализацию этого же приложения. Создайте новый Windows-проект и назовите его "Programm2DataGrid2Table". Скопируем из проекта Visual2DataGrid2Table два элемента DataGrid, Splitter и вставим в новую форму. Пространство имен, строки connectionString и commandText будут в точности такие же, как и в предыдущем проекте, - просто скопируйте их. Конструктор формы будет иметь следующий вид:
public Form1() { InitializeComponent(); // Создаем объект dtTourists для таблицы "Туристы" DataTable dtTourists = new DataTable("Туристы"); DataColumn dcTouristID = new DataColumn("Код туриста", typeof(int)); dcTouristID.Unique = true; DataColumn dcLastName = new DataColumn("Фамилия",typeof(string)); DataColumn dcFirstName = new DataColumn("Имя", typeof(string)); DataColumn dcMiddleName = new DataColumn("Отчество", typeof(string)); dtTourists.Columns.AddRange(new DataColumn[] { dcTouristID, dcLastName, dcFirstName, dcMiddleName }); // Создаем объект dtInfoTourists для таблицы "Информация о туристах" DataTable dtInfoTourists = new DataTable("Информация о туристах"); DataColumn dcInfoTouristsID = new DataColumn("Код туриста", typeof(int)); dcInfoTouristsID.Unique = true; DataColumn dcPassport = new DataColumn("Серия паспорта", typeof(string)); DataColumn dcCity = new DataColumn("Город", typeof(string)); DataColumn dcCountry = new DataColumn("Страна", typeof(string)); DataColumn dcPhone = new DataColumn("Телефон", typeof(decimal)); DataColumn dcIndex = new DataColumn("Индекс", typeof(decimal));


dtInfoTourists.Columns.AddRange(new DataColumn[] { dcInfoTouristsID, dcPassport, dcCity, dcCountry, dcPhone, dcIndex });
DataSet dsTourists = new DataSet(); // Добавляем таблицы в DataSet dsTourists.Tables.AddRange(new DataTable[] { dtTourists, dtInfoTourists});
// Создаем отношение между таблицами dsTourists.Relations.Add(new DataRelation("Дополнительная_информация", dcTouristID, dcInfoTouristsID));
//Подключаемся к базе и выводим данные OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand;
OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); dataAdapter2.SelectCommand = myCommand2;
dataAdapter.Fill(dsTourists.Tables["Туристы"]); dataAdapter2.Fill(dsTourists.Tables["Информация о туристах"]); dgTourists.DataSource = dsTourists; dgTourists.DataMember = "Туристы";
dgInfoTourists.DataSource = dsTourists; dgInfoTourists.DataMember = "Туристы.Дополнительная_информация"; }
Запускаем приложение - его функциональность должна быть в точности такая же, как и в проекте Visual2DataGrid2Table.
В программном обеспечении к курсу вы найдете приложение Programm2 DataGrid2Table (Code\Glava4\ Programm2DataGrid2Table).

Вывод связанных таблиц. Вывод двух таблиц в один элемент DataGrid


База данных Microsoft Access BDTur_firm.mdb содержит таблицу "Туристы", которая связана с другими таблицами. Было бы удобно выводить эту таблицу в элемент DataGrid вместе с другими таблицами, а также выводить связанные записи этой таблицы. Создайте новое Windows-приложение и назовите его DataGrid2Table. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. Переходим в код формы и подключаем пространство имен:

using System.Data.OleDb;

В конструкторе формы создаем соединение, объект OleDbCommand, определяем для него соединение и строку CommandText:

OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText;

Подключаемся к файлу базы данных BDTur_firm.mdb, указываем соответствующие параметры строк connectionString и commandText:

string commandText = "SELECT [Код туриста], Фамилия, Имя, Отчество FROM Туристы"; string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" ";Data Source=""D:\Uchebnik\Code\Glava1\BDTur_firm.mdb""; ID=Admin;Jet OLEDB:Encrypt Database=False";

Создаем объект DataAdapter и в свойстве SelectCommand устанавливаем значение myCommand, открываем соединение:

OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand; conn.Open();

Создаем объект DataSet:

DataSet ds = new DataSet();

В объекте DataSet здесь будут храниться две таблицы - главная и связанная с ней дочерняя. Поэтому воспользуемся свойством TableMappings объекта DataAdapter для занесения в него первой таблицы "Туристы":

DataSet ds = new DataSet(); dataAdapter.TableMappings.Add("Table", "Туристы"); dataAdapter.Fill(ds);

Свойству DataSource объекта dataGrid1 указываем таблицу "Туристы" объекта ds. Обратите внимание на синтаксис - свойство Tables подразумевает наличие нескольких таблиц в объекте DataSet:


dataGrid1.DataSource = ds.Tables["Туристы"].DefaultView;
Закрываем соединение:
conn.Close();
Запускаем приложение. Пока на форме появляется только одна таблица. Закрываем приложение и переходим в код формы. Теперь нам следует добавить объекты OleDbDataAdapter и OleDbCommand для таблицы "Информация о туристах":
OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter();
Обратите внимание, что dataAdapter2 использует то же самое подключение conn, что и dataAdapter.
Строку commandText2 определим следующим образом:
string commandText2 = "SELECT [Код туриста], [Серия паспорта], Город, Страна, Телефон, Индекс FROM [Информация о туристах]";
Теперь свяжем второй объект OleDbDataAdapter с только что созданной второй командой и отобразим "Информацию о туристах" на его таблицу. Затем можно заполнить объект DataSet данными из второй таблицы:
dataAdapter2.SelectCommand = myCommand2; dataAdapter2.TableMappings.Add("Table", "Информация о туристах"); dataAdapter2.Fill(ds);
В итоге у нас получился объект DataSet с двумя таблицами. Теперь можно выводить одну из этих таблиц на форму, или две сразу. Но связь между таблицами еще не создана. Для конфигурирования отношения по полю "Код туриста" создаем два объекта DataColumn:
DataColumn dcTouristsID = ds.Tables["Туристы"].Columns["Код туриста"]; DataColumn dcInfoTouristsID = ds.Tables["Информация о туристах"].Columns["Код туриста"];
Создаем объект DataRelation, в его конструкторе передаем название отношения между таблицами и два объекта DataColumn:
DataRelation dataRelation = new DataRelation("Дополнительная информация", dcTouristsID, dcInfoTouristsID);
Добавляем созданный объект отношения к объекту DataSet:
ds.Relations.Add(dataRelation);
Создаем объект DataViewManager, отвечающий за отображение DataSet в объекте DataGrid:


DataViewManager dsview = ds.DefaultViewManager;
Присваиваем свойству DataSource объекта DataGrid созданный объект DataViewManager:
dataGrid1.DataSource = dsview;
Последнее, что нам осталось сделать, - сообщить объекту DataGrid, какую таблицу считать главной (родительской) и, соответственно, отображать на форме:
dataGrid1.DataMember = "Туристы";
Закрываем соединение:
conn.Close();
Запускаем приложение. Теперь можно просматривать связанные записи (рис. 9.12):

увеличить изображение
Рис. 9.12.  Для перехода на дочернюю запись нажимаем на ссылку "Дополнительная информация". Для возвращения на родительскую запись нажимаем на кнопку со стрелкой
Обратите внимание, что здесь мы не стали накладывать ограничения - это сделано для упрощения кода.
Конструктор формы будет иметь следующий вид:
public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand; conn.Open();
DataSet ds = new DataSet(); dataAdapter.TableMappings.Add("Table", "Туристы"); dataAdapter.Fill(ds);
OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); dataAdapter2.SelectCommand = myCommand2; dataAdapter2.TableMappings.Add("Table", "Информация о туристах"); dataAdapter2.Fill(ds);
DataColumn dcTouristsID = ds.Tables["Туристы"].Columns["Код туриста"]; DataColumn dcInfoTouristsID = ds.Tables["Информация о туристах"].Columns["Код туриста"]; DataRelation dataRelation = new DataRelation("Дополнительная информация", dcTouristsID, dcInfoTouristsID); ds.Relations.Add(dataRelation); DataViewManager dsview = ds.DefaultViewManager; dataGrid1.DataSource = dsview; dataGrid1.DataMember = "Туристы"; conn.Close(); }


Закомментируем фрагмент кода, отвечающий за создание связи:
//DataColumn dcTouristsID = ds.Tables["Туристы"].Columns["Код туриста"]; //DataColumn dcInfoTouristsID = // ds.Tables["Информация о туристах"].Columns["Код туриста"]; //DataRelation dataRelation = // new DataRelation("Дополнительная информация", dcTouristsID, dcInfoTouristsID); //ds.Relations.Add(dataRelation); DataViewManager dsview = ds.DefaultViewManager; dataGrid1.DataSource = dsview; //dataGrid1.DataMember = "Туристы"; conn.Close();
Теперь в приложении будут отдельные ссылки на две таблицы - "Туристы" (рис. 9.13, А) и "Информация о туристах" (рис. 9.13, Б):

увеличить изображение
Рис. 9.13.  Переход на две таблицы - "Туристы" (А) и "Информация о туристах" (Б)
В программном обеспечении к курсу вы найдете приложение DataGrid2 Table (Code\Glava4\ DataGrid2Table).

Атрибуты XML-документов


Атрибуты или свойства HTML-элементов представляют собой наиболее наглядное понятие - мы сталкивались с ними при определении фонового цвета странички "Пример HTML-документа.htm":

<BODY BGCOLOR = LIGHTGREY>

при определении шрифта и его цвета:

<FONT COLOR = RED SIZE = 16 FACE = ARIAL>

Описание атрибута представляет собой пару "имя (BGCOLOR) - значение (LIGHTGREY)", атрибуты отвечают в основном за вид элементов на web-странице (см. рис. 10.2).

Атрибуты для XML-элементов предназначены для более широкого спектра задач, это своеобразные метки, по которым можно как форматировать затем элемент, применяя CSS или XSL, так и группировать. Они также используются для хранения различных свойств элемента, однако эти свойства не обязательно будут отображены. В спецификации XML не установлено строгих разграничений относительно типа информации, которую можно описывать с помощью атрибутов или внутри содержимого элемента.

Описание атрибута состоит из имени атрибута, вслед за которым идет знак равенства и значение атрибута. Можно выбирать любое имя атрибута, придерживаясь при этом следующих правил (таблица 10.11).

Таблица 10.11. Некоторые правила для атрибутов XML-документов

ПравилоПравильноНеправильно
Имя атрибута должно начинаться с буквы или символа подчеркивания (_), после чего могут следовать другие буквы, цифры, символы точки (.), тире (-) или подчеркивания<TOUR _Open = "yes"><TOUR 1Open = "yes">
Каждое имя атрибута может только один раз присутствовать в одном и том же начальном теге или в теге пустого элемента<TOUR Open = "yes"><TOUR Open = "yes" Open ="undefined" >
Значение атрибута должно быть заключено в одинарные (') или в двойные кавычки (")

<TOUR Open = "yes"> или <TOUR Open = 'yes'>

<TOUR Open = yes>

Значение атрибута не может содержать внутри себя тот же символ кавычек, которыми оно ограничено

<TOUR Open = '"yes"'> или <TOUR Open = "Only 'yes' allowed">

<TOUR Open = ' 'yes' '> или <TOUR Open = ""yes"">или <TOUR Open = "Only "yes" allowed">

Значение атрибута не может содержать символ < (синтаксический анализатор может воспринять этот символ как начало описания XML-разметки)

<TOUR Open = "yes">

<TOUR Open = "<yes>">

Среда Visual Studio .NET выделяет атрибуты красным шрифтом, при просмотре XML-документа их легко выделять. Мы уже сталкивались с атрибутами при создании самого первого XML-документа:

<?xml version="1.0" encoding="utf-8" ?>

Здесь version и encoding - атрибуты, а "1.0" и "utf-8" - их значения соответственно.



Что такое HTML?


Язык разметки гипертекста HTML (Hyper Text Markup Language) представляет собой определенный набор элементов, которые используются для создания web-страницы. Примерами таких элементов являются заголовки, абзацы, списки, таблицы. Запускаем блокнот и вводим следующий текст:

<HTML> <HEAD> <TITLE>Это пример простейшего HTML-документа</TITLE> </HEAD> <BODY BGCOLOR = LIGHTGREY>

Несколько примеров

<BR> <B> Жирный текст </B> <U>Подчеркнутый текст</U> <S>Перечеркнутый текст</S> <P>Абзац</P> <BR> <FONT COLOR = RED SIZE = 16 FACE = ARIAL> Красный </FONT> <BR> <FONT COLOR = YELLOW SIZE = 16 FACE = TIMES NEW ROMAN> Желтый </FONT> <BR> <FONT COLOR = GREEN SIZE = 16 FACE = VERDANA> Зеленый </FONT> <BR> <MARQUEE BGCOLOR = BLACK> <FONT COLOR = WHITE> Бегущая строка </FONT></MARQUEE> </BODY> </HTML>

Сохраняем текст, назвав документ "Пример HTML-документа". Открываем "Мой компьютер \ Сервис \ Свойства папки \ вкладка "Вид"" и в окне "Дополнительные параметры" снимаем галочку "Скрывать расширения для зарегистрированных типов файлов". Теперь на компьютере все файлы будут в своем названии содержать и тип: например, рисунки - .bmp или .jpeg, программы - .exe, а файл блокнота - "Пример HTML-документа.txt". Меняем расширение на "Пример HTML-документа.htm" и запускаем. Появляется браузер, в котором открывается созданная страница (рис. 10.1):


Рис. 10.1.  Готовая страница в Internet Explorer

В главном меню выбираем "Вид \ Просмотр НТМL-кода" - появляется блокнот, содержащий набранный текст (рис. 10.2, подписи были добавлены к графическому изображению):


Рис. 10.2.  Просмотр HTML-кода страницы

Обратите внимание на форматирование HTML-кода - браузер игнорирует пробелы, отступы и абзацы; для их вывода на страницу используются специальные элементы.
Каждый элемент начинается с начального тега: текста, заключенного в угловые скобки (< >), который содержит имя элемента. В скобках указываются также дополнительные свойства (атрибуты) тега, например, серый фоновый цвет страницы определяет атрибут BGCOLOR со значением LIGHTGREY элемента BODY. Большинство элементов заканчиваются конечным тегом, в котором повторяется название элемента с символом косой черты(/), но другие могут оканчиваться иначе - например, тег <BR>. Элемент "содержание" представляет собой текст, расположенный между начальным и конечным тегами. Сами теги могут быть написаны прописными (<BODY>) или строчными буквами(<body>) - в любом случае браузер отобразит страницу верно. Для формирования страницы были использованы следующие эл ементы (Таблица 10.1).

Таблица 10.1. Некоторые HTML-элементыHTML-элементОписание
<HTML></HTML>Ключевой элемент, текст внутри него и представляет код страницы
<HEAD></HEAD>"Голова", шапка страницы
<TITLE></TITLE>Заголовок, отображаемый браузером
<BODY></BODY>"Тело" страницы, ее содержимое
BGCOLORАтрибут, задающий цвет заднего фона
<BR>Новая строка
<B></B>Жирный текст
<U></U>Подчеркнутый текст
<S></S>Перечеркнутый текст
<P></P>Абзац
<FONT></FONT>Оформление шрифта
COLORЦвет
SIZEРазмер
FACEВид шрифта
<MARQUEE></MARQUEE>Бегущая строка
Набор HTML-элементов достаточно велик, но есть документы, которые не могут быть описаны на языке HTML. Это математические, химические формулы, музыкальные символы, иерархические структуры. При выводе содержимого из базы данных HTML представляет данные в виде таблиц, однако в нем нет возможности определения ключей таблиц для последующей сортировки, поиска и фильтрации данных.

Созданная HTML страничка элементарна, тем не менее в программном обеспечении к курсу вы ее найдете. (Code\Glava5\ Пример HTML-документа.htm).


Что такое каскадные таблицы стилей (CSS)?


Для определения свойств текста в HTML-документе, таких как размер шрифта, стиль, начертание, цвет, используются атрибуты тегов, в которых указываются нужные параметры. Для нескольких небольших страничек с уникальным дизайном применение атрибутов, относящихся к дизайну, вполне оправданно. Для крупных проектов, например, электронного журнала или портала новостей, где требуется соблюдать одинаковое оформление и иметь возможность быстро его заменять, возникает идея отделить содержимое страницы от его оформления. Это можно сделать с помощью таблиц каскадных стилей (Cascade Style Sheets, CSS). Для присвоения какому-либо элементу определенных характеристик необходимо один раз описать этот элемент и определить это описание как стиль, а в дальнейшем просто указывать, что элемент, который нужно оформить соответствующим образом, должен принять свойства описанного стиля. Эта концепция позволяет также сократить размер страницы - вместо многократного повторения заданного атрибута получаем всего лишь одно описание его стиля.

В название таблицы стилей включено определение "каскадные", поскольку возможно использование нескольких таблиц стилей для форматирования одного документа HTML, а браузер по определенным правилам выстраивает приоритетность выполнения этих таблиц. Они выстраиваются своеобразным "каскадом", по которому обрабатывается документ.

Существует четыре основных способа связывания HTML-документа и таблицы стилей.

Связывание - одна таблица стилей используется для форматирования нескольких страниц.Внедрение - все правила таблицы стилей задаются непосредственно в самом документе. Встраивание в теги документа - изменяется форматирование конкретных элементов страницы. Импортирование - в один или несколько документов встраивается таблица стилей, расположенная на сервере.

Рассмотрим каждый из этих способов в отдельности.

Связывание

Запускаем Visual Studio .NET и создаем три HTML-страницы (см. рис. 10.3, шаблон HTML-Page), которые называем 1, 2 и 3. Затем создаем файл MyStyle.css, для которого выбираем шаблон Style Sheet (см.
рис. 10.3). Сохраняем все эти файлы в одну папку, которую называем "Связывание". Содержимое и вид страниц после запуска приводится в таблице 10.3.

Таблица 10.3. Применение CSS: связываниеНазвание файла Содержимое Вид в Internet Explorer
1.htm

<html> <head> <title>Первая страница</title> <LINK REL=STYLESHEET TYPE="text/css" HREF="MyStyle.css"></LINK> </head> <body> <h1>Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> </body> </html>
2.htm

<html> <head> <title>Вторая страница</title> <LINK REL=STYLESHEET TYPE="text/css" HREF="MyStyle.css"></LINK> </head> <body> <h4>Четвертый заголовок</h4> <h5>Пятый заголовок</h5> <h6>Шестой заголовок</h6> </body> </html>
3.htm

<html> <head> <title>Третья страница</title> <LINK REL=STYLESHEET TYPE="text/css" HREF="MyStyle.css"></LINK> </head> <body> <h1>Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> <h4>Четвертый заголовок</h4> <h5>Пятый заголовок</h5> <h6>Шестой заголовок</h6> </body> </html>
MyStyle.css

BODY {background: Dodgerblue } H1{font-family:Arial; color:Red; font-size:medium} H2{font-family:Bookman Old Style; color:Orangered; font-size:medium} H3{font-family:Comic Sans MS; color:Yellow; font-size:medium} H4{font-family:Courier New; color:Lime; font-size:medium} H5{font-family:Times New Roman; color:Mediumblue; font-size:medium} H6{font-family:Verdana; color:Darkviolet; font-size:medium}
Элементы h1, h2, ..., h6 используются для создания заголовков текста. Без применения стиля самый крупный заголовок - h1, самый мелкий - h6.

Итак, в самих документах нет описания того, как должны выглядеть страницы.


Вместо этого во всех трех страницах есть инструкция, указывающая на файл MyStyle.css:

<LINK REL=STYLESHEET TYPE="text/css" HREF="MyStyle.css"></LINK>

Первые два параметра этого тега являются зарезервированными именами, требующимися для того, чтобы сообщить браузеру, что на этой страничке будет использоваться CSS. Третий параметр - HREF= "URL" - указывает на файл, который содержит описания стилей. Этот параметр должен содержать либо относительный путь к файлу - в случае если он находится на том же сервере, что и документ, из которого к нему обращаются, - либо полный URL ("http://...") в случае если файл стилей находится на другом сервере.

Файл MyStyle.css не выводится бразуером - в нем хранится лишь описание стилей. В рассматриваемом примере элементу BODY, отвечающему за содержимое страницы, устанавливается значение фонового цвета Dodgerblue:

BODY {background: Dodgerblue}

Вначале указывается название элемента (BODY), затем атрибут (background) со значением после двоеточия (Dodgerblue).

Аналогичная конструкция, задающая значение фонового цвета в виде атрибута, будет иметь следующий вид:

<body bgcolor="Dodgerblue"> </body>

При описании нескольких свойств они разделяются точкой с запятой:

H1{font-family:Arial; color:Red; font-size:medium}

Простое задание атрибутов элемента H1 выглядит так:

<h1><font family="Arial" color="Red" size="medium">Первый заголовок<font></h1>

Вы могли заметить, что названия элементов я иногда писал прописными буквами (H1), а иногда строчными (h1). Для HTML это допустимо (для XML - нет) - браузеры правильно интерпретируют описание в обоих случаях. Также я указывал значения атрибутов иногда в кавычках (bgcolor="Dodgerblue") а иногда - нет (bgcolor = lightgrey). Опять-таки, при работе с HTML-документами так делать можно, при работе с XML - нет (все значения атрибутов должны указываться в кавычках, см.


далее).

При создании таблицы каскадных стилей некоторые свойства отличаются от аналогичных атрибутов - сравните названия "background" и "bgcolor". Сориентироваться при создании таблицы стилей вам помогут всплывающие подсказки Visual Studio .NET (рис. 10.9) и собственный опыт.


Рис. 10.9.  Всплывающая подсказка Visual Studio .NET при работе с шаблоном Style Sheet

В программном обеспечении к курсу вы найдете папку "Связывание" с рассмотренными документами (Code\Glava5\CSS\Связывание).



Внедрение

Другой способ использования CSS - внедрение описания стилей в сам документ. Создадим новую папку, назовем ее "Внедрение" и изменим страницы 1, 2 и 3 следующим образом (таблица 10.4):

Таблица 10.4. Применение CSS: внедрениеНазвание файлаСодержимое
1.htm<html> <head> <title>Первая страница</title> <STYLE TYPE="text/css"> <!-- body {background: Dodgerblue } H1{font-family:Arial; color:Red; font-size:medium} H2{font-family:Bookman Old Style; color:Orangered; font-size:medium} H3{font-family:Comic Sans MS; color:Yellow; font-size:medium} --> </STYLE> </head> <body> <h1>Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> </body> </html>
2.htm<html> <head> <title>Вторая страница</title> <STYLE TYPE="text/css"> <!-- body {background: Dodgerblue } H4{font-family:Courier New; color:Lime; font-size:medium} H5{font-family:Times New Roman; color:Mediumblue; font-size:medium} H6{font-family:Verdana; color:Darkviolet; font-size:medium} --> </STYLE> </head> <body> <h4>Четвертый заголовок</h4> <h5>Пятый заголовок</h5> <h6>Шестой заголовок</h6> </body> </html>
3.htm<html> <head> <title>Третья страница</title> <STYLE TYPE="text/css"> <!-- body {background: Dodgerblue } H1{font-family:Arial; color:Red; font-size:medium} H2{font-family:Bookman Old Style; color:Orangered; font-size:medium} H3{font-family:Comic Sans MS; color:Yellow; font-size:medium} H4{font-family:Courier New; color:Lime; font-size:medium} H5{font-family:Times New Roman; color:Mediumblue; font-size:medium} H6{font-family:Verdana; color:Darkviolet; font-size:medium} --> </STYLE> </head> <body> <h1>Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> <h4>Четвертый заголовок</h4> <h5>Пятый заголовок</h5> <h6>Шестой заголовок</h6> </body> </html>


При запуске этих страниц их внешний вид не изменится, поскольку мы оставили стили страниц прежними. Описание располагается внутри тега STYLE:

<STYLE type="text/css">... </STYLE>.

Сама конструкция окружена символами комментария:

<!-- _-->

Несмотря на то, что подавляющее большинство браузеров поддерживает CSS, всегда следует учитывать обратную возможность - в этом случае знаки комментария позволят проигнорировать описание и избежать ошибки.

В программном обеспечении к курсу вы найдете папку "Внедрение" с рассмотренными документами (Code\Glava5\CSS\Внедрение).



Встраивание в теги документа

Третий вариант, когда описание стиля располагается непосредственно внутри тега описываемого элемента при помощи атрибута STYLE . Этот метод нежелателен, он приводит к потере одного из основных преимуществ CSS - возможности отделения информации от описания оформления информации. Впрочем, если необходимо описать лишь один элемент, этот вариант расположения описания стилей также вполне применим. Создадим новую папку и назовем ее "Встраивание". Скопируем в нее файлы 1.htm и MyStyle.css из папки "Связывание". Теперь у нас есть HTML-документ со связанной таблицей стилей. Сделаем копию документа 1.htm и назовем ее Встраивание1.htm. Изменим HTML-код этого документа:

<html> <head> <title>Первая страница</title> <LINK REL=STYLESHEET TYPE="text/css" HREF="MyStyle.css"></LINK> </head> <body> <h1 STYLE="font-family:Comic Sans MS; color:Indigo; font-size:xx-large">Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> </body> </html>

Таблица стилей по-прежнему работает для этого документа, но описание тега h1 имеет больший приоритет (рис. 10.10):


Рис. 10.10.  Встраивание стиля в тег документа (сравните с табл. 10.3)

В программном обеспечении к курсу вы найдете папку "Встраивание" с рассмотренными документами (Code\Glava5\CSS\Внедрение).



Импортирование

Последний способ применения CSS - импорт внешней таблицы стилей (расположенной на сервере) с помощью свойства @import:

<html> <head> <title>Первая страница</title> <STYLE> @import:url(http://somename.ru/MyStyle.css) </STYLE> </head> <body> <h1>Первый заголовок</h1> <h2>Второй заголовок</h2> <h3>Третий заголовок</h3> </body> </html>


Понятие корректно сформированных (well-formed) XML-документов


Документ называется корректно сформированным, если он соответствует минимальному набору правил для XML-документов:

XML-документ должен иметь только один корневой элемент (элемент "Документ"). Все другие элементы должны быть вложены в корневой элемент. Элементы должны быть вложены упорядоченным образом. Если элемент начинается внутри другого элемента, он должен и заканчиваться внутри этого элемента. Каждый элемент должен иметь начальный и конечный теги. В отличие от HTML, в XML не разрешается опускать конечный тег - даже в том случае, когда браузер в состоянии определить, где заканчивается элемент. Название элемента в начальном теге должно в точности соответствовать (с учетом регистра) названию в соответствующем конечном теге. Название элемента должно начинаться с буквы или с символа подчеркивания ( _ ), после чего могут идти буквы, цифры, символы точки (.), тире (-) или подчеркивания.

Это базовые критерии корректного формирования. Для других понятий языка XML (атрибутов, примитивов, связей) действуют свои правила, которые необходимо соблюдать. Можно сказать, что если документ создан правильно, верно и при его отображении и использовании не возникает никаких ошибок, то это и есть корректно сформированный документ. Если вы ошибетесь в каком-либо теге HTML-страницы, браузер просто проигнорирует соответствующий тег, а ошибка в теге XML сделает невозможным отображение страницы. В этом смысле написание XML похоже на программирование на C# - компилятор не запустит программу при наличии синтаксических ошибок. При наличии одной из ошибок встроенный в Internet Explorer анализатор (его иногда называют XML-процессор или парсер) определяет ее позицию (таблица 10.2).

Таблица 10.2. Ошибки формирования XML-документов

№ОшибкаОписание
1

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <TABLE> : </TABLE> <TABLE2> </TABLE2>

В документе находится два корневых элемента
Результат в Internet Explorer
2

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</TOUR> </INFORMATION> : </TABLE>

Тег INFORMATION начинается внутри тега TOUR, а заканчивается снаружи

Правильная структура:

<TOUR> <INFORMATION> </INFORMATION> </TOUR>

Неправильная:

<TOUR> <INFORMATION> </TOUR> </INFORMATION>

Результат в Internet Explorer
3

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <TABLE> <TOUR> <IDTOUR>1 : </TOUR> : </TABLE>

Нет конечного тега IDTOUR
Результат в Internet Explorer
4

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</name> : </TOUR> : </TABLE>

Регистр начального и конечного тега не совпадают
Результат в Internet Explorer
5

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <1PRICE>25 000,00р. </PRICE> : </TOUR> : </TABLE>

Название элемента 1PRICE начинается с цифры
Результат в Internet Explorer

В программном обеспечении к курсу вы найдете папку "Ошибки_XML", в которой находятся рассмотренные документы XML с ошибками (Code\Glava5\Ошибки_XML).



Применение атрибутов в XSL-схемах для фильтрации выводимого содержимого


Мы уже отмечали преимущества использования XSL-таблиц перед таблицами каскадных стилей. Содержимое XML может фильтроваться или сортироваться при выводе с помощью XSL-таблиц. Пример подобной фильтрации реализован в таблице 10.12.

Таблица 10.12.

XML-документ, XMLTour4.xmlXSL-таблица, XSLTour4.xsl
<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <?xml-stylesheet type="text/xsl" href="XSLTour4.xsl"?>

<TABLE> <TOUR Open = "yes"> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMfTION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMfTION> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMfTION>В августе и сентябре действуют специальные скидки</INFORMfTION> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMfTION>Не включая стоимость авиабилета</INFORMfTION> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMfTION>Завтрак в отеле включен в стоимость путевки</INFORMfTION> </TOUR> <TOUR Open = "yes"> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMfTION> </TOUR> </TABLE>

<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet xmlns:xsl= "http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/"> <H3>Таблица "Туры"</H3> <xsl:for-each select="TABLE/TOUR[@ Open = 'yes']"> <BR/>

<SPAN STYLE= "font-style:italic">Код тура: </SPAN> <xsl:value-of select="IDTOUR"/><BR/> <SPAN STYLE= "font-style:italic">Название: </SPAN> <xsl:value-of select = "NAME"/> <BR/> <SPAN STYLE= "font-style:italic"> Цена: </SPAN> <xsl:value-of select= "PRICE"/><BR/> <SPAN STYLE= "font-style:italic">Информация: </SPAN> <xsl:value-of select= "INFORMATION"/><BR/> </xsl:for-each> </xsl:template> </xsl:stylesheet>

Вид в браузере Internet Explorer


Исходные документы XMLTour2.xml и XSLTour2. xsl были взяты из таблицы 10.8. Для туров с кодами 1 и 5 в тегах TOUR добавлены атрибуты Open:

... <TOUR Open = "yes"> <IDTOUR>1</IDTOUR> ... <TOUR Open = "yes"> <IDTOUR>5</IDTOUR> ...

Таблица XSL трактует атрибут, принадлежащий элементу в XML-документе, как дочерний элемент. Для ссылки на атрибут в образце XSL необходимо предварить имя атрибута символом @, - это указывает, что имя относится к атрибуту, а не к элементу:

<xsl:for-each select="TABLE/TOUR[@ Open = 'yes']">

В результате этого отбора были выведены только те элементы XML-документа, которые содержали описываемый атрибут.

В программном обеспечении к курсу вы найдете файлы XMLTour4.xml и XSLTour4.xsl в папке AttXSL (Code\Glava5\XSL\ AttXSL).

  1)

  WYSIWYG - "What You See Is What You Get" - "Что видишь, то и получаешь".

  2)

  Среда Microsoft Visual Studio.NET не содержит встроенных средств для проверки документа на действительность. Для этого можно использовать специализированные xml-редакторы, например, <oXygen/> (http://www.oxygenxml.com/).

Применение CSS для представления XML


При просмотре документа XMLTour.xml в браузере была представлена определенная структура, однако его внешний вид напоминает скорее внутренний HTML-код web-страницы, чем готовый документ. Дело в том, что мы создали свой набор элементов, и браузер не имеет встроенных средств, позволяющих определить, как его надо отобразить. Создание таблицы каскадных стилей и связывание ее с XML-документом - это один из способов сообщить браузеру, как отображать каждый из элементов документа.

Хранение инструкций по отображению в таблице стилей отдельно от самого XML-документа повышает гибкость XML-документа и облегчает работу с ним. Можно быстро адаптировать один XML-документ к различным условиям отображения (различным браузерам, приложениям, контекстным ситуациям) простым присоединением соответствующей таблицы стилей, без необходимости изменения самого документа.

Мы рассмотрим один из способов связи XML с CSS. Создадим новую папку и назовем ее CSSXML. Скопируем в нее документ XMLTour.xml и MyStyle.css. Во избежание путаницы переименуем MyStyle.css в CSSTour.css, затем откроем этот файл и изменим содержимое следующим образом:

TABLE{display:block; font-family:Arial; color:Red; font-size:medium}

В XML-документе элемент TABLE - корневой. Открываем XMLTour.xml и указываем связь с таблицей стилей:

<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <?xml-stylesheet type="text/css" href="CSSTour.css"?> <TABLE> ... </TABLE>

Просматриваем документ в браузере - все дочерние элементы приняли свойства корневого (рис. 10.11):


Рис. 10.11.  Просмотр XMLTour.xml с таблицей стилей

Для указания свойств дочерних элементов необходимо указать их описание в таблице стилей. Сделаем копии файлов XMLTour.xml и CSSTour.css и назовем их XMLTour2.xml и CSSTour2.css соответственно. В таблице стилей зададим описание для всех элементов:

TABLE{display:block; font-family:Arial; color:Red; font-size:medium} TOUR{font-family:Bookman Old Style; color:Orangered; font-size:medium} IDTOUR{font-family:Comic Sans MS; color:Yellow; font-size:medium} NAME{font-family:Courier New; color:Lime; font-size:medium} PRICE{font-family:Times New Roman; color:Mediumblue; font-size:medium} INFORMATION{font-family:Verdana; color:Darkviolet; font-size:medium}


В файле XMLTour2.xml изменим связь:

... <?xml-stylesheet type="text/css" href="CSSTour2.css"?> ...

Теперь каждый элемент имеет свое описание (рис. 10.12):


Рис. 10.12.  Применение стиля к каждому элементу XMLTour2.xml

Определение собственных стилей имеет больший приоритет перед описанием корневого элемента. Поскольку в рассматриваемом случае теги TABLE и TOUR не содержат символьных данных, их определение можно опустить, и тогда эквивалентная таблица стилей примет вид

IDTOUR{font-family:Comic Sans MS; color:Yellow; font-size:medium} NAME{font-family:Courier New; color:Lime; font-size:medium} PRICE{font-family:Times New Roman; color:Mediumblue; font-size:medium} INFORMATION{font-family:Verdana; color:Darkviolet; font-size:medium}

Таблица стилей состоит из одного или нескольких правил (иногда их называют набором правил). Правило содержит информацию по отображению определенного типа элемента в XML-документе. Селектор представляет собой имя типа элемента, к которому относится информация по отображению (рис. 10.13):


Рис. 10.13.  Структура правила таблицы стилей

За селектором следует блок объявлений, который ограничивается фигурными скобками ({}) и содержит одно или несколько объявлений, разделяемых точкой с запятой.

Каждое объявление задает установку определенного свойства, такого как размер шрифта, который будет использован для отображения элемента. Объявление состоит из свойства, вслед за которым идет двоеточие, и затем следует значение для данного свойства (рис. 10.14):


Рис. 10.14.  Структура объявления

Таблица каскадных стилей позволяет управлять способами форматирования содержимого элементов в XML-документах, но она не дает возможности изменять или реорганизовывать само содержимое. Она также не позволяет осуществлять доступ к компонентам XML и не дает возможности обрабатывать информацию, которую эти компоненты содержат.

В программном обеспечении к курсу вы найдете папку "CSSXML" с рассмотренными документами (Code\Glava5\ CSSXML).


SGML, HTML и XML


Обобщенный структурированный язык разметки (Structured Generalized Language - SGML) представляет собой основу всех языков разметки. SGML не только определяет базовый синтаксис, но дает вам возможность создавать собственные элементы. Для создания описания документа на SGML нужно продумать соответствующий набор элементов и структуру документа.

Набор наиболее употребительных элементов, используемых для описания документа определенного типа, называется SGML-приложением. Если встроить в программу обработку этих элементов и последующую их интерпретацию, получится браузер. Язык HTML и представляет SGML-пpилoжeниe, разработанное в 1991 г. для описания web-страниц, для которого были созданы различные обозреватели - Internet Explorer, Netscape Navigator, Opera.

Разработчики из консорциума World Wide Web Consortium (W3C) сочли язык SGML слишком сложным и фундаментальным для представления информации в Интернете. Гибкость и большое обилие средств, поддерживаемых SGML, затрудняет написание программного обеспечения, необходимого для обработки и отображения SGML-информации в web-браузерах. Язык HTML, использующийся вплоть до настоящего времени, справляется со своей основной задачей, однако он не реализует в полной мере мощных функциональных возможностей SGML. В 1996 г. группа XML Working Group разработала ветвь языка SGML, назвав его расширяемым языком разметки - Extensible Markup Language.

XML является упрощенной версией SGML, приспособленной для Web. При разработке XML-документа вместо использования ограниченного набора определенных элементов можно создавать свои собственные элементы и присваивать им любые имена - именно поэтому язык XML называется расширяемым (extensible). Следовательно, XML пригоден для описания практически любого документа, от математических уравнений до базы данных. Синтаксис XML более простой, чем SGML, что облегчает восприятие XML-документов, а также написание программ браузеров, кодов и Web-страниц для доступа и представления информации документа.



Создание действительных (valid) XML-документов. Определение типа документа (DTD)


Любой XML-документ должен отвечать минимальным требованиям по составлению, т.е. быть корректно сформированным. Если документ содержит ошибки, он не может считаться XML-документом.

Корректно сформированный XML-документ также может быть действительным (valid). Действительным называется корректно сформированный (well-formed) документ, отвечающий двум дополнительным требованиям2):

Пролог документа должен содержать специальное объявление типа документа, которое включает определение типа документа (Data Type Definition, DTD), задающее структуру документа.Корневой элемент должен отвечать структуре, заданной в DTD.

Использование критерия действительности необходимо для включения XML-документа в группу схожих документов, отвечающих определенной структуре или набору стандартов. Достаточно один раз выработать схему DTD и затем просто подключать ее к создаваемым документам. Стандарт XML определяет DTD как "грамматику для определенного класса документов".

Объявление типа документа представляет собой блок XML-разметки, который располагается в любом месте пролога корректно сформированного документа. Оно имеет следующую обобщенную форму записи:

<!DOCTYPE Название_корневого_элемента DTD>

DTD представляет собой определение типа документа. Оно состоит из символа левой квадратной скобки ([), после которой следует ряд объявлений разметки, заканчивающихся правой квадратной скобкой (]). Объявления разметки описывают логическую структуру документа, т.е. задают элементы документа, атрибуты и другие компоненты. Действительный XML-документ, содержащий DTD с единственным объявлением разметки, которое определяет один тип элемента в документе, TABLE, выглядит так (рис. 10.15).


Рис. 10.15.  Простейший документ с DTD

В этом примере DTD документа указывает, что он может содержать только элементы типа TABLE (это единственный заданный тип элемента) и что элемент TABLE может иметь любое допустимое для данного типа содержимое (ключевое слово ANY).

DTD может содержать различные типы объявлений разметки: объявления типов документов, объявления типов атрибутов, инструкции по обработке и другие.
Объявление типа элемента имеет следующую обобщенную форму:

<!ELEMENT Название_элемента описание_содержимого>

Некоторые возможные описания содержимого приведены в таблице 10.5.

Таблица 10.5. Объявления содержимого документа№ШаблонОписаниеПримерПравильноНеправильноПримерПравильноНеправильноПримерПравильноНеправильноПримерПравильноНеправильно
1<!ELEMENT Название_элемента (#PCDATA)>Элемент TABLE может содержать только символьные данные, дочерние элементы не допускаются
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE (#PCDATA)>] > <TABLE>Простейший документ</TABLE><?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE (#PCDATA)>] > <TABLE> <TOUR></TOUR> Простейший документ </TABLE>
2<!ELEMENT Название_элемента ANY>Элемент TABLE может содержать любые данные. Объявлять тип элемента в документе можно только один раз.
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE ANY>] > <TABLE> <TOUR></TOUR> Простейший документ </TABLE><?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE ANY>] > [<!ELEMENT TABLE (#PCDATA)>] > <TABLE> <TOUR></TOUR> Простейший документ </TABLE>
3<!ELEMENT Название_элемента (Название_дочернего_элемента)+>Элемент TABLE может содержать один или несколько элементов TOUR
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE (TOUR)+>] > <TABLE> <TOUR>Кипр</TOUR> <TOUR>Греция</TOUR> <TOUR>Таиланд</TOUR> <TOUR>Италия</TOUR> </TABLE>
4<!ELEMENT Название_элемента EMPTY>Элемент TABLE должен быть пустым, т.е. не иметь содержимого
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE EMPTY>] > <TABLE> </TABLE><?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TABLE [<!ELEMENT TABLE EMPTY>] > <TABLE> Простейший документ </TABLE>


При создании документа XMLTour. xml мы определили элемент TOUR, который содержит дочерние элементы в следующей последовательности: IDTOUR, NAME, PRICE и INFORMATION. Далее, при заполнении списка туров для нас важно осуществлять проверку расположения элементов - дочерние элементы должны располагаться только так. В таблице 10.6 рассмотрены некоторые типичные примеры определения расположения дочерних элементов.

Таблица 10.6. Примеры описания структуры элемента№ШаблонОписаниеПримерПравильноНеправильноПримерПравильноНеправильно
1

<!ELEMENT Название_элемента (Название_первого_дочернего_элемента, название_второго_дочернего_элемента, название_третьего_дочернего_элемента,:, название_n-ного_дочернего_элемента)>
Последовательная форма модели содержимого указывает, что элемент должен иметь заданную последовательность дочерних элементов. Имена типов дочерних элементов отделяются запятыми. Пропуск дочернего элемента или использование одного и того же типа дочернего элемента более одного раза также недопустимо


<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TOUR [ <!ELEMENT TOUR (IDTOUR, NAME, PRICE, INFORMATION)> <!ELEMENT IDTOUR (#PCDATA)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT PRICE (#PCDATA)> <!ELEMENT INFORMATION (#PCDATA)> ] > <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR>


<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TOUR [ <!ELEMENT TOUR (IDTOUR, NAME, PRICE, INFORMATION)> <!ELEMENT IDTOUR (#PCDATA)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT PRICE (#PCDATA)> <!ELEMENT INFORMATION (#PCDATA)> ] > <TOUR> <NAME>Кипр</NAME> <IDTOUR>1</IDTOUR> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR>
2<!ELEMENT Название_элемента (Название_первого_дочернего_элемента | название_второго_дочернего_элемента | название_третьего_дочернего_элемента |:| название_n-ного_дочернего_элемента)>Выборочная форма модели содержимого указывает, что элемент может иметь один любой из серии допустимых дочерних элементов, разделяемых символом "|"


<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TOUR [ <!ELEMENT TOUR (IDTOUR | NAME | PRICE | INFORMATION)> <!ELEMENT IDTOUR (#PCDATA)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT PRICE (#PCDATA)> <!ELEMENT INFORMATION (#PCDATA)> ] >

Правильные варианты:

<TOUR> <IDTOUR>1</IDTOUR> </TOUR>

<TOUR> <NAME>Кипр</NAME> </TOUR>

<TOUR> <PRICE>25 000,00р. </PRICE> </TOUR>

<TOUR> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR>


<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE TOUR [ <!ELEMENT TOUR (IDTOUR | NAME | PRICE | INFORMATION)> <!ELEMENT IDTOUR (#PCDATA)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT PRICE (#PCDATA)> <!ELEMENT INFORMATION (#PCDATA)> ] >

Неправильные варианты:

<TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME>

</TOUR> <TOUR> <NAME>Кипр</NAME> <IDTOUR>1</IDTOUR> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR>
<


Рассмотренные модели содержимого можно дополнять следующими знаками:

+ - один или несколько элементов;

* - ни одного или несколько элементов;

? - ни одного или один элемент.

Следующее объявление означает, что документ может содержать один или несколько элементов NAME и что элемент INFORMATION может не существовать или быть в единственном экземпляре:

... <!ELEMENT TOUR (IDTOUR, NAME+, PRICE, INFORMATION?)> ... <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <NAME>Греция(включая Кипр)</NAME> <PRICE>25 000,00р. </PRICE> </TOUR>

Другое объявление означает, что документ может содержать ни одного или несколько элементов IDTOUR, либо элемент NAME, либо элемент PRICE, либо ни одного или один элемент INFORMATION:

... <!ELEMENT TOUR (IDTOUR* | NAME| PRICE | INFORMATION?)> ...

Соответствующие правильные три варианта выглядят следующим образом:

<TOUR> <IDTOUR>1</IDTOUR> <IDTOUR>2</IDTOUR> </TOUR>

<TOUR> <NAME>Кипр</NAME> </TOUR>

<TOUR> </TOUR>

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

<!DOCTYPE TOUR [ <!ELEMENT TOUR (IDTOUR, NAME, (PRICE_EURO | PRICE_DOLLAR | PRICE_RUB) )> <!ELEMENT IDTOUR (#PCDATA)> <!ELEMENT NAME (#PCDATA)> <!ELEMENT PRICE_EURO (#PCDATA)> <!ELEMENT PRICE_DOLLAR (#PCDATA)> <!ELEMENT PRICE_RUB (#PCDATA)> ] >

Для него будут верными следующие варианты корневого элемента:



<TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE_RUB>25 000,00р. </PRICE_RUB> </TOUR>

<TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE_DOLLAR>1000$. </PRICE_DOLLAR> </TOUR>

<TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE_EURO>760 _. </PRICE_EURO> </TOUR>


Создание XML-документа


Приступим к созданию XML-документа. В качестве структуры, которую мы будем описывать, возьмем таблицу "Туры базы данных" - BDTur_firm.mdb, - с которой мы начали работать еще в первой лекции. Для создания документа запускаем Microsoft Visual Studio .NET, выбираем "File \ New \ File" (или используем сочетание клавиш Ctrl+N) и в списке шаблонов выбираем XML File (рис. 10.3):


Рис. 10.3.  Создание XML-документа

Конечно, XML-документ, так же как и HTML-документ, можно формировать и в блокноте, но подсветка синтаксиса значительно ускорит работу. Сохраняем появившейся документ, называя его XMLTour.xml. В новом документе есть всего одна строчка:

<?xml version="1.0" encoding="utf-8" ?>

В первой строке происходит объявление XML, указывающее на то, что это XML-документ, и содержащее номер версии. Объявление XML не является обязательным, хотя спецификация требует его включения. Если вы включаете XML-объявление, оно должно находиться в начале документа. Далее указывается кодировка документа - utf-8. Аналогично, если вы создадите HTML-страницу, выбрав соответствующий шаблон (см. рис. 10.3), в шаблоне будет указываться дополнительная информация, такая как версия языка HTML 4.0, язык EN, пакет верстки - Microsoft Visual Studio .NET 7.1 и другие:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title></title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </head> <body>

</body> </html>

Возвращаемся к нашему XML-документу и вводим комментарий - он начинается с символа "!--" и заканчивается символом "--":

<!-- Название файла XMLTour.xml -->

Определяем так называемый элемент "Документ", или корневой элемент. Поскольку у нас таблиц несколько, логично будет назвать его TABLE:


<TABLE></TABLE>

При завершении написания начального тега среда автоматически вставляет конечный тег. Внутри корневого элемента определяем вложенные элементы:

<TABLE> <TOUR> <IDTOUR></IDTOUR> <NAME></NAME> <PRICE></PRICE> <INFORMATION></INFORMATION> </TOUR> </TABLE>

Все! По сути, мы создали таблицу - переключаемся на вкладку
(Data) и вносим данные (рис. 10.4):


увеличить изображение
Рис. 10.4.  Готовая таблица файла XMLTour.xml

Переключаемся в режим XML, для чего нажимаем соответствующую кнопку
и видим сформированный документ:

<?xml version="1.0" encoding="utf-8" ?> <!-- Название файла XMLTour.xml --> <TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMATION>В августе и сентябре действуют специальные скидки</INFORMATION> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMATION>Не включая стоимость авиабилета</INFORMATION> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMATION>Завтрак в отеле включен в стоимость путевки</INFORMATION> </TOUR> <TOUR> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMATION> </TOUR> </TABLE>

Среда Visual Studio .NET облегчает работу по созданию XML-документов, подобно тому, как WYSIWYG1) редакторы HTML-кода помогают создавать web-страницы.Сохраним страницу и откроем ее в браузере - можно изменять степень детализации представления документа, щелкая на знаки плюс "+" и минус "-" (рис. 10.5):


увеличить изображение
Рис. 10.5.  Просмотр файла XMLTour.xml в Internet Explorer

В программном обеспечении к курсу вы найдете документ XMLTour.xml (Code\Glava5\ XMLTour.xml).


Структура XML-документа


Созданный нами документ состоит из двух основных частей - пролога и корневого элемента (элемента "Документ") (рис. 10.6).


увеличить изображение
Рис. 10.6.  Структура документа XML

Пролог может иметь необязательные компоненты, такие как тип и структура документа, инструкции по обработке приложения. Далее мы будем встречать эти компоненты. Корневой элемент представляет собой основу всего элемента и состоит из начального тега, конечного тега и содержимого. В качестве содержимого могут быть данные (текст), другие теги (вложенные элементы) или их сочетание. В рассматриваемом документе корневой элемент - TABLE. Его начальный тег - <TABLE>, конечный - </TABLE>, а содержимое - пять вложенных элементов <TOUR>. Каждый элемент Tour в свою очередь состоит из группы вложенных элементов (рис. 10.7).


Рис. 10.7.  Структура элемента

Каждый тег, входящий в элемент TOUR, представляет собой простейшую структуру (рис. 10.8).


Рис. 10.8.  Структура тега

Структура в точности такая же, как и у элементов HTML, только названия элементов определены, а в тегах XML тип задается пользователем.



XSL и XSLT


Расширяемый язык таблиц стилей (Extensible Stylesheet Language) состоит из двух частей - языка форматирования и языка преобразований (трансформирования). Язык форматирования описывает таблицы стилей XSL, которые подобно таблицам каскадных стилей (CSS), отвечают за отображение в браузере документов XML. Язык преобразований (Extensible Stylesheet Language Transformations) отвечает за средства контроля над выводимыми данными, такими как сортировка, фильтрация, для этого он использует структуру (например, DTD) XML-документов. Таким образом, можно считать обе части одним единым языком - XSL. В литературе и Интернете на сегодняшний день наблюдается различие названий - под XSLT понимается язык XSL, под XSL понимается XSLT и в дополнении ко всем прочим названиям форматов XML это вносит изрядную путаницу. Далее мы будем называть рассматриваемый язык стилей XSL, подразумевая обе эти части.

Таблица XSL может содержать один или несколько шаблонов, описывающих документ XML. В таблице 10.7 приводится документ XMLTour.xml, соответствующая таблица стилей, содержащая один шаблон, и вид страницы в браузере.

Таблица 10.7. XSL-таблица с одним шаблоном

XML - документ, XMLTour.xmlXSL- таблица, XSLTour.xsl

<?xml version="1.0" encoding="utf-8" ?>

<?xml-stylesheet type="text/xsl" href="XSLTour.xsl"?>

<!-- Название файла XMLTour.xml -->

<TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р.</PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR>

<?xml version="1.0" encoding="UTF-8" ?>

<xsl:stylesheet xmlns:xsl= "http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/">

<H3>Таблица "Туры"</H3>

<SPAN STYLE="font-style:italic"> Код тура: </SPAN> <xsl:value-of select= "TOUR/IDTOUR"/><BR/> <SPAN STYLE= "font-style:italic">Название:</SPAN> <xsl:value-of select= "TOUR/NAME"/> <BR/> <SPAN STYLE= "font-style:italic"> Цена:</SPAN> <xsl:value-of select= "TOUR/PRICE"/><BR/> <SPAN STYLE= "font-style:italic">Информация:</SPAN> <xsl:value-of select= "TOUR/INFORMATION"/><BR/>

</xsl:template> </xsl:stylesheet>

Вид в браузере Internet Explorer
<
Для связывания XML-документа с таблицей XSL в прологе указывается ее адрес:

<?xml-stylesheet type="text/xsl" href="XSLTour.xsl"?>

Наиболее часто используется относительный URL - обычно таблица стилей располагается в одной папке с документами. При необходимости можно также указать полный адрес.

Таблица стилей представляет собой обычный текстовый файл с расширением *.xsl. После создания файла для подсветки синтаксиса его можно открыть с помощью Visual Studio .NET (среда содержит шаблон XSLT-документа, шаблонов XSL в ней нет). Первая строка XSL таблицы указывает на то, что это XML-документ:

<?xml version="1.0" encoding="UTF-8" ?>

И действительно, открывая файл файл XSLTour.xsl, в браузере устанавливаем, что это корректно сформированный документ с корневым элементом xsl:stylesheet (рис. 10.16):


увеличить изображение
Рис. 10.16.  Таблица XSL - корректно сформированный XML-документ

Этот элемент идентифицирует документ как таблицу стилей, он является одним из XSL-элементов специального назначения. Все XSL-элементы принадлежат пространству имен XSL - т.е. необходимо перед названием каждого XSL-элемента писать префикс xsl:, обозначающий пространство имен. Дополнительно указывается пространство имен для спецификации таблицы:

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"

Детали этого объявления не суть важны, главное, что нужно запомнить, - на сегодняшний день эта самая свежая версия спецификации. (Ранее применялась более старая спецификация http://www.w3.org/ TR/WD-xsl).

Корневой элемент xsl:stylesheet XSL-таблицы стилей должен содержать один или несколько шаблонов элементов. Корневой элемент из рассмотренного примера содержит только один шаблон, имеющий следующую структуру:

<xsl:template match="/"> <!--дочерние элементы... --> </xsl:template>

Браузер использует шаблон для отображения определенной ветви элементов в иерархии XML-документа, с которым связана таблица стилей. Атрибут match шаблона указывает на определенную ветвь. (Атрибут match представляет собой аналог селектора в правиле CSS).


Значение атрибута match носит название образца (pattern). Образец в данном примере ("/") представляет корневой элемент всего XML-документа. Этот шаблон, таким образом, включает в себя инструкции для отображения всего XML-документа. Шаблон содержит два вида элементов - HTML и XSL. К HTML-элементам относится заголовок <H3>Таблица "Туры"</H3> и описание названия записи <SPAN STYLE="font-style:italic">Код тура: </SPAN>. Это обычные HTML-элементы, единственное, что нужно делать при работе с ними - добавлять закрывающийся тег для всех элементов. Элементы XSL и отвечают за вывод содержимого XML-документа на web-страницу, - например, здесь выводится содержимое дочернего элемента IDTOUR, который принадлежит родительскому элементу TOUR:

<xsl:value-of select="TOUR/IDTOUR"/>

Порядок выводимых элементов определяется именно расположением XSL-элементов, например, если поменять строки, описывающие IDTOUR и NAME, то соответствующим образом изменится web-страница (рис. 10.17 ):

... <SPAN STYLE="font-style:italic">Название: </SPAN> <xsl:value-of select="TOUR/NAME"/> <BR/> <SPAN STYLE="font-style:italic">Код тура: </SPAN> <xsl:value-of select="TOUR/IDTOUR"/><BR/> <SPAN STYLE="font-style:italic"> Цена: </SPAN> <xsl:value-of select="TOUR/PRICE"/><BR/> <SPAN STYLE="font-style:italic">Информация: </SPAN> <xsl:value-of select="TOUR/INFORMATION"/><BR/> ...


Рис. 10.17.  Теперь сначала выводится название тура, а затем код

В программном обеспечении к курсу вы найдете файлы XMLTour.xml и XSLTour.xsl в папке SimpleXSL (Code\Glava5\XSL\SimpleXSL)

Рассмотрим теперь вывод переменного числа дочерних элементов, входящих в корневой элемент XML-документа. В таблице 10.8 приводится описание этой структуры.

Таблица 10.8. XSL-таблица с одним шаблоном и несколькими элементами. Фрагменты XSL-таблицы, одинаковые с таблицей XSLTour.xsl, выделены фоновым цветом.XML-документ, XMLTour2.xmlXSL-таблица, XSLTour2.xsl
<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <?xml-stylesheet type="text/xsl" href="XSLTour2.xsl"?>

<TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMATION>В августе и сентябре действуют специальные скидки</INFORMATION> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMATION>Не включая стоимость авиабилета</INFORMATION> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMATION>Завтрак в отеле включен в стоимость путевки</INFORMATION> </TOUR> <TOUR> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMATION> </TOUR> </TABLE>

<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet xmlns:xsl= "http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/"> <H3>Таблица "Туры"</H3> <xsl:for-each select="TABLE/TOUR"> <BR/>

<SPAN STYLE= "font-style:italic" >Код тура: </SPAN> <xsl:value-of select= "IDTOUR"/><BR/> <SPAN STYLE= "font-style:italic">Название:</SPAN> <xsl:value-of select = "NAME"/> <BR/> <SPAN STYLE= "font-style:italic"> Цена: </SPAN> <xsl:value-of select= "PRICE"/><BR/> <SPAN STYLE="font-style:italic" >Информация:</SPAN> <xsl:value-of select= "INFORMATION"/><BR/> </xsl:for-each> </xsl:template> </xsl:stylesheet>
Вид в браузере Internet Explorer
<


Для цикличного вывода содержимого дочернего элемента TOUR, принадлежащего элементу TABLE, используется элемент for-each:

<xsl:for-each select="TABLE/TOUR">

Далее, внутри for-each задается описание текущего элемента:

<xsl:for-each select="TABLE/TOUR"> <BR/> <SPAN STYLE="font-style:italic">Код тура: </SPAN> <xsl:value-of select="IDTOUR"/><BR/> ... </xsl:for-each>

В результате выводятся данные из всех элементов TOUR, найденных в документе, независимо от того, сколько этих элементов содержит документ.

В программном обеспечении к курсу вы найдете файлы XMLTour2.xml и XSLTour2.xsl в папке SomeElement (Code\Glava5\XSL\SomeElement).

Другой способ отображения повторяющихся XML-элементов состоит в создании отдельного шаблона для каждого элемента с последующим вызовом этого шаблона. Пример использования подобной методики приведен в таблице 10.9.

Таблица 10.9. XSL-таблица с двумя шаблонами и несколькими элементами. Фрагменты XSL-таблицы, одинаковые с таблицей XSLTour2.xsl, выделены фоновым цветом.XML-документ, XMLTour3.xmlXSL-таблица, XSLTour3.xsl


<?xml version="1.0" encoding="utf-8" ?>

<!-- Название файла XMLTour.xml --> <?xml-stylesheet type= "text/xsl" href="XSLTour3.xsl"?>

<TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMATION>В августе и сентябре действуют специальные скидки</INFORMATION> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMATION>Не включая стоимость авиабилета</INFORMATION> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMATION>Завтрак в отеле включен в стоимость путевки</INFORMATION> </TOUR> <TOUR> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMATION> </TOUR> </TABLE>



<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet xmlns:xsl= "http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:template match="/"> <H3>Таблица "Туры"</H3> <xsl:apply-templates select="TABLE/TOUR"/>

</xsl:template> <xsl:template match="TOUR">

<P> <SPAN STYLE= "font-style:italic">Код тура: </SPAN> <xsl:value-of select= "IDTOUR"/><BR/> <SPAN STYLE= "font-style:italic">Название:</SPAN> <xsl:value-of select = "NAME"/><BR/> <SPAN STYLE= "font-style:italic"> Цена: </SPAN> <xsl:value-of select= "PRICE"/><BR/> <SPAN STYLE= "font-style:italic">Информация:</SPAN> <xsl:value-of select= "INFORMATION"/><BR/> </P>

</xsl:template> </xsl:stylesheet>
<


Таблица стилей XSLTour3.xsl включает в себя два шаблона. Первый шаблон содержит инструкции для отображения коревого элемента - на это указывает атрибут match="/". Этот шаблон мы уже встречали ранее. Второй шаблон содержит инструкции для отображения элемента TOUR (атрибут match="TOUR"). Сначала браузер обрабатывает шаблон, соответствующий корневой части элемента:

<xsl:template match="/"> <H3>Таблица "Туры"</H3> <xsl:apply-templates select="TABLE/TOUR" />

</xsl:template>

Элемент apply-templates сообщает браузеру, что для каждого элемента TOUR внутри корневого элемента TABLE он должен обрабатывать шаблон, отвечающий элементу TOUR, - т. е. шаблон, для атрибута match которого установлено значение "TOUR". Далее описывается таблица стилей для этого элемента:

<xsl:template match="TOUR">

<P> <SPAN STYLE="font-style:italic">Код тура: </SPAN> <xsl:value-of select="IDTOUR"/><BR/> <SPAN STYLE="font-style:italic">Название: </SPAN> <xsl:value-of select = "NAME"/> <BR/> <SPAN STYLE="font-style:italic"> Цена: </SPAN> <xsl:value-of select="PRICE"/><BR/> <SPAN STYLE="font-style:italic">Информация: </SPAN> <xsl:value-of select="INFORMATION"/><BR/> </P>

</xsl:template>

Доступ к дочерним элементам TOUR осуществляется посредством образца, содержащего только имя элемента:

<xsl:value-of select="IDTOUR"/>

В программном обеспечении к курсу вы найдете файлы XMLTour3.xml и XSLTour3.xsl в папке SomeTemplate (Code\Glava5\XSL\ SomeTemplate).

Для создания таблиц XSL в среде Visual Studio .NET предусмотрен общий шаблон XSLT-документов. При запуске переходим в пункт главного меню "File \ New \ File" (см. рис. 10.3) и в появившемся списке шаблонов выбираем XSLT File. Сгенерированный текст содержит основные определения, для создания файла XSL следует лишь указать пространство имен XSL (таблица 10.10):

Таблица 10.10. Шаблоны XSLT и XSLXSLT-шаблон Visual Studio.NETXSL-таблица стилей
<?xml version="1.0" encoding="UTF-8" ?> <stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">

</stylesheet>



<?xml version="1.0" encoding="UTF-8" ?> <xsl:stylesheet xmlns:xsl= "http://www.w3.org/1999/XSL/Transform" version="1.0">

</xsl:stylesheet>

Получение информации о структуре объекта DataSet


После загрузки схемы в объект DataSet можно получить информацию о его структуре - таблицах, столбцах, отношениях между таблицами. Для этого применяются свойства, приведенные в таблице 11.7.

Таблица 11.7. Некоторые свойства объекта DataSet

СвойствоОписание
Tables Ссылка на коллекцию DataTable
Tables.Count Количество объектов DataTable (таблиц)
Relations Ссылка на коллекцию DataRelation
Relations.Count Количество объектов DataRelation (отношений)
Relations[индекс].ParentTable Родительская таблица отношения с заданным индексом
Relations[индекс].ChildTable Дочерняя таблица отношения с заданным индексом
Tables[индекс].TableName Название таблицы с заданным индексом в коллекции DataTable
Tables[индекс].Columns Ссылка на коллекцию DataColumn (столбцов)
Tables[индекс].Columns.Count Количество объектов DataColumn (столбцов)
Tables[индекс].Columns[индекс].ColumnName Название столбца с заданным индексом в коллекции DataColumn
Tables[индекс].Columns[индекс].DataType Тип данных столбца с заданным индексом в коллекции DataColumn

Скопируйте папку приложения TypedDataSet и назовите ее "Structure TypedDataSet". Свойству Dock элемента DataGrid, расположенного на форме, устанавливаем значение "Left". Добавляем элемент Splitter. Перетаскиваем элемент RichTextBox, свойству Dock устанавливаем значение "Fill", удаляем название в поле свойства Text. В конструкторе формы после создания всех объектов добавляем следующий код:

public Form1() { ... richTextBox1.Text+= ("Структура объекта DataSet." + dsTour.DataSetName); //Вывод количества таблиц и отношений richTextBox1.Text+= ("\nКоличество таблиц: "+ dsTour.Tables.Count.ToString()); richTextBox1.Text+= ("\nКоличество отношений: "+ dsTour.Relations.Count.ToString());

for(int i=0;i<dsTour.Tables.Count;i++) { //Вывод названий таблиц richTextBox1.Text+=( "\n"+"\n Таблица: "+ dsTour.Tables[i].TableName.ToString()); richTextBox1.Text+=(" из "+dsTour.Tables[i].
Columns.Count.ToString() + " столбцов"+"\n"); for(int j=0;j<dsTour.Tables[i].Columns.Count;j++) { //Вывод названий столбцов и их типов данных richTextBox1.Text+=("\nСтолбец: "+ dsTour.Tables[i].Columns[j].ColumnName.ToString()+ " тип данных: "+ dsTour.Tables[i].Columns[j].DataType); } } //Вывод названий связанных таблиц и отношений for(int k = 0; k<dsTour.Relations.Count; k++) { richTextBox1.Text+= ("\n"+"\nРодительская таблица: " + dsTour.Relations[k].ParentTable+ " --- отношение: " + dsTour.Relations[k].ParentKeyConstraint + " --- дочерняя таблица: " + dsTour.Relations[k].ChildTable); } }

Запускаем приложение. В текстовое поле выводится информация о таблицах, полях, отношениях (рис. 11.24):


Рис. 11.24.  Структура объекта DataSet

В программном обеспечении к курсу вы найдете приложение Structure TypedDataSet (Code\Glava5\ StructureTypedDataSet).


Создание типизированного объекта DataSet


Разобравшись наконец со всеми понятиями, приступим к их применению. Как мы знаем, объект DataSet представляет собой кэш, буфер для хранения данных, получаемых из базы. Он состоит из объектов DataTable (таблиц), DataColumn (столбцов) и DataRow (записей). Все столбцы в самой базе данных, разумеется, типизированы - то есть для каждого из них определен тип данных, например, int для ключевого поля, string для примечаний, date/time для даты или времени. При программном создании основных объектов ADO .NET для вывода столбцов из базы данных в объект DataSet, а затем в элемент DataGrid, информация о типах данных, не говоря уже об отношениях между таблицами, теряется. Конечно, можно программно же детализировать структуру объекта DataSet - создать таблицы, поля, определить тип данных полей, создать отношения (именно этим мы и занимались в восьмой лекции), но, наверное, это не самый быстрый и удобный способ определения структуры DataSet. Разумеется, всякая структура лучше простого вывода данных, однако технология ADO .NET предлагает универсальный способ создания типизированных объектов - использование XSD-схем.

С типизированными объектами DataSet мы имели дело с самого начала курса - просто мы не обращали на это внимание. Откройте проект VisualDataMDB из первой лекции, в окне Solution Explorer щелкаем дважды на файле dsCustomer.xsd - появляется элемент со всеми полями таблицы Customer (рис. 11.10):


Рис. 11.10.  XSD-схема dsCustomer.xsd в проекте VisualDataMDB

Вся структура таблицы, включая тип данных и ключевое поле (поле Customer ID, отмечено значком ключа), воспроизведена в этом файле. При запуске приложения среда формирует объект DataSet на основании XSD-схемы.

Приступим теперь к созданию типизированного DataSet. В качестве исходной базы данных воспользуемся файлом BDTur_firm.mdb из первой лекции. Скопируйте его, переименуйте в BDTur_firm_Eng.mdb и откройте в Microsoft Access. Изменим названия таблиц "Туры", "Сезоны", "Путевки", "Оплата" и поля в них так, как это было сделано в таблице 11.6 (см.
также рис. 11.8). В результате у нас получится следующая схема данных (рис. 11.11).


увеличить изображение
Рис. 11.11.  Схема данных в файле BDTur_firm_Eng.mdb

Создайте новое Windows-приложение и назовите его "TypedDataSet". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение Fill. Переходим в код формы, подключаем пространство имен:

using System.Data.OleDb;

В классе формы создаем строки commandText и commandText2 для извлечения содержимого таблиц TOUR и SEASON, а также строку подключения connectionString:

string commandText = "SELECT * FROM TOUR"; string commandText2 = "SELECT* FROM SEASON"; string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" ";Data Source=""D:\Uchebnik\Code\Glava5\BDTur_firm_Eng.mdb" ";User ID=Admin;Jet OLEDB:Encrypt Database=False";

Обратите внимание на адрес каталога базы BDTur_firm_Eng.mdb - приведенное значение верно только лишь для моего компьютера! В конструкторе формы создаем объекты для вывода содержимого двух таблиц в элемент DataGrid:

public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet();

dataAdapter.TableMappings.Add("Table", "TOUR");

OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); dataAdapter2.SelectCommand = myCommand2; dataAdapter2.TableMappings.Add("Table", "SEASON");

conn.Open(); dataAdapter.Fill(ds); dataAdapter2.Fill(ds); conn.Close(); dataGrid1.DataSource = ds;

}

Практически этот же код мы использовали в проекте DataGrid2Table в восьмой лекции. Запускаем приложение.


Обратите внимание, что таблице не связаны - в самом деле, мы ведь не создавали никаких отношений (рис. 11.12).


увеличить изображение
Рис. 11.12.  Приложение TypedDataSet. Вывод двух таблиц TOUR и SEASON. Объект DataSet обычный, нетипизированный, создан программно

В окне Solution Explorer щелкнем правой кнопкой на названии проекта "TypedDataSet" и в появившемся контекстном меню выберем пункт "Add \ Add New Item". В окне шаблонов выделяем объект DataSet, называем его "XSDTour_bd.xsd" и нажимаем кнопку "Open" (рис. 11.13).


Рис. 11.13.  Добавление схемы XSDTour_bd.xsd

В результате мы добавили к проекту не только XSD-схему, но еще и дополнительный файл, который будет необходим для создания объекта DataSet на основе этой схемы. В окне доступен также шаблон XML Schema - именно его мы и использовали для создания схемы к документам XML, он тоже имеет расширение .xsd. Что будет, если мы выберем его? После добавления к проекту в окне Solution Explorer щелкните на кнопку - Show All Files ("Показывать все файлы"). В результате будут отображены все файлы в проекте, в том числе и те, которые обычно скрыты. При выборе объекта XML Schema среда генерирует два файла - XSDTour_bd.xsd и XSDTour_bd.xsx (рис. 11.14, А). В файл XSDTour_bd.xsx записываются данные о расположении элементов в режиме дизайна XSD-схемы - в этом можно убедиться, открыв его:

<?xml version="1.0" encoding="utf-8"?> <!--This file is auto-generated by the XML Schema Designer. It holds layout information for components on the designer surface.--> <!-- Этот файл сгенерирован автоматически в режиме дизайна XML-схемы. Он хранит информацию о расположении компонент --> <XSDDesignerLayout />

При выборе объекта DataSet добавляется также файл XSDTour_bd.cs (рис. 11.14, Б):




А - объект XML - Schema, Б - DataSet " width="578" height="620">

увеличить изображение
Рис. 11.14.  Добавление XSD-схемы. А - объект XML - Schema, Б - DataSet

Как мы знаем, в файлах с расширением *. cs хранится код на языке C#. Это и есть тот файл, в котором будет храниться описание структуры DataSet. В принципе, можно работать и с объектом XSD-Schema в чистом виде, но тогда нам придется создавать самим файл XSDTour_bd.cs. Можете просмотреть его содержимое, сгенерированное автоматически, и убедиться в том, что среда в этом случае действительно облегчает работу.

Итак, "правильный" файл XSDTour_bd.xsd создан. Переходим в окно Server Explorer и создаем подключение к базе данных Microsoft Access BDTur_firm_Eng.mdb. Затем перетаскиваем таблицы TOUR и SEASON на поверхность схемы (рис. 11.15):


Рис. 11.15.  Перетаскивание таблиц. Перевод надписи: "Для начала работы перетащите объекты из окна Server Explorer или Toolbox или щелкните правой кнопкой здесь"

В результате у нас появились два элемента TOUR и SEASON, представляющих структуру соответствующих таблиц. Для создания отношения между таблицами переходим в окно Toolbox и перетаскиваем на элемент TOUR объект Relation (рис. 11.16):


Рис. 11.16.  Перетаскивание объекта Relation

В появившемся окне Edit Relation из выпадающего списка "Parent element" выбираем "TOUR", из выпадающего списка "Child element" выбираем "SEASON" - редактор сам определит верное ключевое поле IDTOUR, и нажимаем "ОК" (рис. 11.17, А). В режиме дизайна2) схемы появляется схематическое изображение связи в виде ромба (рис. 11.17, Б):


увеличить изображение
Рис. 11.17.  Создание отношения между таблицами. А - редактор Edit Relation, Б - схематическое изображение связи в режиме дизайна схемы

Создание XSD-схемы завершено. Переходим на вкладку Data окна Toolbox и перетаскиваем на форму элемент DataSet. В окне Add DataSet оставляем предложенный вариант "Typed dataset" (рис. 11.18).




Рис. 11.18.  Добавление объекта DataSet

На панели компонент формы появляется объект xsdTour_bd1. Переходим в окно Properties и в свойстве Name вводим "dsTour". Здесь мы видим разницу3) между свойством Name - названием, используемым в коде, и свойством DataSetName - названием объекта DataSet, которое образовано от схемы XSDTour_bd.xsd (рис. 11.19).


Рис. 11.19.  Окно Properties объекта DataSet

Переходим в код формы. Закомментируем создание экземпляра ds и заменим его на типизированный dsTour:

... //DataSet ds = new DataSet(); ... //dataAdapter.Fill(ds); //dataAdapter2.Fill(ds); dataAdapter.Fill(dsTour); dataAdapter2.Fill(dsTour); conn.Close(); //dataGrid1.DataSource = ds; dataGrid1.DataSource = dsTour; ...

Запускаем приложение. От записи в таблице TOUR можно перейти к дочерней таблице SEASON по отношению TOURSEASON (рис. 11.20):


увеличить изображение
Рис. 11.20.  Приложение TypedDataSet. Вывод двух таблиц TOUR и SEASON. Типизированный объект DataSet (ср. рис. 11.12)

Мы уже получали эту же функциональность в проекте DataGrid2Table, однако на этот раз мы обошлись без определения каких-либо отношений в коде. Название связи, выводимое в элемент DataGrid, - "TOURSEASON" трудно назвать удачным: его сгенерировала студия, для пользователей следует сделать его более понятным. Чтобы его изменить, следует перейти к схеме XSDTour_bd.xsd (в режим дизайна), щелкнуть правой кнопкой на символе связи - ромбике - и выбрать пункт "Edit Relation". Появится уже знакомый редактор (cм. рис.11.17, А), в котором можно будет изменить название. Впрочем, для нашего учебного проекта оставим его как есть.

Добавим в схему DataSet таблицы PASS и PAYMENT. Для этого снова откроем окно Server Explorer и перетащим их на схему XSDTour_bd.xsd. Из окна Toolbox добавим объект Relation на элемент SEASON - создадим связь с элементом PASS по полю IDSEASON. Аналогично, добавим объект Relation на элемент PASS для создания связи с элементом PAYMENT по полю IDPASS. Готовая схема будет иметь следующий вид (рис. 11.21):




увеличить изображение
Рис. 11.21.  Готовая схема XSDTour_bd.xsd

Перейдем в режим кода формы, закомментируем строку command Text2 и объекты, нужные для извлечения таблицы SEASON:

... //string commandText2 = "SELECT* FROM SEASON"; ... //OleDbCommand myCommand2 = new OleDbCommand(); //myCommand2.Connection = conn; //myCommand2.CommandText = commandText2; //OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); //dataAdapter2.SelectCommand = myCommand2; //dataAdapter2.TableMappings.Add("Table", "SEASON"); ... //dataAdapter2.Fill(dsTour);

Запускаем приложение: это кажется невероятным - мы оставили извлечение из базы данных только одной таблицы, а на форме отображается четыре связанных таблицы (рис. 11.22)!!


увеличить изображение
Рис. 11.22.  Приложение TypedDataSet. Вывод схемы четырех таблиц

В действительности ничего удивительного нет - DataSet содержит структуру четырех таблиц, которую элемент DataGrid и отобразил на форме. Подключение к базе извлекает данные только для одной таблицы - TOUR, поэтому мы видим ее заполненной. Остальные таблицы - пустые, записей в них нет.

Мы очень быстро и легко воссоздали структуру фрагмента базы данных. Создание объектов DataTable, DataColumn, определение отношений - программно или с помощью визуальных средств студии - заняло бы гораздо больше времени.

Заполним DataSet данными из четырех таблиц. Добавим дополнительные строки commandText для извлечения содержимого остальных таблиц, в результате получится следующее:

string commandText = "SELECT * FROM TOUR"; string commandText2 = "SELECT* FROM SEASON"; string commandText3 = "SELECT* FROM PASS"; string commandText4 = "SELECT* FROM PAYMENT";

Также создадим дополнительные4) объекты DataAdapter, конструктор формы примет следующий вид:

public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(connectionString); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand;



dataAdapter.TableMappings.Add("Table", "TOUR");

OleDbCommand myCommand2 = new OleDbCommand(); myCommand2.Connection = conn; myCommand2.CommandText = commandText2; OleDbDataAdapter dataAdapter2 = new OleDbDataAdapter(); dataAdapter2.SelectCommand = myCommand2; dataAdapter2.TableMappings.Add("Table", "SEASON");

OleDbCommand myCommand3 = new OleDbCommand(); myCommand3.Connection = conn; myCommand3.CommandText = commandText3; OleDbDataAdapter dataAdapter3 = new OleDbDataAdapter(); dataAdapter3.SelectCommand = myCommand3; dataAdapter3.TableMappings.Add("Table", "PASS");

OleDbCommand myCommand4 = new OleDbCommand(); myCommand4.Connection = conn; myCommand4.CommandText = commandText4; OleDbDataAdapter dataAdapter4 = new OleDbDataAdapter(); dataAdapter4.SelectCommand = myCommand4; dataAdapter4.TableMappings.Add("Table", "PAYMENT");

conn.Open(); dataAdapter.Fill(dsTour); dataAdapter2.Fill(dsTour); dataAdapter3.Fill(dsTour); dataAdapter4.Fill(dsTour); conn.Close(); dataGrid1.DataSource = dsTour; }

Запускаем приложение. Все таблицы теперь заполнены данными (рис. 11.23).


Рис. 11.23.  Готовое приложение TypedDataSet

В программном обеспечении к курсу вы найдете файл базы данных BDTur_firm_Eng.mdb и приложение TypedDataSet (Code\Glava5\ BDTur_firm_Eng.mdb и TypedDataSet).


Создание XSD-схемы в среде Visual Studio .NET


Выше мы получили XSD-схему документа XMLTourFull.xml. Итак, методика следующая: создаем XML-документ, затем выбираем "Create Schema" - среда сама генерирует готовую схему. Но как быть, если нам совсем не нужен XML-документ, а нужна XSD-схема сама по себе? Создадим точно такую же схему, как и XMLTourFull.xsd, только на этот раз без всякой привязки к документу. Запускаем студию, нажимаем Ctrl+N и в появившемся окне выбираем XML \ Schema (см. рис. 11.3). Сохраним сразу документ, задав ему название "CreateXSDSchema.xsd". Для добавления элемента можно воспользоваться одним из следующих способов: перетащить из окна Toolbox элемент управления, который так и называется - element (рис. 11.7, А); в главном меню перейти "Schema \ Add \ New element" (рис. 11.7, Б); выбрать пункт контекстного меню "Add \ New element" (рис. 11.7, В ).


увеличить изображение
Рис. 11.7.  Различные способы создания нового элемента

Далее нам предстоит заполнить элементы полями, как это показано в таблице 11.6. В результате получится четыре элемента (рис. 11.8):


увеличить изображение
Рис. 11.8.  Готовые отдельные элементы

Для создания вложенной структуры берем элемент SEASON и перетаскиваем его на элемент TOUR. В элементе TOUR появляется дополнительное поле SEASON (SEASON), отражающее связь, и значок с линией - щелкнув на него, можно собрать структуру (рис. 11.9).


Рис. 11.9.  Создание вложенной структуры

Аналогичным образом добавляем элементы PASS и PAYMENT. Переходим в режим XML и убеждаемся в том, что созданный код практически одинаков с кодом документа XMLTourFull.xsd - в последнем имеются только дополнительные атрибуты, например minOccurs="0". При необходимости их можно добавить вручную.

Мы рассмотрели создание простейшей XSD-схемы. Графическое конструирование более сложных XSD-схем не будет отличаться принципиально - вместо написания тегов, приведенных в таблицах 11.2-11.4, на рабочую область просто переносится и настраивается соответствующий элемент.

В программном обеспечении к курсу вы найдете файл CreateXSD Schema.xsd в папке XSD (Code\Glava5\XSD).



Связи в XSD-схемах


Приступим к рассмотрению связей между элементами и документами, которые могут хранить XSD-схемы. Самое сложное в создании связей - определение логической структуры документа или группы документов, впрочем, мы уже сталкивались с этим при проектировании базы данных. В этой лекции при создании документа XMLTour.xml мы использовали содержимое таблицы "Туры" базы данных BDTur_firm.mdb. В первой лекции мы определяли связи между таблицами этой базы данных, так, одна ветвь имела следующий вид:

"Туры" 1 -

"Сезоны" 1 -
"Путевки" 1 -
"Оплата"

Восстановим это же отношение в XML-документах. Первое, с чего мы начнем, - это создание отдельных документов (таблица 11.5).

Таблица 11.5. XML-документы, соответствующие таблицам "Сезоны", "Путевки" и "Оплата"

Сезоны, файл XMLSeason.xmlПутевки, файл XMLPass.xmlОплата, XMLPayment.xml

<?xml version="1.0" encoding="utf-8" ?> <!-- Название файла XMLSeason.xml --> <TABLE> <SEASON> <IDSEASON>1</IDSEASON> <IDTOUR>1</IDTOUR> <DATE_OF_COMMENCEMENT>01.05.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>25</PLACE> </SEASON> <SEASON> <IDSEASON>2</IDSEASON> <IDTOUR>2</IDTOUR> <DATE_OF_COMMENCEMENT>01.06.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>31.08.2007</DATA_OF_TERMINATION> <SEASON_OFF>1</SEASON_OFF> <PLACE>30</PLACE> </SEASON> <SEASON> <IDSEASON>3</IDSEASON> <IDTOUR>3</IDTOUR> <DATE_OF_COMMENCEMENT>01.04.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>15</PLACE> </SEASON> <SEASON> <IDSEASON>4</IDSEASON> <IDTOUR>4</IDTOUR> <DATE_OF_COMMENCEMENT>01.03.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.09.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>12</PLACE> </SEASON> <SEASON> <IDSEASON>5</IDSEASON> <IDTOUR>5</IDTOUR> <DATE_OF_COMMENCEMENT>01.03.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>12</PLACE> </SEASON> </TABLE>

<?xml version="1.0" encoding="utf-8" ?> <!-- Название файла XMLPass.xml --> <TABLE> <PASS> <IDPASS>1</IDPASS> <IDTOURIST>1</IDTOURIST> <IDSEASON>1</IDSEASON> </PASS> <PASS> <IDPASS>2</IDPASS> <IDTOURIST>2</IDTOURIST> <IDSEASON>2</IDSEASON> </PASS> <PASS> <IDPASS>3</IDPASS> <IDTOURIST>3</IDTOURIST> <IDSEASON>3</IDSEASON> </PASS> <PASS> <IDPASS>4</IDPASS> <IDTOURIST>4</IDTOURIST> <IDSEASON>4</IDSEASON> </PASS> <PASS> <IDPASS>5</IDPASS> <IDTOURIST>5</IDTOURIST> <IDSEASON>5</IDSEASON> </PASS> </TABLE>

<?xml version="1.0" encoding="utf-8" ?> <!-- Название файла XMLPayment.xml --> <TABLE> <PAYMENT> <IDPAYMENT>1</IDPAYMENT> <IDPASS>1</IDPASS> <DATE_OF_PAYMENT>13.04.2007</DATE_OF_PAYMENT> <AMOUNT>25 000,00р.</AMOUNT> </PAYMENT> <PAYMENT> <IDPAYMENT>2</IDPAYMENT> <IDPASS>2</IDPASS> <DATE_OF_PAYMENT>15.05.2006</DATE_OF_PAYMENT> <AMOUNT>32 000,00р.</AMOUNT> </PAYMENT> <PAYMENT> <IDPAYMENT>3</IDPAYMENT> <IDPASS>3</IDPASS> <DATE_OF_PAYMENT>05.03.2007</DATE_OF_PAYMENT> <AMOUNT>30 000,00р.</AMOUNT> </PAYMENT> <PAYMENT> <IDPAYMENT>4</IDPAYMENT> <IDPASS>4</IDPASS> <DATE_OF_PAYMENT>02.02.2007</DATE_OF_PAYMENT> <AMOUNT>26 000,00р.</AMOUNT> </PAYMENT> <PAYMENT> <IDPAYMENT>5</IDPAYMENT> <IDPASS>5</IDPASS> <DATE_OF_PAYMENT>25.02.2007</DATE_OF_PAYMENT> <AMOUNT>27 000,00р.</AMOUNT> </PAYMENT> </TABLE>

<
Ничего сложного в создании этих документов нет - их структура в точности такая же, как и структура документа XMLTour.xml. Названия полей были переведены по смыслу на английский язык и стали названиями тегов. Теперь нам нужно объединить все четыре документа в один документ XMLTourFull.xml следующим образом:

<?xml version="1.0" encoding="utf-8"?> <!-- Название файла XMLTourFull.xml --> <TABLE> <TOUR> ... Дочерние элементы тега TOUR документа XMLTour.xml ... <SEASON> ... Дочерние элементы тега SEASON ... <PASS> ... Дочерние элементы тега PASS ...

<PAYMENT> ... Дочерние элементы тега PAYMENT ... </PAYMENT> </PASS> </SEASON> </TOUR> ... Следующая группа дочерних элементов текущего тега TOUR ...

</TABLE>

Готовый документ будет иметь следующий вид:

<?xml version="1.0" encoding="utf-8"?> <!-- Название файла XMLTourFull.xml --> <TABLE> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)</INFORMATION> <SEASON> <IDSEASON>1</IDSEASON> <IDTOUR>1</IDTOUR> <DATE_OF_COMMENCEMENT>01.05.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>25</PLACE> <PASS> <IDPASS>1</IDPASS> <IDTOURIST>1</IDTOURIST> <IDSEASON>1</IDSEASON> <PAYMENT> <IDPAYMENT>1</IDPAYMENT> <IDPASS>1</IDPASS> <DATE_OF_PAYMENT>13.04.2007</DATE_OF_PAYMENT> <AMOUNT>25 000,00р.</AMOUNT> </PAYMENT> </PASS> </SEASON> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMATION>В августе и сентябре действуют специальные скидки</INFORMATION> <SEASON> <IDSEASON>2</IDSEASON> <IDTOUR>2</IDTOUR> <DATE_OF_COMMENCEMENT>01.06.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>31.08.2007</DATA_OF_TERMINATION> <SEASON_OFF>1</SEASON_OFF> <PLACE>30</PLACE> <PASS> <IDPASS>2</IDPASS> <IDTOURIST>2</IDTOURIST> <IDSEASON>2</IDSEASON> <PAYMENT> <IDPAYMENT>2</IDPAYMENT> <IDPASS>2</IDPASS> <DATE_OF_PAYMENT>15.05.2006</DATE_OF_PAYMENT> <AMOUNT>32 000,00р.</AMOUNT> </PAYMENT> </PASS> </SEASON> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMATION>Не включая стоимость авиабилета</INFORMATION> <SEASON> <IDSEASON>3</IDSEASON> <IDTOUR>3</IDTOUR> <DATE_OF_COMMENCEMENT>01.04.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>15</PLACE> <PASS> <IDPASS>3</IDPASS> <IDTOURIST>3</IDTOURIST> <IDSEASON>3</IDSEASON> <PAYMENT> <IDPAYMENT>3</IDPAYMENT> <IDPASS>3</IDPASS> <DATE_OF_PAYMENT>05.03.2007</DATE_OF_PAYMENT> <AMOUNT>30 000,00р.</AMOUNT> </PAYMENT> </PASS> </SEASON> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMATION>Завтрак в отеле включен в стоимость путевки</INFORMATION> <SEASON> <IDSEASON>4</IDSEASON> <IDTOUR>4</IDTOUR> <DATE_OF_COMMENCEMENT>01.03.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.09.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>12</PLACE> <PASS> <IDPASS>4</IDPASS> <IDTOURIST>4</IDTOURIST> <IDSEASON>4</IDSEASON> <PAYMENT> <IDPAYMENT>4</IDPAYMENT> <IDPASS>4</IDPASS> <DATE_OF_PAYMENT>02.02.2007</DATE_OF_PAYMENT> <AMOUNT>26 000,00р.</AMOUNT> </PAYMENT> </PASS> </SEASON> </TOUR> <TOUR> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMATION> <SEASON> <IDSEASON>5</IDSEASON> <IDTOUR>5</IDTOUR> <DATE_OF_COMMENCEMENT>01.03.2007</DATE_OF_COMMENCEMENT> <DATA_OF_TERMINATION>01.10.2007</DATA_OF_TERMINATION> <SEASON_OFF>0</SEASON_OFF> <PLACE>12</PLACE> <PASS> <IDPASS>5</IDPASS> <IDTOURIST>5</IDTOURIST> <IDSEASON>5</IDSEASON> <PAYMENT> <IDPAYMENT>5</IDPAYMENT> <IDPASS>5</IDPASS> <DATE_OF_PAYMENT>25.02.2007</DATE_OF_PAYMENT> <AMOUNT>27 000,00р.</AMOUNT> </PAYMENT> </PASS> </SEASON> </TOUR> </TABLE>



Переключаемся на вкладку
(Data) и видим, что представление XML-документа сильно изменилось (сравните с рис. 10.4). Теперь таблицы связаны (рис. 11.6).


увеличить изображение
Рис. 11.6.  А - переход к записи таблицы "Сезоны", Б - переход к записи таблицы "Путевки", В - переход к записи таблицы "Оплата", Г - результирующий вид

Создадим схему документа XMLTourFull.xml, выбирая в главном меню пункт "XML\Create Schema". Графическое представление схемы отражает связь между соответствующими таблицами XML-документа (таблица 11.6).

Таблица 11.6. Схема XMLTourFull.xsdВид в режиме
(DataSet) Вид в режиме
(XML)


<?xml version="1.0" ?> <xs:schema id="TABLE" targetNamespace="http://tempuri.org/XMLTourFull.xsd" xmlns:mstns="http://tempuri.org/XMLTourFull.xsd" xmlns="http://tempuri.org/XMLTourFull.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault="qualified" elementFormDefault="qualified"> <xs:element name="TABLE" msdata:IsDataSet="true" msdata:Locale="ru-RU" msdata:EnforceConstraints="False"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="TOUR"> <xs:complexType> <xs:sequence> <xs:element name="IDTOUR" type="xs:string" minOccurs="0" /> <xs:element name="NAME" type="xs:string" minOccurs="0" /> <xs:element name="PRICE" type="xs:string" minOccurs="0" /> <xs:element name="INFORMATION" type="xs:string" minOccurs="0" /> <xs:element name="SEASON" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="IDSEASON" type="xs:string" minOccurs="0" /> <xs:element name="IDTOUR" type="xs:string" minOccurs="0" /> <xs:element name="DATE_OF_COMMENCEMENT" type="xs:string" minOccurs="0" /> <xs:element name="DATA_OF_TERMINATION" type="xs:string" minOccurs="0" /> <xs:element name="SEASON_OFF" type="xs:string" minOccurs="0" /> <xs:element name="PLACE" type="xs:string" minOccurs="0" /> <xs:element name="PASS" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="IDPASS" type="xs:string" minOccurs="0" /> <xs:element name="IDTOURIST" type="xs:string" minOccurs="0" /> <xs:element name="IDSEASON" type="xs:string" minOccurs="0" /> <xs:element name="PAYMENT" minOccurs="0" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="IDPAYMENT" type="xs:string" minOccurs="0" /> <xs:element name="IDPASS" type="xs:string" minOccurs="0" /> <xs:element name="DATE_OF_PAYMENT" type="xs:string" minOccurs="0" /> <xs:element name="AMOUNT" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>
В программном обеспечении к курсу вы найдете файлы XMLTourFull.xml и XMLTourFull.xsd в папке XSD (Code\Glava5\ XSD).


XML-схемы данных (XSD)


Схемы данных1) XSD (XML Schema Document, XSD) представляют собой альтернативный способ правил построения XML-документов. По сравнению с DTD, схемы обладают более мощными средствами для определения сложных структур данных, обеспечивают более понятный способ описания грамматики языка, способны легко модернизироваться и расширяться. Схема XSD может содержать следующую информацию:

представление связей между элементами данных, аналогичное связям внешних ключей между таблицами в реляционной базе данных;представление уникальных идентификаторов, подобных первичному ключу; спецификацию типов данных каждого индивидуального элемента и атрибута в XML-документе.

Для создания схемы данных в Visual Studio .NET в главном меню переходим File \ New \ File и выбираем из списка шаблонов (см. рис. 11.3) XML Schema. Появляется окно с надписью "Чтобы начать, перетащите объекты из окна Server Explorer или Toolbox на рабочую область (область дизайна) или щелкните правой кнопкой" (рис. 11.1):


Рис. 11.1.  Создание новой XSD-схемы

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

(XML):

<?xml version="1.0" encoding="utf-8" ?> <xs:schema targetNamespace="http://tempuri.org/XMLSchema.xsd" elementFormDefault="qualified" xmlns="http://tempuri.org/XMLSchema.xsd" xmlns:mstns="http://tempuri.org/XMLSchema.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema"> </xs:schema>

Первая строка - это уже знакомое указание на то, что схема представляет собой XML-документ с корневым элементом xs:schema. Префикс xs: предваряет все элементы схемы, указывая на свое пространство имен. Во второй строке располагается длинное, подробное и зачастую совершенно ненужное описание схемы (сравните с аналогичным кодом для HTML-страниц). Для корректной работы вполне достаточно ограничиться следующим представлением:

<?xml version="1.0" encoding="utf-8" ?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> </xs:schema>


Впрочем, встроенные средства визуализации студии предполагают наличие этой "шапки", поэтому ее не следует удалять. Создание схемы, описывающей заданный XML-документ в среде Visual Studio .NET - довольно простая задача. Создадим следующий документ XMLEasy.xml:

<?xml version="1.0" encoding="utf-8" ?> <TOUR> <IDTOUR>1</IDTOUR> </TOUR>

Переключаемся на вкладку
(Data) и видим всего одну запись (рис. 11.2).


Рис. 11.2.  Документ XMLEasy.xml в режиме просмотра Data

Создать схему, описывающую этот документ, можно несколькими способами: в главном меню выбрать пункт "XML \ Create Schema" (рис. 11.3, А), в режиме XML в контекстном меню выбрать этот же пункт (рис. 11.3, Б), в режиме Data в контекстном меню выбрать этот пункт (рис. 11.3, В), и, наконец, в режиме Data нажать на кнопку панели инструментов XML (рис. 11.3, Г).


Рис. 11.3.  Различные способы создания XSD схемы в среде Visual Studio .NET

В любом случае появляется схема документа в виде таблицы (рис. 11.4). Оставим пока режим Schema и переключимся в режим
(XML).


Рис. 11.4.  Схема документа. Для наглядности для трех всплывающих подсказок был сделан коллаж

Среда сгенерировала XML-код, описывающий структуру документа:

<?xml version="1.0"?> <xs:schema id="NewDataSet" targetNamespace="http://tempuri.org/XMLEasy.xsd" xmlns:mstns="http://tempuri.org/XMLEasy.xsd" xmlns="http://tempuri.org/XMLEasy.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" attributeFormDefault="qualified" elementFormDefault="qualified"> <xs:element name="TOUR"> <xs:complexType> <xs:sequence> <xs:element name="IDTOUR" type="xs:string" minOccurs="0" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="ru-RU" msdata:EnforceConstraints="False"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element ref="TOUR" /> </xs:choice> </xs:complexType> </xs:element> </xs:schema>



Сюда входит также описание, необходимое для дальнейшего манипулирования схемой при помощи объектов ADO .NET. В исходном документе XMLEasy.xml появилась ссылка на схему данных:

<TOUR xmlns="http://tempuri.org/XMLEasy.xsd">

Документ XMLEasy.xsd был автоматически создан в той же самой директории, где находится XMLEasy.xml.

Для того чтобы научиться понимать схемы XSD, вначале следует поработать с описанием данных в чистом виде, без дополнительных элементов. В таблице 11.1 приводится несколько простейших XML-документов и их схем, сформированных без привязки к объектам ADO .NET.

Таблица 11.1. Примеры составления XSD-схем Содержимое XML-документаСодержимое XSD-схемыОписаниеСодержимое XML-документаСодержимое XSD-схемыОписаниеСодержимое XML-документаСодержимое XSD-схемыОписание
<?xml version="1.0" encoding="utf-8"?> <!-- Документ называется XMLEasy.xml --> <TOUR xmlns= "http://tempuri.org/XMLEasy.xsd"> <IDTOUR>1</IDTOUR> </TOUR>

<?xml version="1.0" encoding="utf-8" ?> <!-- Схема называется XMLEasy.xsd --> <xs:schema xmlns:xs= "http://www.w3.org/2001/XMLSchema"> <xs:element name="TOUR"> <xs:complexType> <xs:sequence> <xs:element name="IDTOUR" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>


В документе XMLEasy.xml элемент TOUR - корневой элемент, содержащий дочерний элемент IDTOUR. Общая схема для корневого элемента имеет следующий вид:

<xs:element name="Название_элемента"> <xs:complexType> : Содержимое элемента ... </xs:complexType> </xs:element>

Дочерние элементы описываются так: <xs:element name="Название_элемента" type="xs:тип_данных" />

<?xml version="1.0" encoding="utf-8"?> <!-- Документ называется XMLTour5.xml --> <TABLE xmlns= "http://tempuri.org/XMLTour5.xsd"> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR> <TOUR> <IDTOUR>2</IDTOUR> <NAME>Греция</NAME> <PRICE>32 000,00р. </PRICE> <INFORMATION>В августе и сентябре действуют специальные скидки</INFORMATION> </TOUR> <TOUR> <IDTOUR>3</IDTOUR> <NAME>Таиланд</NAME> <PRICE>30 000,00р.</PRICE> <INFORMATION>Не включая стоимость авиабилета</INFORMATION> </TOUR> <TOUR> <IDTOUR>4</IDTOUR> <NAME>Италия</NAME> <PRICE>26 000,00р.</PRICE> <INFORMATION>Завтрак в отеле включен в стоимость путевки</INFORMATION> </TOUR> <TOUR> <IDTOUR>5</IDTOUR> <NAME>Франция</NAME> <PRICE>27 000,00р.</PRICE> <INFORMATION>Дополнительные экскурсии не входят в стоимость путевки</INFORMATION> </TOUR> </TABLE>

<?xml version="1.0"?> <!-- Схема называется XMLTour5.xsd --> <xs:schema xmlns:xs= "http://www.w3.org/2001/XMLSchema"> <xs:element name="TABLE"> <xs:complexType> <xs:choice maxOccurs= "unbounded"> <xs:element name="TOUR"> <xs:complexType> <xs:sequence> <xs:element name= "IDTOUR" type= "xs:string" /> <xs:element name= "NAME" type="xs:string" /> <xs:element name="PRICE" type="xs:string" /> <xs:element name="INFORMATION" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>


Корневой элемент TABLE содержит элемент TOUR, состоящий, в свою очередь, из группы дочерних элементов. Элемент choice определяет выбор других элементов, причем значение "unbounded" (неограниченно) атрибута maxOccurs указывает на возможность неограниченного наличия групп TOUR. <xs:element name= "TABLE" > <xs:complexType> <xs:choice maxOccurs= "unbounded"> <xs:element name="TOUR"> <xs:complexType> : : </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element>
<?xml version="1.0" encoding="utf-8" ?> <!-- Документ называется XMLTour6.xml --> <TABLE xmlns= "http://tempuri.org/XMLTour5.xsd"> <TOUR> <IDTOUR>1</IDTOUR> <NAME>Кипр</NAME> <PRICE>25 000,00р. </PRICE> <CLOSED>1</CLOSED> <INFORMATION>В стоимость двух взрослых путевок входит цена одной детской (до 7лет)</INFORMATION> </TOUR> </TABLE>

<?xml version="1.0" ?> <xs:schema xmlns:xs= "http://www.w3.org/2001/XMLSchema"> <xs:element name="TABLE"> <xs:complexType> <xs:choice maxOccurs="unbounded"> <xs:element name="TOUR"> <xs:complexType> <xs:sequence> <xs:element name="IDTOUR" type="xs:int" /> <xs:element name="NAME" type="xs:string" /> <xs:element name="PRICE" type="xs:string" /> <xs:element name="CLOSED" type="xs:boolean"/> <xs:element name="INFORMATION" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:choice> </xs:complexType> </xs:element> </xs:schema>


Рис. 11.5.  Типы данных схемы XSD



Для элемента IDTOUR был установлен тип данных int, для элемента CLOSED - тип boolean, для остальных - по умолчанию тип string. Изменять тип данных можно непосредственно в режиме XML-схемы данных, но более удобно - в режиме Schema (в данном случае режим будет называться DataSet) выбирать тип данных из выпадающего списка (рис. 11.5):
<


В программном обеспечении к курсу вы найдете все файлы этой таблицы в папке XSD (Code\Glava5\ XSD).

Задание типа данных в XML-документе (последний пример - табл. 11.1) - один из способов ограничения содержимого. Для ограничения значения заданного типа применяются дополнительные атрибуты. В следующем фрагменте схемы значение элемента PRICE должно быть в пределах от 50 до 100:

<xs:element name="PRICE" > <xs:simpleType> <xs:restriction base="xs:int" > <xs:minInclusive value="50"/ > <xs:maxInclusive value="100"/ > </xs:restriction> </xs:simpleType> </xs:element>

Для ограничения XML-документа некоторыми фиксированными значениями используется следующая конструкция:

<xs:element name="NAME" > <xs:simpleType> <xs:restriction base="xs:string" > <xs:enumeration value="Кипр"/ > <xs:enumeration value="Греция"/ > <xs:enumeration value="Таиланд"/ > <xs:enumeration value="Италия"/ > <xs:enumeration value="Франция"/ > </xs:restriction> </xs:simpleType> </xs:element>

Здесь элемент NAME может принимать только одно фиксированное значение из пяти названий стран.

Разработка XSD-схемы представляет собой довольно кропотливую работу. Визуальные средства среды Visual Studio .NET значительно облегчают эту задачу. Для освоения основных концепций желательно изучить несколько схем XML-документов, созданных автоматически. В таблицах 11.2-11.4 приводится описание основных элементов и атрибутов, которые при этом можно встретить.

Таблица 11.2. Элементы XSD-схемЭлементОписание
all Вложенные элементы могут определяться в произвольном порядке
annotation Родительский элемент элементов-комментариев <appInfo> и <documentation>
any Любые вложенные элементы
anyAttribute Любые атрибуты
appInfo Элемент-коментарий. Задает титул схемы
attribute Атрибут
attributeGroup Группа атрибутов
choice Выбор других элементов. Аналог оператора "|" в DTD
complexContent Ограничения или расширения модели содержимого сложного типа
complexType Элемент сложного типа
documentation Элемент-комментарий. Предоставляет информацию о схеме
element Элемент
extension Расширения элемента
field Объявление поля. Применяется внутри элемента <unique> для определения полей
group Группа элементов
import Импорт декларации типов из другой схемы
include Включение другой схемы в существующее пространство имен
key Задание элемента или атрибута с ключом, указывающим на другой элемент
keyref Задание элемента или атрибута, на который указывает ключ
list Элемент, который может содержать список значений
redefine Переопределение уже объявленных элементов
restriction Ограничение элемента
schema Корневой элемент схемы
selector Селектор для отбора XML-элементов
sequence Последовательность других элементов. Аналог оператора "," в DTD
simpleContent Модель, содержимое которой представляет только символьные данные
simpleType Элемент простого типа
union Элемент или атрибут, который может иметь множественное значение
unique Элемент или атрибут, который должен иметь уникальное значение
<


table class="xml_table" cellpadding="2" cellspacing="1">

Таблица 11.3. Атрибуты - ограничения XSD-схемАтрибутОписаниеenumeration Список значенийlength ДлинаmaxLength Максимальная длинаminLength Минимальная длинаmaxExclusive Максимальное значениеmaxInclusive Максимальное значение включительноminExclusive Минимальное значениеminInclusive Минимальное значение включительноfractionDigits Количество знаков после запятой в дробных числахtotalDigits Количество цифрpattern Образец (паттерн) содержимого элементовwhiteSpace Количество пробелов в содержимом элементов Таблица 11.4. АтрибутОписание
abstract Задание элемента абстрактного типа
attributeFormDefault Задание свойств локальных атрибутов как глобальных
base Базовый тип элемента
block Запрещенное выведение ограничением (derivations-by-restriction)
blockDefault Задание начального ограничения block на все определения типов
default Значение элемента или атрибута по умолчанию
elementFormDefault Задание свойств локального элемента как глобально определенного
fixed Фиксированное значение элемента или атрибута
form Локально объявленные элементы определяются в конкретных экземплярах документов
itemType Тип пунктов списка
memberTypes Тип членов, использованных в объединении (union)
maxOccurs Максимальное количество вхождений элемента
minOccurs Минимальное количество вхождений элемента
mixed Задание элемента, имеющего смешанный тип
name Название элемента или атрибута
namespace Пространство имен
noNamespace Задание местоположения документа-схемы,
SchemaLocation не имеющего результирующих пространств имен
nillable Определение того, что элемент может иметь пустое значение NULL (nil)
ref Задание ссылки на глобально определенный элемент
schemaLocation Определение местоположения схемы
substitutionGroup Определение замены элементов другими элементами
targetNamespace Результирующее пространство имен схемы
type Тип элемента
use Является элемент обязательным или нет
value Значение элемента схемы
xsi:nil Задание реального содержания пустого (NULL) элемента XML-документа
xsi:schemaLocation Реальное местоположение элемента в XML-документе
xsi:type Реальный тип элемента в XML-документе

Загрузка XML-документов и XSD-схем в типизированный объект DataSet


Мы привыкли получать данные для объекта DataSet из базы Microsoft Access или SQL. Между тем можно загружать данные непосредственно из XML-файла с встроенной схемой или без нее. Для этого применяется метод ReadXml, который является перегруженным и может использоваться для чтения из XML-файла, из объекта подкласса TextReader, из объекта потока или из подкласса XmlReader (рис. 11.25):


увеличить изображение
Рис. 11.25.  Перегруженный метод ReadXml

Метод ReadXml вторым параметром принимает XmlReadMode, применяемый для задания того, что содержит XML-файл и какая информация должна быть из него загружена. Этот параметр не обязателен, и если он не указан, используется значение по умолчанию - Auto. В таблице 11.8 приводятся другие значения параметра XmlReadMode.

Таблица 11.8. Значения параметра XmlReadMode

ЗначениеОписание
ReadSchema Загружает и данные, и схему. Если в DataSet уже есть схема, таблицы из встроенной схемы добавляются. В случае, когда хотя бы одна из добавляемых таблиц уже есть в DataSet, генерируется исключение. При загрузке в объект DataSet, не содержащий схемы, XML-файла также без схемы - данные не будут прочитаны
IgnoreSchema Игнорирует встроенную схему и загружает данные в уже имеющуюся схему DataSet. Не подходящие к этой схеме данные отбрасываются. Если в DataSet нет схемы, то данные не будут прочитаны
InferSchema Игнорирует встроенную схему и выводит схему из структуры данных XML в файле. Если в DataSet уже есть схема, она расширяется путем добавления новых таблиц или путем добавления столбцов в уже существующие таблицы
DiffGram Читает объект DiffGram и добавляет данные в уже существующую схему. (DiffGram-документ содержит всю информацию о данных объекта DataSet, включая первоначальное и текущее значение строк. Эти сведения можно использовать для проведения слияния объектов DataSet или обновления базы данных)
Fragment Читает фрагменты XML до конца потока. Данные, соответствующие схеме DataSet, добавляются в конец нужной таблицы; несоответствующие - отбрасываются.
Auto Используется по умолчанию. Исследует данные XML и выбирает наиболее подходящий режим: Если XML-документ является объектом DiffGram - используется значение DiffGram;Если DataSet содержит схему или XML-документ содержит встроенную схему - используется значение ReadSchema;Если ни DataSet, ни XML не содержат схемы - используется значение InferSchema
<
Для загрузки XSD-схемы в объект DataSet применяется метод ReadXmlSchema. Этот метод является перегруженным, так что можно указать источник информации в одном из следующих видов: имя файла, объект подкласса TextReader, поток или объект подкласса XmlReader (рис. 11.26):


Рис. 11.26.  Перегруженный метод

Скопируйте папку приложения StructureTypedDataSet и назовите ее "TypedDataSetReadXML5)". Открываем проект, перетаскиваем на форму элемент управления MainMenu и создаем следующие пункты (рис. 11.27):


Рис. 11.27.  Пункты главного меню приложения TypedDataSetReadXML

NameText
MnuFile &Файл
MnuFill &Заполнить
MnuOpen &Открыть
MnuAuto &Auto
mnuDiffGram &DiffGram
mnuFragment &Fragment
mnuIgnoreSchema I&gnore Schema
mnuInferSchema &Infer Schema
mnuReadSchema &Read Schema
mnuReadXmlSchema Открыть &схему
Добавляем элемент управления OpenFileDialog, в его свойстве Filter вводим строку для расширений файлов:

XML and XSD Files(*.xml, *.xsd)| *.xml; *.xsd; |All Files(*.*)|*.*

Переходим в код формы. Создаем метод ClearForm, который будет удалять содержимое формы:

private void ClearForm() { dsTour.Clear(); dataGrid1.DataSource = null; richTextBox1.Text = ""; }

Создаем метод StructureDataSet, задача которого - выводить в элемент richTextBox1 структуру типизированного объекта DataSet. Соответствующий фрагмент кода вырезаем из конструктора формы:

private void StructureDataSet() { //Код для вывода структуры объекта DataSet }

Создаем обработчик пункта главного меню "Заполнить". Фрагмент кода для извлечения данных также вырезаем из конструктора формы:

private void mnuFill_Click(object sender, System.EventArgs e) { ClearForm(); //Код для извлечения данных из базы BDTur_firm_Eng.mdb StructureDataSet(); }

Подключаем пространство имен для работы с потоками:

using System.IO;

Создаем обработчик пункта "Auto", в котором делаем переключатель различных значений параметра XmlReadMode. Дополнительно содержимое файла в виде простого текста будет выводиться в элемент richTextBox:



private void mnuAuto_Click(object sender, System.EventArgs e) { ClearForm(); if(openFileDialog1.ShowDialog() == DialogResult.OK) { XmlReadMode readMode = XmlReadMode.Auto; MenuItem menuItem = (MenuItem)sender; switch(menuItem.Index) { case 0: readMode = XmlReadMode.Auto; break; case 1: readMode = XmlReadMode.DiffGram; break; case 2: readMode = XmlReadMode.Fragment; break; case 3: readMode = XmlReadMode.IgnoreSchema; break; case 4: readMode = XmlReadMode.InferSchema; break; case 5: readMode = XmlReadMode.ReadSchema; break; } dsTour.ReadXml(openFileDialog1.FileName, readMode); dataGrid1.DataSource = dsTour; // Создаем поток для заполнения элемента richTextBox1 FileStream filestream= File.Open(openFileDialog1.FileName, FileMode.Open, FileAccess.Read); //Проверяем, открыт ли поток, и если открыт, выполняем условие if(filestream != null) { //Создаем объект streamreader и связываем его с потоком filestream StreamReader streamreader = new StreamReader(filestream); //Считываем весь файл и записываем его в TextBox richTextBox1.Text = streamreader.ReadToEnd(); //Закрываем поток. filestream.Close(); } } }

Аналогично, в обработчике пункта меню "Открыть схему" используем метод ReadXmlSchema объекта DataSet:

private void mnuReadXmlSchema_Click(object sender, System.EventArgs e) { ClearForm(); if(openFileDialog1.ShowDialog() == DialogResult.OK) { dsTour.ReadXmlSchema(openFileDialog1.FileName); dataGrid1.DataSource = dsTour; FileStream filestream= File.Open(openFileDialog1.FileName, FileMode.Open, FileAccess.Read); //Проверяем, открыт ли поток, и если открыт, выполняем условие if(filestream != null) { //Создаем объект streamreader и связываем его с потоком filestream StreamReader streamreader = new StreamReader(filestream); //Считываем весь файл и записываем его в TextBox richTextBox1.Text = streamreader.ReadToEnd(); //Закрываем поток. filestream.Close(); } } }

Последнее, что нам осталось сделать, - привязать обработчиков пунктов меню группы "Открыть" к обработчику пункта "Auto":



public Form1() { InitializeComponent(); //Открытие this.mnuDiffGram.Click += new EventHandler(mnuAuto_Click); this.mnuFragment.Click += new EventHandler(mnuAuto_Click); this.mnuIgnoreSchema.Click += new EventHandler(mnuAuto_Click); this.mnuInferSchema.Click += new EventHandler(mnuAuto_Click); this.mnuReadSchema.Click += new EventHandler(mnuAuto_Click); }

Запускаем приложение. Для его тестирования можно воспользоваться содержимым папки XSD (рис. 11.28):


Рис. 11.28.  Приложение TypedDataSetReadXML. А - загрузка данных из базы, Б - открытие файла XMLTourFull.xml, В - открытие схемы XMLTourFull.xsd

Мы не добавляли обработку исключений, поэтому в ходе тестирования могут возникать ошибки. Добавьте ее самостоятельно для обработчиков пунктов меню "Открыть" и "Открыть схему".

В программном обеспечении к курсу вы найдете приложение TypedData SetReadXML (Code\Glava5\ TypedDataSetReadXML).


Загрузка XML-документов и XSD-схем в обычный объект DataSet


Рассмотрим теперь загрузку XML-документов в обычный объект DataSet. Скопируйте папку приложения TypedDataSetReadXML и переименуйте ее в "UntypedDataSetReadXML". Открываем проект, в окне Solution Explorer щелкаем правой кнопкой на объекте XSDTour_bd.xsd и выбираем пункт меню "Delete". В классе формы удаляем соответствующее объявление:

private TypedDataSet.XSDTour_bd dsTour;

Там же создаем экзмепляр dsTour:

DataSet dsTour;

Далее нам предстоит cделать небольшие исправления в коде. В обработчике пункта меню "Заполнить" инициализируем dsTour и изменяем очередность вызова метода ClearForm:

private void mnuFill_Click(object sender, System.EventArgs e) { ... ClearForm(); dsTour = new DataSet(); conn.Open(); dataAdapter.Fill(dsTour); dataAdapter2.Fill(dsTour); dataAdapter3.Fill(dsTour); dataAdapter4.Fill(dsTour); conn.Close();

dataGrid1.DataSource = dsTour; StructureDataSet(); }

Обработчики пунктов меню "Auto" и "Открыть схему" будут выглядеть так:

private void mnuAuto_Click(object sender, System.EventArgs e) { dsTour = new DataSet(); ClearForm(); ... } private void mnuReadXmlSchema_Click(object sender, System.EventArgs e) { dsTour = new DataSet(); ClearForm(); ... }

Наконец, в самом методе ClearForm добавляем проверку на наличие таблиц в объекте dsTour:

private void ClearForm() { if (dataGrid1.DataSource!=null) { dsTour.Clear(); } dataGrid1.DataSource = null; richTextBox1.Text = ""; }

Запускаем приложение. Теперь объект DataSet содержит текущую структуру документа XMLEasy.xml (рис. 11.29, А) и документа XMLTourFull.xml (рис. 11.29, Б), схему XMLTourFull.xsx (при выборе в качестве типа файлов All Files) (рис. 11.29, В) и конечно, структуру таблиц, загруженных из базы (рис. 11.29, Г). В последнем случае обратите внимание на то, что объект DataSet сам по себе не получает из базы сведений о связях между таблицами.


увеличить изображение
Рис. 11.29.  Приложение UntypedDataSetReadXML. А - загрузка документа XMLEasy.xml, Б - документ XMLTourFull.xml, В - схема XMLTourFull.xsx, Г - данные из базы

В программном обеспечении к курсу вы найдете приложение Untyped DataSetReadXML (Code\Glava5\ UntypedDataSetReadXML).



Запись содержимого и структуры обычного объекта DataSet


Запись содержимого обычного, нетипизированного, объекта DataSet практически ничем не отличается от рассмотренной выше. Скопируйте папку приложения "UntypedDataSetReadXML" и назовите ее "UntypedDataSetReadandWriteXML". Открываем проект и добавляем те же пункты меню, что и для типизированного DataSet. Перетаскиваем на форму элемент управления SaveFileDialog, в свойстве Filter вводим следующее значение:

XML and XSD Files(*.xml, *.xsd)| *.xml; *.xsd; |All Files(*.*)|*.*

Далее создаем обработчиков пунктов меню "DiffGram", "Сохранить схему", а также вносим изменения в конструктор формы - код в точности такой же, как и в приложении TypedDataSetReadandWriteXML. Запускаем приложение. Открывая простейший документ XMLEasy.xml, можно затем сохранить его и схему (рис. 11.34).


увеличить изображение
Рис. 11.34.  Приложение "UntypedDataSetReadandWriteXML". А - сохранение XML - документа, Б - сохранение схемы

В программном обеспечении к курсу вы найдете приложение UntypedData SetReadandWriteXML (Code\Glava5\UntypedDataSetReadandWriteXML).

  1)

  Точный перевод XML Schema Document - <схема документа XML>, однако мы будем в дальнейшем использовать термин "схема данных XML".

  2)

  Режимом дизайна здесь и далее будем называть графический вид элементов.

  3)

  В восьмой лекции была фраза: "Свойство DataSetName используется для работы с XSD-схемами, но пока про это название (до Главы 5) мы можем просто забыть." Теперь про это свойство пора вспомнить.

  4)

  Конечно, извлечь все эти таблицы можно и с помощью всего одного объекта DataAdapter. В шестой Главе мы научимся делать это

  5)

  Здесь и далее используются просто невероятно длинные названия проектов. Я решил, что название "TypedDataSetReadXML" будет более понятным, чем, например, "TDSRXML" или "TDSRX". Впрочем, вы можете называть свои проекты как вам угодно.

Запись содержимого и структуры типизированного объекта DataSet


Содержимое объекта DataSet может быть записано в виде XML-файла. Это очень удобно для последующей передачи данных или межплатформенного взаимодействия. Для записи используется метод WriteXml, который, подобно методу ReadXml, может записывать данные в файл, в объект подкласса XmlWriter, в объект подкласса TextWriter или в поток (рис. 11.30).


Рис. 11.30.  Перегруженный метод WriteXml

Необязательный параметр XmlWriteMode позволяет дополнительно уточнять записываемые данные. Его значения приводятся в таблице 11.9.

Таблица 11.9. Значения параметра XmlWriteMode

ЗначениеОписание
IgnoreSchema В XML-документ записываются только данные объекта DataSet. Если данные не загружены, документ не создается. Это значение по умолчанию
WriteSchema В XML-файл записываются данные объекта DataSet с добавлением схемы в качестве встроенного XSD-документа
DiffGram В XML-документ сохраняются первоначальные и текущие значения данных

Для записи схемы DataSet применяется метод WriteXmlSchema, который записывает данные в те же объекты, что и методы WriteXml или ReadXml (рис. 11.31):


Рис. 11.31.  Перегруженный метод WriteXmlSchema

Скопируйте папку приложения TypedDataSetReadXML и назовите ее "TypedDataSetReadandWriteXML". Добавляем в главное меню следующие пункты (рис. 11.32):

NameText
mnuSave &Сохранить
mnusDiffGram Diff&Gram
mnusIgnoreSchema Ig&nore Schema
mnusWriteSchema &Write Schema
mnusSchema Cохранить схе&му


Рис. 11.32.  Главное меню приложения TypedDataSetReadandWriteXML

Из окна Toolbox перетаскиваем на форму элемент управления SaveFileDialog, в свойстве Filter вводим то же самое значение, что и для OpenFileDialog:

XML and XSD Files(*.xml, *.xsd)| *.xml; *.xsd; |All Files(*.*)|*.*

Создаем обработчик пункта меню "DiffGram", в котором устанавливаем переключатель для различных значений параметра XmlWriteMode метода WriteXml:

private void mnusDiffGram_Click(object sender, System.EventArgs e) { if(saveFileDialog1.ShowDialog() == DialogResult.OK) { XmlWriteMode writeMode = XmlWriteMode.DiffGram; MenuItem menuItem = (MenuItem)sender; switch(menuItem.Index) { case 0: writeMode = XmlWriteMode.DiffGram; break; case 1: writeMode = XmlWriteMode.IgnoreSchema; break; case 2: writeMode = XmlWriteMode.WriteSchema; break; } if (dataGrid1.DataSource == null) { MessageBox.Show("Нет данных для записи!", "Внимание"); return; } DataSet currentds = (DataSet)dataGrid1.DataSource; currentds.WriteXml(saveFileDialog1.FileName, writeMode); } }


Создаем обработчик пункта "Сохранить схему":

private void mnusSchema_Click(object sender, System.EventArgs e) { if(saveFileDialog1.ShowDialog() == DialogResult.OK) { dsTour.WriteXmlSchema(saveFileDialog1.FileName); } if (dataGrid1.DataSource == null) { MessageBox.Show("Нет данных для записи!", "Внимание"); return; } }

Наконец, в конструкторе формы привязываем обработчиков пунктов меню группы "Сохранить" к обработчику пункта "DiffGram":

public Form1() { InitializeComponent(); //Открытие this.mnuDiffGram.Click += new EventHandler(mnuAuto_Click); this.mnuFragment.Click += new EventHandler(mnuAuto_Click); this.mnuIgnoreSchema.Click += new EventHandler(mnuAuto_Click); this.mnuInferSchema.Click += new EventHandler(mnuAuto_Click); this.mnuReadSchema.Click += new EventHandler(mnuAuto_Click); //Сохранение this.mnusIgnoreSchema.Click += new EventHandler(mnusDiffGram_Click); this.mnusWriteSchema.Click += new EventHandler(mnusDiffGram_Click); }

Запускаем приложение. При выборе пункта меню "Write Schema" (метод WriteXml) записываются данные и схема содержимого DataSet (рис. 11.33, А), при выборе пункта "Сохранить схему" (метод WriteXmlSchema) - только схема (рис. 11.33, Б).


увеличить изображение
Рис. 11.33.  Приложение "TypedDataSetReadandWriteXML". А - сохранение данных и схемы содержимого DataSet, Б - сохранение только схемы

В программном обеспечении к курсу вы найдете приложение TypedData SetReadandWriteXML (Code\Glava5\TypedDataSetReadandWriteXML).


Изменение записей


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

Создайте новое Windows-приложение и назовите его "Change_Data". Для ускорения разработки интерфейса выделяем и копируем все элементы на форме приложения DataBindings, которое мы создали во второй лекции. Располагаем вставленные элементы на форме приложения Change_Data - при этом мы избавляемся от необходимости определять свойства Name и Text. Перетаскиваем объект oleDbDataAdapter из окна Toolbox, устанавливаем соединение с таблицей "Туристы" базы данных Microsoft Access BDTur_firm2.mdb (Code\Glava3 BDTur_firm2.mdb) . Генерируем объект DataSet и называем его "dsTourists". Связываем текстовые поля с соответствующими столбцами таблицы "Туристы", используя свойство DataBindings. В конструкторе формы вызываем метод Fill объекта DataAdapter:

public Form1() { InitializeComponent(); oleDbDataAdapter1.Fill(dsTourists1); }

Для проверки приложения запускаем его. Добавляем кнопки для навигации по записям и соответствующие обработчики, используя объект CurrencyManager (можно также воспользоваться готовым проектом DataBindings, изменяя названия объекта DataSet). Добавим теперь кнопки для добавления и удаления записей. Располагаем на форме две кнопки - "Добавить" и "Удалить", в свойстве Name устанавливаем значения "btnAdd" и "btnRemove" соответственно. В обработчике кнопки "Добавить" вызываем метод AddNew объекта CurrencyManager:

private void btnAdd_Click(object sender, System.EventArgs e) { cmRecords.AddNew(); }

Для обработчика кнопки "Удалить" также используем встроенный метод RemoveAt, причем при отсутствии на форме записей вызываем окно предупреждения:

private void btnRemove_Click(object sender, System.EventArgs e) { if(cmRecords.Count>0)cmRecords.RemoveAt(cmRecords.Position); else MessageBox.Show("Нет записи для удаления!", "Удаление записи", MessageBoxButtons.OK, MessageBoxIcon.Error); }


Запускаем приложение. Теперь можно добавлять и удалять записи - однако все сделанные изменения хранятся в объекте DataSet и не передаются в базу данных. Перезапустив приложение, мы обнаружим, что все записи остались прежними. Добавляем кнопку "Обновить", свойству Name этой кнопки устанавливаем значение "btnUpdate". В обработчике кнопки создаем новый объект DataSet changes, в который будут записываться все изменения старого объекта dsTourists1 при помощи метода GetChanges. Методу Update объекта oleDbDataAdapter1 передаем новый объект changes и, наконец, вызываем метод AcceptChanges для подтверждения изменений:

private void btnUpdate_Click(object sender, System.EventArgs e) { DataSet changes = dsTourists1.GetChanges(); oleDbDataAdapter1.Update(changes); dsTourists1.AcceptChanges(); }

Запускаем приложение, вносим новую запись и нажимаем кнопку "Обновить". Возникает сообщение об ошибке: "Additional information: Value cannot be null" ("Дополнительная информация: значение не может быть равным нулю"). Дело в том, что объект CurrencyManager сохраняет изменения в DataSet после того, как мы перейдем от редактируемой записи к следующей. Но кроме этого, подобная функциональность кнопки "Обновить" нас не может устраивать. Поэтому добавим блок для обработки исключений, который будет проверять наличие изменений в dsTourists1:

private void btnUpdate_Click(object sender, System.EventArgs e) { if (dsTourists1.HasChanges()) try { DataSet changes = dsTourists1.GetChanges(); oleDbDataAdapter1.Update(changes); dsTourists1.AcceptChanges(); }

catch(Exception ex) { MessageBox.Show(ex.Message, "Неудачное обновление", MessageBoxButtons.OK, MessageBoxIcon.Error); } else MessageBox.Show("Нет записей для изменения", "Изменение записей", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

Теперь, если мы попытаемся внести изменения, не переходя к следующей записи, появится окно предупреждения "Нет записей для изменения".


Более того, если связывание с базой данных окажется невозможным (например, она заблокирована), то в окне "Неудачное обновление" выйдет сообщение с кодом ошибки. Для того чтобы пользователю не приходилось задумываться о работе CurrencyManager, добавим в код кнопки "Обновить" отключение остальных кнопок на время редактирования записи:

private void btnAdd_Click(object sender, System.EventArgs e) { cmRecords.AddNew(); btnFirst.Enabled = false; btnLast.Enabled = false; btnNext.Enabled = false; btnPrevious.Enabled = false; btnRemove.Enabled = false; btnUpdate.Enabled = false;

}

Для возврата к обычному режиму добавим на форму кнопку "Сохранить", которая будет возвращать позицию на первую запись и включать остальные кнопки:

private void btnSave_Click(object sender, System.EventArgs e)

{ cmRecords.Position = 0; btnFirst.Enabled = true; btnLast.Enabled = true; btnNext.Enabled = true; btnPrevious.Enabled = true; btnRemove.Enabled = true; btnUpdate.Enabled = true; }

Теперь наше приложение позволяет просматривать, изменять и сохранять записи в базе данных (рис. 12.29):


Рис. 12.29.  Готовое приложение "Change_Data"

В программном обеспечении к курсу вы найдете приложение Change_ Data (Code\Glava6\Change_Data).


Конструктор объекта DataAdapter


Существует три способа создания объекта DataAdapter1): перетаскивание из окна Toolbox элемента DataAdapter, перетаскивание из окна Server Explorer определенного подключения к базе данных или создание в коде. Первые два способа были рассмотрены во второй лекции, а для программного создания можно использовать один из четырех конструкторов, приведенных в таблице 12.1.

Таблица 12.1. Конструктор класса DataAdapter

№ВидОписание
1public DataAdapter ()Конструктор по умолчанию
2public DataAdapter (string commandText, string connectionString)Первый параметр представляет собой свойство CommandText объекта Command. В свою очередь, объект Command представляется свойством SelectCommand объекта DataAdapter. Второй параметр - строка подключения к базе данных ConnectionString
3public DataAdapter (string commandText, Connection connection)Первый параметр - свойство CommandText объекта Command, второй - объект класса Connection
4public DataAdapter (Command command)В качестве параметра передается проинициализированный объект класса Command

В лекции 1 у нас был проект ConnectionSQL. Скопируйте его папку и переименуйте в "ConstructorDA". В классе формы мы объявляли строки СommandText и СonnectionString:

string commandText = "SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region," + " PostalCode, Country, Phone, Fax FROM Customers"; string connectionString = "workstation id=7EA2B2F6068D473;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=NorthwindCS";

Для первого варианта таблицы 12.1 конструктор2) формы будет иметь следующий вид:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close(); }


При выполнении метода Fill будет устанавливаться соединение с базой данных на время извлечения данных. Здесь методы Open и Close объекта conn являются необязательными - другими словами, метод Fill сам откроет и закроет соединение, когда ему будет нужно. Однако оставим их для наглядности. Весь код представляется нам уже достаточно знакомым.

Во втором варианте конструктор формы будет выглядеть так:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; //SqlCommand myCommand = new SqlCommand(); //myCommand.Connection = conn; //myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(commandText, connectionString); //dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close(); }

Здесь, по сути, мы отказались от применения экзмепляра myCommand класса SqlCommand. Внимательно посмотрите на закомментированные фрагменты: объект myCommand вводился для определения строки commandText и опосредованно - через объект conn строки connectionString. Применяя конструктор объекта dataAdapter, в котором задаются обе эти строки, мы делаем ненужным введение объекта myCommand для этой же цели. Для других задач, например, для запуска хранимой процедуры, объект myCommand может понадобиться. Здесь также можно отказаться от объекта conn, если предоставить методу Fill самому открывать и закрывать соединение:

public Form1() { //SqlConnection conn = new SqlConnection(); //conn.ConnectionString = connectionString; //SqlCommand myCommand = new SqlCommand(); //myCommand.Connection = conn; //myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(commandText, connectionString); //dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); //conn.Open(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; //conn.Close(); }



Наконец, значения строк commandText и connectionString можно задавать прямо в конструкторе, тогда их не нужно объявлять в классе формы:

public Form1() { //SqlConnection conn = new SqlConnection(); //conn.ConnectionString = connectionString; //SqlCommand myCommand = new SqlCommand(); //myCommand.Connection = conn; //myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter("SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region," + "PostalCode, Country, Phone, Fax FROM Customers", "workstation id=7EA2B2F6068D473;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=NorthwindCS"); //dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); //conn.Open(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; //conn.Close(); }

Наилучшим способом управления данными в цепочке "несколько объектов DataTable объекта DataSet - несколько таблиц в базе данных" является создание отдельных объектов DataAdapter для каждого экземпляра DataTable. Представим себе, что наш DataSet состоит из десяти таблиц (представленных соответствующими объектами DataTable). Нам понадобится десять экземпляров DataAdapter для управления данными. Если эти десять экземпляров мы будем создавать вторым конструктором, куда подаются строки запроса и подключения, то в результате появятся десять независимых объектов класса Connection, одинаковых по своей сути. Производительность такого приложения будет низкой.

Чтобы решить эту проблему, лучше применить третий вариант конструктора для создания адаптера, принимающего строку запроса и объект Connection. Следующий фрагмент кода создает два объекта DataAdapter, использующих один объект Connection:

public Form1() { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; //SqlCommand myCommand = new SqlCommand(); //myCommand.Connection = conn; //myCommand.CommandText = commandText; SqlDataAdapter dataAdapter1 = new SqlDataAdapter(commandText1, conn); //Создаем второй экземпляр dataAdapter2 класса DataAdapter //SqlDataAdapter dataAdapter2 = new SqlDataAdapter(commandText2, conn); //dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); conn.Open(); dataAdapter1.Fill(ds, "Customers"); //Используем второй экземпляр dataAdapter2 для заполнения даными //dataAdapter2.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close(); }



В качестве строк commandText1 и commandText2 применяется исходная строка commandText, в которой разделили выбираемые поля пополам:

string commandText1 = "SELECT CustomerID, CompanyName, ContactName, ContactTitle FROM Customers"; string commandText2 = "SELECT Address, City, Region, PostalCode, Country, Phone, Fax FROM Customers";

Конечно же, в конструкторе можно передать значения этих строк непосредственно:

SqlDataAdapter dataAdapter1 = new SqlDataAdapter("SELECT CustomerID, CompanyName, ContactName, ContactTitle FROM Customers", conn); //Создаем второй экземпляр dataAdapter2 класса DataAdapter //SqlDataAdapter dataAdapter2 = // new SqlDataAdapter("SELECT Address, City, Region, PostalCode, // Country, Phone, Fax FROM Customers", conn);

Запускаем приложение. Выводится первая половина таблицы Customers, соответствующая запросу commandText1 (рис. 12.1, А). Изменим применяемый объект DataAdapter с помощью комментирования - выводится запрос commandText2 (рис. 12.1, Б).


увеличить изображение
Рис. 12.1.  Создание двух объектов DataAdapter, A - содержимое первого запроса (commandText1), Б - содержимое второго запроса (commandText2)

Четвертый способ создания конструктора представляет собой включение объекта Command:

public Form1() { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(myCommand); //dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close(); }

Какой из этих способов лучше всего использовать? Трудно дать универсальную рекомендацию - в каждом конкретном случае наиболее подходящим может быть один из этих способов.

В программном обеспечении к курсу вы найдете приложение Constructor DA (Code\Glava6 ConstructorDA).


Метод Fill


Мы использовали ранее метод Fill объекта DataAdapter для заполнения данными DataSet, не задумываясь о том, что этот метод является перегруженным. В таблице 12.2 приводится описание различных параметров этого метода:

Таблица 12.2. Конструктор метода Fill

№Вид
1

Экземпляр DataSet, начальная запись, количество записей, таблица-источник.

Пример: dataAdapter.Fill(ds, 0, 5,"Customers")

2

Экземпляр DataSet, таблица-источник.

Пример: dataAdapter.Fill(ds, "Customers")

3

Экземпляр DataSet.

Пример:dataAdapter.Fill(ds);

4

Экземпляр DataTable.

Пример:dataAdapter.Fill(dtCustomers);

В качестве таблицы-источника указывается название таблицы, извлекаемой из базы данных, - в приведенных примерах таковой является Customers. Создайте новое приложение и назовите его "FillMethod". Получите функциональность приложения ConstructorDА с первым вариантом конструктора: для этого достаточно просто скопировать соответствующие фрагменты кода. Для вывода первых пяти записей таблицы Customers применяем следующий метод Fill объекта DataAdapter:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds, 0, 5,"Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close(); }

Здесь 0 - индекс первой записи, 5 - число записей. Запускаем приложение. Объект DataAdapter выводит на форму только пять первых записей, отбрасывая все остальные (рис. 12.2):


Рис. 12.2.  Вывод заданного количества записей

Тот же самый результат можно было получить, формируя в SQL-запросе (строке commandText) выборку нужных записей. Однако применение DataAdapter является более гибким средством - из всего набора записей, возвращенного запросом, можно при помощи нескольких объектов DataAdapter делать различные выборки.

В программном обеспечении к курсу вы найдете приложение FillMethod (Code\Glava6 FillMethod).



Применение свойства TableMappings для помещения в DataSet нескольких таблиц


Свойство TableMappings используется также для помещения нескольких таблиц из базы данных в один объект DataSet. Создайте новое приложение и назовите его SomeTable. Из окна Toolbox перетаскиваем на форму элемент управления TabControl, устанавливаем его свойству Dock значение Fill. В поле свойства TabPages нажимаем на кнопку

(...) (рис. 12.25, А). В редакторе TabPage Collection Editor нажимаем три раза на кнопку "Add" для создания элементов со следующими свойствами (рис. 12.25, Б):


увеличить изображение
Рис. 12.25.  Настройка элемента TabControl. А - запуска редактора, Б - настройка элементов

NameText
tpCustomersCustomers
tpEmployeesEmployees
tpOrdersOrders

Завершив работу, нажимаем "ОК" - на форме появляются три вкладки. Помещаем на каждую вкладку элемент управления DataGrid с установленным значением Fill свойства Dock. Называем их "dgCustomers", "dgEmployees", "dgOrders" в соответствии с названиями вкладок. Переходим в код формы и подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В классе формы создаем строки commandText и connectionString:

string commandText = "SELECT * FROM Customers; SELECT * FROM Employees; SELECT * FROM Orders;"; string connectionString = "workstation id=7EA2B2F6068D473;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=NorthwindCS";

Обратите внимание на то, что в SQL-запросе commandText используется извлечение содержимого сразу трех таблиц. В конструкторе формы создаем объекты SqlConnection, SqlCommand и SqlDataAdapter:

SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand;

Вызывая метод Add свойства TableMapping, мы помещаем несколько таблиц в один объект DataAdapter:


dataAdapter.TableMappings.Add("Customers", "DataSetTableCustomers"); dataAdapter.TableMappings.Add("Employees", "DataSetTableEmployees"); dataAdapter.TableMappings.Add("Orders", "DataSetTableOrders");

В параметрах метода Add передаем название таблицы в базе данных, а затем название в объекте DataSet (рис. 12.26):


Рис. 12.26.  Параметры метода Add

Создаем объект DataSet, заполняем его данными, определяем источник данных для элементов DataGrid:

DataSet ds = new DataSet(); dataAdapter.Fill(ds); dgCustomers.DataSource = ds.Tables[0].DefaultView; dgEmployees.DataSource = ds.Tables[1].DefaultView; dgOrders.DataSource = ds.Tables[2].DefaultView;

Свойство Tables принимает либо порядковый номер объекта DataTable в DataSet, либо его название (Name) (рис. 12.27):


Рис. 12.27.  Свойство Tables

Здесь мы применили обращение по номеру. Запускаем приложение. Перемещаясь по вкладкам, можно видеть таблицы Customers, Employees и Orders (рис. 12.28):


Рис. 12.28.  Готовое приложение SomeTable

В программном обеспечении к курсу вы найдете приложение SomeTable (Code\Glava6 SomeTable).


Создание объектов OleDbCommand для передачи изменений в базу данных


Для взаимодействия с базой данных Microsoft Access нам потребуется настроить объекты OleDbCommand. Создайте новое Windows-приложение и назовите его "VisualAllOleDbCommands". Визуальная часть его создания будет совпадать с описанием разработки приложения VisualAllSqlCommands, за исключением, пожалуй, приставки "OleDb" в названиях объектов. Ключевым моментом здесь является синтаксис SQL-запросов - поставщик OleDb не поддерживает названия параметров типа "@Имя_параметра". Вместо этого используется знак вопроса - "?". С учетом этого правила запрос объекта myInsertCommand будет выглядеть так:

INSERT INTO Туристы (Фамилия, Имя, Отчество) VALUES (?, ?, ?)

Здесь мы убрали возможность вставки значения поля "Кодтуриста", поскольку в самой базе данных Microsoft Access значения этого поля подставляются автоматически7).

Немного сложнее будет устроена конструкция объекта myUpdate Command. Мы можем просто заменить значения параметров на символы вопроса:

UPDATE Туристы SET Кодтуриста = ?, Имя = ?, Отчество = ?, Фамилия = ? WHERE (Кодтуриста = ?)

Но тогда этот запрос допускает изменение значения поля "Кодтуриста", что приводит к ошибке (рис. 12.40):


увеличить изображение
Рис. 12.40.  Неверный запрос объекта myUpdateCommand и связанное с ним сообщение об ошибке

Следовательно, наше приложение также не должно предоставлять возможность обновления значения поля "Кодтуриста":

UPDATE Туристы SET Имя = ?, Отчество = ?, Фамилия = ? WHERE (Кодтуриста = ?)

Аналогично, в запросе объекта myDeleteCommand убираем возможность удаления отдельного значения поля "Кодтуриста":

DELETE FROM Туристы WHERE (Фамилия = ?) OR (Имя = ?) OR (Отчество = ?)

Наконец, запрос объекта mySelectCommand, не содержащий параметров, будет иметь самый обычный вид:

SELECT * FROM Туристы

Код приложения также будет совпадать, за исключением префиксов в названиях объектов.

В программном обеспечении к курсу вы найдете приложение VisualAll OleDbCommands (Code\Glava6\VisualAllOleDbCommands).


Нетрудно создать аналогичное приложение без применения визуальных средств Visual Studio .NET. Создайте новое приложение и назовите его "ProgrammAllOleDbCommands". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой данных:

using System.Data.OleDb;

В классе формы создаем строку подключения, объекты DataSet и DataAdapter:

string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" ";Data Source=""D:\Uchebnik\Code\Glava3\BDTur_firm2.mdb" ";User ID=Admin;Jet OLEDB:Encrypt Database=False"; DataSet dsTourists; OleDbDataAdapter dataAdapter;

В конструкторе формы создаем все объекты ADO .NET:

public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; OleDbCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandText = "SELECT * FROM Туристы"; dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; //InsertCommand OleDbCommand myInsertCommand = conn.CreateCommand(); myInsertCommand.CommandText = "INSERT INTO Туристы (Фамилия, Имя, Отчество) VALUES (?, ?, ?)"; myInsertCommand.Parameters.Add("@Фамилия", OleDbType.VarWChar, 50, "Фамилия"); myInsertCommand.Parameters.Add("@Имя", OleDbType.VarWChar, 50, "Имя"); myInsertCommand.Parameters.Add("@Отчество", OleDbType.VarWChar, 50, "Отчество"); dataAdapter.InsertCommand = myInsertCommand; //UpdateCommand OleDbCommand myUpdateCommand = conn.CreateCommand(); myUpdateCommand.CommandText = "UPDATE Туристы SET Имя = ?, Отчество = ?, Фамилия = ? WHERE (Кодтуриста = ?)"; myUpdateCommand.Parameters.Add("@Имя", OleDbType.VarWChar, 50, "Имя"); myUpdateCommand.Parameters.Add("@Отчество", OleDbType.VarWChar, 50, "Отчество"); myUpdateCommand.Parameters.Add("@Фамилия", OleDbType.VarWChar, 50, "Фамилия"); myUpdateCommand.Parameters.Add(new OleDbParameter("@Кодтуриста", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); dataAdapter.UpdateCommand = myUpdateCommand; //DeleteCommand OleDbCommand myDeleteCommand = conn.CreateCommand(); myDeleteCommand.CommandText = "DELETE FROM Туристы WHERE (Фамилия = ?) OR (Имя = ?) OR (Отчество = ?)"; myDeleteCommand.Parameters.Add("@Фамилия", OleDbType.VarWChar, 50, "Фамилия"); myDeleteCommand.Parameters.Add("@Имя", OleDbType.VarWChar, 50, "Имя"); myDeleteCommand.Parameters.Add("@Отчество", OleDbType.VarWChar, 50, "Отчество"); dataAdapter.DeleteCommand = myDeleteCommand; }



В событии Closing формы передаем изменения в базу данных:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { if (dsTourists.HasChanges()) { dataAdapter.Update(dsTourists); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } }

Запускаем приложение. Мы снова можем добавлять, изменять и удалять записи.

В программном обеспечении к курсу вы найдете приложение Programm AllOleDbCommands (Code\Glava6\ProgrammAllOleDbCommands).

При добавлении параметра "@Кодтуриста" в объект Update Command мы воспользовались конструктором OleDbParameter. В сравнении с методом Add набора OleDbParameterCollection (которым мы пользовались ранее), он предоставляет возможность задания большего количества свойств (рис. 12.41):


увеличить изображение
Рис. 12.41.  Метод Add набора OleDbParameterCollection и конструктор new OleDbParameter

В таблице 12.3 приводится описание свойств конструктора.

Таблица 12.3. Свойства конструктора OleDbParameterСвойствоОписание
parameterNameНазвание параметра. Например, "@Кодтуриста" или "Original_Кодтуриста"
dbTypeТип данных параметра
directionЗначение перечисления ParameterDirection, определяющее, является ли параметр входным, выходным, смешанным или возвращающим значение
isNullableОпределение, может ли параметр иметь значения null
precisionТочность числового параметра. Задается количеством знаков после запятой. Для нечисловых параметров это значение равно 0
scaleШкала - общее число знаков числового параметра
sizeРазмер параметра
sourceColumnИсточник данных - название поля (столбца) в объекте DataSet или DataTable
sourceVersionЗначение перечисления DataRowVersion, указывающее на версию передаваемых записей
valueЗначение параметра
При создании объектов Command в визуальном режиме среда использует именно конструктор OleDbParameter для определения создаваемых параметров. Открываем приложение VisualAllOleDbCommands, выделяем объект myUpdateCommand и в окне Properties нажимаем на кнопку
(...) в поле свойства Parameters.


В окне редактора OleDbParameter Collection Editor мы видим параметр "Original_Кодтуриста" с определенными свойствами (рис. 12.42):


Рис. 12.42.  Параметр "Original_Кодтуриста"

Переходим в код формы, раскрываем область Windows Form Designer generated code - для параметра "Original_Кодтуриста" среда сгенерировала код, применив конструктор OleDbParameter:

// // myUpdateCommand // ... this.myUpdateCommand.Parameters.Add(new System.Data.OleDb.OleDbParameter ("Original_Кодтуриста", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));

В приложении ProgrammAllOleDbCommands для задания параметра "@Кодтуриста" мы применили этот же код, убрав определение пространства имен:

//UpdateCommand ... myUpdateCommand.Parameters.Add(new OleDbParameter("@Кодтуриста", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));

Конструктор SqlParameter обладает аналогичной структурой и свойствами.

  1)

  Здесь и далее под названием "DataAdapter" будут подразумеваться как OleDbDataAdapter, так и SqlDataAdapter.

  2)

  На этой странице очень часто встречается слово "конструктор" - не спутайте конструктор объекта DataAdapter с конструктором формы, который имеет вид: public Form1() {//Содержимое конструктора формы}

  3)

  Здесь и далее будут применяться названия на кириллице. Это сделано для наглядности - в реальных приложениях следует использовать латинские буквы.

  4)

  Или базы данных "Northwind".

  5)

  Как вы помните, свойство "AllowDBNull" объекта DataColumn определяет возможность пустых значений: DataColumn myColumn; DataColumn dcmyColumn = dtMyTable.Columns.Add("Название", typeof(Int32)); dcmyColumn.Unique = true; dcmyColumn.AllowDBNull = false;

  6)

  Метод EndCurrentEdit объекта CurrencyManager также позволяет справиться с этой задачей.

  7)

  Для этого поля установлен тип данных "счетчик". Это означает, что уникальные значения формируются автоматически в порядке возрастания - см. первую лекцию.

Создание объектов SqlCommand для передачи изменений в базу данных


При создании объекта DataAdapter с помощью мастера Data Adapter Configuration Wizard происходит генерирование его структуры. Далее для передачи изменений в базу данных достаточно просто вызвать метод Update - мы только что применяли его в приложении Change_Data. Объект DataAdapter может быть также создан программно при помощи всего двух строк кода. Мы неоднократно делали это для извлечения данных. Однако передача изменений в базу невозможна без определения объектов UpdateCommand, DeleteCommand и InsertCommand. Раскрыв область "Windows Form Designer generated code" в приложении Change_Data, можно обнаружить следующие фрагменты кода:

... this.oleDbDataAdapter1 = new System.Data.OleDb.OleDbDataAdapter(); this.oleDbSelectCommand1 = new System.Data.OleDb.OleDbCommand(); this.oleDbInsertCommand1 = new System.Data.OleDb.OleDbCommand(); this.oleDbUpdateCommand1 = new System.Data.OleDb.OleDbCommand(); this.oleDbDeleteCommand1 = new System.Data.OleDb.OleDbCommand(); ... // // oleDbSelectCommand1 // ... // // oleDbInsertCommand1 // ... // // oleDbUpdateCommand1 // ... // // oleDbDeleteCommand1 // ...

Итак, среда генерирует объекты вместе с соответствующими параметрами. Создайте новое Windows-приложение и назовите его "VisualAllSqlCommands". Перетаскиваем на форму элемент управления Datagrid, его свойству Dock устанавливаем значение "Fill". В окне Toolbox переходим на вкладку Data и добавляем четыре объекта SqlCommand. В свойстве Name каждого из них вводим "mySelectCommand", "myInsertCommand", "myUpdateCommand", "myDeleteCommand". Выделяем объект mySelectCommand в его свойстве Connection выбираем из выпадающего списка значение "New". Появляется окно "Свойства связи с данными", в котором настраиваем подключение к базе данных BDTur_firm2. Завершив настройку, нажимаем кнопку "OK" - на панели компонент появляется объект sqlConnection1. Снова выделяем объект mySelectCommand, в поле его свойства CommandText нажимаем на кнопку (_).
В появившемся окне " Query Builder" настраиваем извлечение всех полей таблицы "Туристы". Выделяем объект myInsertCommand и в свойстве Connection выбираем существующее соединение sqlConnection1 (рис. 12.30):


Рис. 12.30.  Свойство Connection объекта myInsertCommand

Проделываем то же самое для объектов myUpdateCommand и myDeleteCommand. Снова выделяем объект myInsertCommand, в поле его свойства CommandText нажимаем на кнопку
(...). В появившемся окне Query Builder нажимаем кнопку "Close", закрывая дочернее окно Add Table. В поле запроса Query Builder вставляем следующую SQL-конструкцию:

INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (@Кодтуриста, @Фамилия, @Имя, @Отчество)

Это запрос относится к параметризированным запросам, с ними мы работали в лекции 3. Нажимаем "OK" для закрытия редактора. Появляется предупреждение: "Для некоторых параметров информация об исходных полях может быть пропущена. Вы действительно хотите применить новую конфигурацию параметров?". Дело в том, что среда пытается получить информацию о параметрах из источника - базы данных, которая не всегда может быть получена. Соглашаемся, однако, с предупреждением, нажимая кнопку "Да" (рис. 12.31):


Рис. 12.31.  Окно предупреждения

Для просмотра настроенных параметров в поле свойства "Parameters" нажимаем на кнопку
(...). В редакторе SqlParameter Collection Editor можно видеть параметры, свойства которых (Size, SqlDbType) совпадают с оригинальными значениями полей базы данных BDTur_firm2 (рис. 12.32):


увеличить изображение
Рис. 12.32.  Свойство Parameters объекта "myInsertCommand"

В этом можно убедиться, запустив SQL Server Enterprise Manager и затем открыв таблицу "Туристы" (рис. 12.33):


Рис. 12.33.  Просмотр свойств таблицы "Туристы" в SQL Server Enterprise Manager

Построитель запросов (свойство CommandText) объекта "myInsert Command" также изменил свой внешний вид - теперь в визуальном режиме можно определять параметры запроса (рис. 12.34):




Рис. 12.34.  Окно построителя выражения объекта "myInsertCommand"

Перейдем к настройке объекта myUpdateCommand. Выделяем его, в поле свойства CommandText нажимаем кнопку
(...). Мы можем снова скопировать и вставить (или ввести вручную) готовую SQL-конструкцию, которая должна быть следующей:

UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия, Имя = @Имя, Отчество = @Отчество WHERE (Кодтуриста = @Кодтуриста)

После подтверждения окно Query Builder снова изменит свой вид. Если непосредственное написание SQL-запросов вызывает трудности, можно вначале преобразовать вид построителя, а затем воспользоваться визуальным режимом. Для этого после добавления таблицы "Туристы" щелкаем правой кнопкой в поле Query Builder, а затем выбираем пункт меню "Update". Построитель выражений меняет свой внешний вид - далее запрос можно конструировать, отмечая поля таблицы и вводя названия параметров (рис. 12.35):


увеличить изображение
Рис. 12.35.  Изменение окна "Query Builder" объекта "myUpdateCommand"

В любом случае результат будет одинаковым. Выделяем объект myDeleteCommand, в поле свойства CommandText нажимаем кнопку
(...). Вводим сразу или конструируем запрос на удаление:

DELETE FROM Туристы WHERE (Кодтуриста = @Кодтуриста) OR (Фамилия = @Фамилия) OR (Имя = @Имя) OR (Отчество = @Отчество)

Здесь мы применяем менее строгий запрос, содержащий оператор OR. Если нужно удалять записи целиком, следует использовать оператор AND:

DELETE FROM Туристы WHERE (Кодтуриста = @Кодтуриста) AND (Фамилия = @Фамилия) AND (Имя = @Имя) AND (Отчество = @Отчество)

В нашем случае значения условий распределяются по столбцам "OR" (рис. 12.36).


Рис. 12.36.  Окно построителя выражения объекта "myDeleteCommand"

Мы завершили настройку объектов Command. В окне Toolbox переходим на вкладку Data и перетаскиваем на форму объект SqlDataAdapter. В появившемся мастере "Data Adapter Configuration Wizard" нажимаем кнопку "Cancel" - у нас уже имеется настроенное подключение.


В окне Properties DataAdapter устанавливаем значения свойств "DeleteCommand", "InsertCommand", "SelectCommand" и "UpdateCommand", выбирая из выпадающего списка названия соответствующих объектов (рис. 12.37):


Рис. 12.37.  Свойства объекта DataAdapter

Нажимаем на ссылку "Generate Dataset". В появившемся окне вводим название объекта "dsTourists". Подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В конструкторе формы заполняем объект DataSet и определяем источник данных для элемента DataGrid:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dsTourists1); dataGrid1.DataSource = dsTourists1.Tables[0].DefaultView; }

Изменения будут записываться в базу данных при закрытии формы. Обработчик события Closing формы можно создать в режиме дизайна, нажав на кнопку
"Events":

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { sqlDataAdapter1.Update(dsTourists1); }

Этого кода достаточно для сохранения данных, однако приложение будет обращаться к базе данных всякий раз при своем закрытии, даже если изменения внесены не были. Чтобы избежать ненужного расходования трафика, добавим проверку на наличие изменений в объекте DataSet:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (dsTourists1.HasChanges()) { sqlDataAdapter1.Update(dsTourists1); } }

Теперь приложение будет соединяться с базой данных только при наличии изменений. Запускаем приложение. Проводя тестирование, вы заметите, что если закрыть форму во время редактирования текущей записи (вызвать метод Update), внесенные в нее значения сохраняться не будут (рис. 12.38, А). После переключения фокуса ввода, например на новую запись, текущую запись можно сохранить, даже если она содержит не полностью заполненные поля (рис. 12.38, Б).


Рис. 12.38.  Готовое приложение "VisualAllSqlCommands".


А - закрытие формы при редактировании текущей записи, Б - закрытие формы после переключения фокуса ввода

Эту особенность можно преодолеть, изменив часть приложения, отвечающего за пользовательский интерфейс, например, вводя блокировку завершения работы на время ввода данных6) (см. приложение Change_Data).

В программном обеспечении к курсу вы найдете приложение VisualAll SqlCommands (Code\Glava6 VisualAllSqlCommands).

Сделаем точно такое же приложение, не используя визуальные средства Visual Studio .NET. Создайте новое приложение и назовите его "ProgrammAllSqlCommands". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение Fill. Подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В классе формы создаем строку подключения, объекты DataSet и DataAdapter:

string connectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; DataSet dsTourists; SqlDataAdapter dataAdapter;

В конструкторе формы создаем все объекты ADO .NET:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandText = "SELECT * FROM Туристы"; dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; //InsertCommand SqlCommand myInsertCommand = conn.CreateCommand(); myInsertCommand.CommandText = "INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (@Кодтуриста, @Фамилия, @Имя, @Отчество)"; myInsertCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myInsertCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myInsertCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myInsertCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.InsertCommand = myInsertCommand; //UpdateCommand SqlCommand myUpdateCommand = conn.CreateCommand(); myUpdateCommand.CommandText = "UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия, Имя = @Имя, Отчество = @Отчество WHERE (Кодтуриста = @Кодтуриста)"; myUpdateCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myUpdateCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myUpdateCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myUpdateCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.UpdateCommand = myUpdateCommand; //DeleteCommand SqlCommand myDeleteCommand = conn.CreateCommand(); myDeleteCommand.CommandText = "DELETE FROM Туристы WHERE (Кодтуриста = @Кодтуриста) OR (Фамилия = @Фамилия) OR (Имя = @Имя) OR (Отчество = @Отчество)"; myDeleteCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myDeleteCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myDeleteCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myDeleteCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.DeleteCommand = myDeleteCommand; }

Принципиально нового в этом коде ничего нет - набор Parameters объекта Command мы уже встречали в лекции 3. В методе Add последним параметром мы передаем значение столбца в объекте DataSet, который будет источником данных для данного параметра (рис. 12.39):


Рис. 12.39.  Конструктор метода Add набора Parameters объекта Command

Обработчик события Closing формы будет такой же, как и в предыдущем приложении (с учетом названий объектов):

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (dsTourists.HasChanges()) { dataAdapter.Update(dsTourists); } }

В программном обеспечении к курсу вы найдете приложение Programm AllSqlCommands (Code\Glava6\ProgrammAllSqlCommands).


Свойство MissingMappingAction


В рассмотренных выше примерах мы определяли названия полей в объекте DataSet для всех заголовков таблицы, извлекаемой из базы данных. Таблица в базе данных может измениться, например, в нее будут добавлены столбцы, которых нет в свойстве TableMappings. Что произойдет в этом случае? Объект DataAdapter имеет свойство MissingMappingAction, предназначенное для таких ситуаций. Его значение по умолчанию - Passthrough (рис. 12.10).


Рис. 12.10.  Свойство MissingMappingAction объекта DataAdapter в приложении VisualTableMappings

Для того чтобы разобраться с этим свойством, откроем приложение ProgrammTableMappings, закомментируем пару столбцов в определении TableMappings и добавим программно свойство MissingMappingAction:

... DataColumnMapping[] dcMappings = { new DataColumnMapping("CustomerID", "Номер_клиента"), //new DataColumnMapping("CompanyName", "Компания"), //new DataColumnMapping("ContactName", "Имя"), new DataColumnMapping("ContactTitle", "Должность"), new DataColumnMapping("Address", "Адрес"), new DataColumnMapping("City", "Город"), new DataColumnMapping("Region", "Регион"), new DataColumnMapping("PostalCode", "Индекс"), new DataColumnMapping("Country", "Страна"), new DataColumnMapping("Phone", "Телефон"), new DataColumnMapping("Fax", "Факс"), };

dtMapping.ColumnMappings.AddRange(dcMappings); dataAdapter.TableMappings.Add(dtMapping);

dataAdapter.MissingMappingAction = MissingMappingAction.Passthrough; ...

Запускаем приложение. Мы убрали столбцы "Компания" и "Имя" - при значении по умолчанию Passthrough свойства MissingMappingAction в DataSet помещаются поля с их оригинальными названиями (рис. 12.11):


Рис. 12.11.  Вид таблицы при значении Passthrough свойства Missing MappingAction

Изменим значение на Ignore:

dataAdapter.MissingMappingAction = MissingMappingAction.Ignore;

В результате будут игнорироваться столбцы, не указанные в свойстве TableMappings (рис. 12.12):


Рис. 12.12.  Вид таблицы при значении Ignore свойства Missing MappingAction

Последнее значение Error:

dataAdapter.MissingMappingAction = MissingMappingAction.Error;

В этом случае сгенерируется исключение, которое может быть перехвачено блоком обработки.



Свойство MissingSchemaAction


Свойство MissingMappingAction позволяет быстро конфигурировать поля извлекаемых данных при использовании свойства TableMappings. А как определять вид данных в других случаях, когда свойство TableMappings не задано? Мы знаем, что если объект DataSet вообще не содержит схемы, DataAdapter выведет ее на основании результатов выполнения запроса - на форме появятся все извлеченные поля. Также вполне возможно, что DataSet будет содержать схему, причем она будет отличаться от структуры извлекаемых данных. В этом случае следует применять свойство MissingSchemaAction объекта DataAdapter, которое может принимать следующие значения:

Add - к схеме объекта DataSet будут добавлены все извлекаемые столбцы из базы данных (значение по умолчанию);AddWithKey - к схеме объекта DataSet будут добавлены все извлекаемые столбцы из базы данных. Объект DataAdapter также попытается извлечь из базы данных информацию о ключах и определить первичные ключи заполняемых им объектов DataTable. Кроме того, описание полей, имеющихся в схеме объекта DataSet, может быть заменено на извлеченное из базы данных;Ignore - не входящие в схему объекта DataSet столбцы, извлекаемые из базы данных, игнорируются;Error - при несоответствии схемы объекта DataSet и структуры извлекаемых данных генерируется исключение.

Создайте новое приложение и назовите его "VisualMissingMapping Action". Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. Из окна Server Explorer перетаскиваем на форму таблицу Customers базы данных MS SQL "Northwind". В окне Toolbox переходим на вкладку Data и добавляем на форму объект DataSet. В появившемся окне Add DataSet выбираем Untyped dataset. Переходим в окно Properties добавленного DataSet, в свойстве Name вводим dsCustomers. Теперь нам предстоит создать схему, содержащую всего одно поле (столбец), которое есть в таблице "Customers" базы данных. Для того чтобы вспомнить названия столбцов, посмотрим на содержимое, которое будет извлекать DataAdapter.
Выделяем объект sqlDataAdapter1 и в окне Properties нажимаем на ссылку "Preview Data_" В появившемся окне Data Adapter Preview нажимаем кнопку "Fill Dataset" (рис. 12.13):


увеличить изображение
Рис. 12.13.  Окно Data Adapter Preview

Обратите внимание на определение размера извлекаемых данных - 37,1 Кб. Создадим поле, соответствующее столбцу CustomerID. Закрываем окно Data Adapter Preview, выделяем объект dsCustomers в окне Properties, в поле свойства Tables нажимаем на кнопку
(...). Добавляем объект DataTable (Name - "dtCustomers", TableName - "Customers"), в поле свойства Columns нажимаем на кнопку
(...). Добавляем объект DataColumn (рис. 12.14) (Name - "dcCustomerID", ColumnName - "CustomerID") - всего один, для вывода только одного поля из таблицы базы данных.


увеличить изображение
Рис. 12.14.  Объекты DataTable и DataColumn в схеме dsCustomers

В конструкторе формы заполняем объект DataSet и выводим данные на форму:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dsCustomers); dataGrid1.DataSource = dsCustomers; }

Запускаем приложение. Несмотря на наличие схемы объекта DataSet, на форму выводятся все данные (рис. 12.15):


Рис. 12.15.  Приложение VisualMissingMappingAction

Дело в том, что по умолчанию в DataSet добавляются столбцы из базы данных, которых нет в схеме - результат будет в точности такой же, если мы зададим явно свойство MissingSchemaAction со значением Add:

public Form1() { InitializeComponent(); sqlDataAdapter1.MissingSchemaAction = MissingSchemaAction.Add; sqlDataAdapter1.Fill(dsCustomers); dataGrid1.DataSource = dsCustomers; }

Изменим значение на Ignore:

sqlDataAdapter1.MissingSchemaAction = MissingSchemaAction.Ignore;

Теперь данными заполняется только одно поле, которое было определено в схеме (рис. 12.16):


Рис. 12.16.  Приложение VisualMissingMappingAction. Значение Ignore свойства MissingSchemaAction

Изменим значение на Error:

sqlDataAdapter1.MissingSchemaAction = MissingSchemaAction.Error;



Теперь при запуске появляется исключение - "Пропущен объект DataColumn "CompanyName в DataTable "Customers", соответствующий столбцу в источнике данных CompanyName (рис. 12.17):


Рис. 12.17.  Приложение VisualMissingMappingAction. Значение Error свойства MissingSchemaAction

Объект DataAdapter, установив связь с источником данных, попытался заполнить DataSet данными, но, не обнаружив второй столбец, сгенерировал исключение (после столбца CustomerID следует Company Name, см рис. 12.13). Это исключение может быть перехвачено блоком обработки.

Наконец, устанавливаем четвертое значение - AddWithKey:

sqlDataAdapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey;

На этот раз приложение заполнилось всеми данными, точно так же, как при значении Add. Какая между ними разница? Запустим приложение SQL Server Enterprise Manager, входящее в пакет Microsoft SQL Server 2000, и просмотрим структуру таблицы Customers в режиме дизайна (рис. 12.18):


Рис. 12.18.  Таблица "Customers" базы данных "Northwind"

Поле CustomerID имеет длину 5 символов и, поскольку оно является ключевым, не допускает пустых значений - в столбце Allow Nulls галочки нет. Переходим в код приложения VisualMissingMappingAction, ставим точку остановки напротив строки заполнения элемента dataGrid1 (рис. 12.19):


Рис. 12.19.  Точка остановки

Запускаем приложение. В любом месте кода щелкаем правой кнопкой мыши, в меню выбираем пункт "QuickWatch_". В появившемся окне вводим следующее выражение:

dsCustomers.Tables["Customers"].Columns

Нажимаем кнопку "Recalculate" (или просто клавишу Enter) - появляется группа, содержащая коллекцию Columns таблицы Customers объекта dsCustomers (рис. 12.20):


Рис. 12.20.  Окно QuickWatch. Коллекция Columns

Прокручиваем список, выбираем "List", затем [0]. Мы видим название поля - "CustomerID" и значения параметров: "AllowDBNull5)" - false, MaxLength - 5 (рис. 12.20a), что соответствует определению этого поля в таблице Customers самой базы данных (см.


рис. 12.18).


Рис. 12.20a.  Поле "CustomerID" в окне QuickWatch при значении "AddWithKey" свойства "MissingSchemaAction"

Таким образом, при значении AddWithKey свойства MissingSchema Action для описания добавляемого поля будут взяты значения параметров из базы данных. Закомментируем строку со значением AddWithKey, уберем комментарии со значения Add, запустим приложение и снова откроем окно QuickWatch. На этот раз в добавляемом столбце используются параметры, определенные при создании схемы, в частности, AllowDBNull и MaxLength(рис. 12.21):


увеличить изображение
Рис. 12.21.  Поле CustomerID в окне QuickWatch при значении Add свойства MissingSchemaAction

Можно заметить, что другие параметры, например AutoIncrement, Namespace, также остались без изменений.

В программном обеспечении к курсу вы найдете приложение Visual MissingMappingAction (Code\Glava6 VisualMissingMappingAction).

Сделаем теперь такое же приложение программно. Создайте новый Windows-проект и назовите его "ProgrammMissingSchemaAction". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В классе формы создаем строки commandText и connectionString:

string commandText = "SELECT * FROM Customers"; string connectionString = "workstation id=7EA2B2F6068D473;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=Northwind";

В конструкторе формы создаем подключение к базе данных, а также одно поле - dсCustomerID:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet dsCustomers = new DataSet(); DataTable dtCustomers = dsCustomers.Tables.Add("Customers"); DataColumn dсCustomerID = dtCustomers.Columns.Add("CustomerID", typeof(string)); dсCustomerID.Unique = true; dataAdapter.MissingSchemaAction = MissingSchemaAction.Add; //dataAdapter.MissingSchemaAction = MissingSchemaAction.Ignore; //dataAdapter.MissingSchemaAction = MissingSchemaAction.Error; //dataAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey; dataAdapter.Fill(dsCustomers, "Customers"); dataGrid1.DataSource = dsCustomers; }



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

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

... DataTable dtCustomers = dsCustomers.Tables.Add("Customers"); ... dataAdapter.Fill(dsCustomers, "Customers"); ...

Не будем указывать название таблицы при добавлении объекта dtCustomers в DataSet:

... DataTable dtCustomers = dsCustomers.Tables.Add(); ... dataAdapter.Fill(dsCustomers, "Customers"); ...

Запускаем приложение (при значении "Add" свойства Missing SchemaAction). Объект DataSet содержит теперь две таблицы - Table1 с программно определенной схемой и Customers с данными, извлеченными из базы (рис. 12.22):


Рис. 12.22.  Две таблицы в объекте DataSet - "Table1" и "Customers"

Подобный результат получаем, не указывая в методе Fill, какую таблицу заполнять:

... DataTable dtCustomers = dsCustomers.Tables.Add(); ... dataAdapter.Fill(dsCustomers, "Customers"); ...

Запускаем приложение. Таблица Customers соответствует программно определенной схеме, а Table - таблице базы данных (рис. 12.23):


Рис. 12.23.  Две таблицы в объекте DataSet - Customers и Table

При определении источника данных для объекта DataGrid в результате можно легко сделать ошибку, например, следующий фрагмент кода устанавливает значение Ignore свойства MissingSchemaAction, но в результате его выполнения просто выводится структура объекта DataSet (рис. 12.24):

... DataTable dtCustomers = dsCustomers.Tables.Add("Customers"); DataColumn dсCustomerID = dtCustomers.Columns.Add("CustomerID", typeof(string)); dсCustomerID.Unique = true; dataAdapter.MissingSchemaAction = MissingSchemaAction.Ignore; dataAdapter.Fill(dsCustomers); dataGrid1.DataSource = dsCustomers.Tables["Customers"].DefaultView; }


Рис. 12.24.  Вывод структуры объекта DataSet

Конечно, в таких простых приложениях легко найти ошибку, но это гораздо сложнее сделать в проектах, содержащих десятки таблиц.

В программном обеспечении к курсу вы найдете приложение Programm MissingSchemaAction (Code\Glava6 ProgrammMissingSchemaAction).


Свойство TableMappings. Окно QuickWatch


При создании базы данных в ней определяются названия столбцов. При выводе таблицы в элемент DataSet часто бывает нужно изменить эти названия. Самый простой способ это сделать - использовать SQL-запрос, содержащий псевдонимы. Скопируйте папку приложения FillMethod и переименуйте ее в "Psevdonim". Изменим строку commandText следующим образом3):

string commandText = "SELECT CustomerID AS Номер_клиента, CompanyName AS Компания, ContactName AS Имя, ContactTitle AS Должность, Address AS Адрес, City AS Город, Region AS Регион," + "PostalCode AS Индекс, Country AS Страна, Phone AS Телефон, Fax AS Факс FROM Customers";

Оператор AS представляет названия поля, например, CustomerID в виде псевдонима "Номер_клиента", который будет заголовком столбца в объекте DataSet. Изменим фрагмент кода в конструкторе формы:

dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView;

Запустив приложение, мы обнаруживаем, что заголовки столбцов появились с новыми названиями (рис. 12.3):


Рис. 12.3.  Применение псевдонимов

Подобный SQL-запрос действительно удобно применять при выводе информации только для чтения. Если же требуется определить структуру объекта DataSet не только для вывода, но и для изменений данных, использование псевдонимов может оказаться достаточно сложным. Надежный способ решить эту же задачу - задействовать свойство TableMappings объекта DataAdapter. Рассмотрим вначале визуальные средства студии для настройки свойства. Создайте новое приложение и назовите его VisualTableMappings. Из окна Server Explorer перетаскиваем на форму таблицу Customers базы данных MS SQL "NorthwindCS4)" (рис. 12.4):


Рис. 12.4.  Перемещение таблицы "Customers" на форму

Выбираем появившейся элемент sqlDataAdapter1, переходим в окно Properties, в поле свойства TableMappings нажимаем на кнопку

(...) (рис. 12.5, А). В появившемся окне Table Mappings называем таблицу в DataSet "Клиенты", а затем переименовываем поля следующим образом (рис. 12.5, Б):


Source ColumnsDataset Columns
СustomerIDНомер_клиента
CompanyNameКомпания
ContactNameИмя
ContactTitleДолжность
AddressАдрес
CityГород
RegionРегион
PostalCodeИндекс
CountryСтрана
PhoneТелефон
FaxФакс

увеличить изображение
Рис. 12.5.  Свойство TableMappings. А - переход к настройке, Б - задание заголовков столбцов

Завершив настройку, нажимаем кнопку OK. Выделяем объект sqlDataAdapter1, в окне Properties щелкаем на ссылку "Generate DataSet". В появившемся окне вводим название "dsCustomers". Перетаскиваем на форму элемент DataGrid и его свойству Dock устанавливаем значение "Fill". В свойстве DataSource выбираем из выпадающего списка значение "dsCustomers1.Клиенты" - в результате на элементе появляются заголовки столбцов. Переходим в код формы, подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В конструкторе формы заполняем объект DataSet данными:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dsCustomers1); }

Запущенное приложение будет выглядеть так же, как и в случае применения псевдонимов (см. рис. 12.3).

Посмотрим, какие поля будут находиться в DataSet во время выполнения программы, после того как мы применили свойство TableMappings. Поставим точку остановки в строке заполнения данными (рис. 12.6):


Рис. 12.6.  Точка остановки. Для ее установки или удаления дважды щелкаем на поле

Запускаем приложение. Программа прервет свое выполнение на точке остановки. Установив курсор на объект dsCustomers1, вызываем контекстное меню, в котором выбираем пункт "QuickWatch_" (рис. 12.7):


увеличить изображение
Рис. 12.7.  Вызов окна "QuickWatch:" во время выполнения программы

Диалоговое окно QuickWatch предназначено для быстрого расчета значения переменной (рис. 12.8). В столбцах Name, Value и Type этого окна отображаются сведения только об одной переменной, из него переменную можно добавить в окно Watch, а затем изменить ее значение, введя новое значение в столбец Value (рис. 12.9).




увеличить изображение
Рис. 12.8.  Окно QuickWatch. Добавление столбца "Адрес"


Рис. 12.9.  Окно Watch. Изменение названия в поле "Value"

Оставим, однако, названия полей, определенные в окне Table Mappings.

В программном обеспечении к курсу вы найдете приложения Psevdonim и VisualTableMappings (Code\Glava6).

Перейдем к программному определению свойства TableMappings. Создайте новое приложение и назовите его ProgrammTableMappings. Перетаскиваем на форму элемент DataGrid, его свойству Dock устанавливаем значение Fill. В классе формы определяем строки commandText и connectionString:

string commandText = "SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region," + " PostalCode, Country, Phone, Fax FROM Customers"; string connectionString = "workstation id=7EA2B2F6068D473;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=NorthwindCS";

Подключаем пространства имен для работы c базой данных и классом DataTableMapping:

using System.Data.SqlClient; using System.Data.Common;

В конструкторе формы создаем объекты SqlConnection, SqlCommand и SqlDataAdapter:

SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand;

Создаем экземпляр dtMapping класса DataTableMapping, устанавливаем соответствие между таблицами "Table" базы данных и "DataSetTable Customers" объекта DataSet:

DataTableMapping dtMapping = new DataTableMapping("Table", "DataSetTableCustomers");

Почему таблица Customers, которую мы извлекаем, здесь называется "Table"? Дело в том, что у объекта DataAdapter нет возможности определить, как называется таблица в базе, и он подставляет предположительное название "Table".


Второй параметр, DataSetTableCustomers, - это произвольно задаваемое название таблице в DataSet.

Создаем экземпляр dcMappings класса DataColumnMapping, в котором определяем названия полей в DataSet:

DataColumnMapping[] dcMappings = { new DataColumnMapping("CustomerID", "Номер_клиента"), new DataColumnMapping("CompanyName", "Компания"), new DataColumnMapping("ContactName", "Имя"), new DataColumnMapping("ContactTitle", "Должность"), new DataColumnMapping("Address", "Адрес"), new DataColumnMapping("City", "Город"), new DataColumnMapping("Region", "Регион"), new DataColumnMapping("PostalCode", "Индекс"), new DataColumnMapping("Country", "Страна"), new DataColumnMapping("Phone", "Телефон"), new DataColumnMapping("Fax", "Факс"), };

Добавляем созданный экземпляр dcMappings в коллекцию ColumnMappings объекта dtMapping:

dtMapping.ColumnMappings.AddRange(dcMappings);

Готовый объект dtMapping передаем в DataAdapter, используя его свойство TableMappings:

dataAdapter.TableMappings.Add(dtMapping);

Создаем и заполняем объект DataSet данными:

DataSet dsCustomers = new DataSet(); dataAdapter.Fill(dsCustomers); dataGrid1.DataSource = dsCustomers.Tables["DataSetTableCustomers"].DefaultView;

Запускаем приложение. Его вид будет в точности такой же, как и в случае применения псевдонимов (см. рис. 12.3).

В программном обеспечении к курсу вы найдете приложение Programm TableMappings (Code\Glava6 ProgrammTableMappings).


Метод GetChanges объекта DataSet


Объекты DataSet и DataTable предоставляют перегруженный метод GetChanges, конструктор которого имеет следующий вид (рис. 13.13):


увеличить изображение
Рис. 13.13.  Метод GetChanges. A - конструктор для объекта DataSet, Б - конструктор для объекта DataTable

Метод GetChanges возвращает новый объект DataSet или DataTable cо структурой исходного объекта, но содержащий только измененные записи из исходного объекта. На рис. 13.13 исходными объектами являются myDataSet и myDataTable, а новыми, созданными методом GetChanges - newDataSet и newDataTable. Применяя в качестве параметра значение перечисления DataRowState, можно получить записи с конкретным состоянием, например, только удаленные (Deleted) или добавленные (Added).

При передаче изменений в базу данных отправка объекта DataSet, полученного в результате вызова метода GetChanges, позволяет уменьшить объем трафика. В самом деле, отправка только внесенных изменений при прочих равных условиях займет меньше ресурсов, чем отправка полного объекта DataSet. Это становится особенно важным при работе с большими объемами данных или значительным числом подключений.

Метод GetChanges также применяется в качестве своеобразного сигнального флага, сообщающего, были ли затронуты данные. Создайте новое Windows-приложение и назовите его "GetChangesMethod". С помощью визуальных средств студии4) настраиваем подключение к базе данных Microsoft SQL "BDTur_firm2" для извлечения таблицы "Туристы". Названия объектов ADO .NET оставляем по умолчанию. В конструкторе формы заполняем данными объект DataSet11 и затем выводим их в элемент DataGrid:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dataSet11); dataGrid1.DataSource = dataSet11.Tables[0].DefaultView; }

В обработчике события Closing формы проверяем изменения в объекте DataSet11 при помощи метода GetChanges и в случае их наличия выводим диалоговое окно:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { DataSet ds = dataSet11.GetChanges(); if(ds == null) return; if(MessageBox.Show("Вы хотите сохранить изменения в базе данных?", "Завершение работы", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) sqlDataAdapter1.Update(ds); }

Запускаем приложение. При закрытии формы после внесения изменений появляется диалоговое окно, в котором предлагается изменения сохранить (рис. 13.14):


Рис. 13.14.  Готовое приложение GetChangesMethod

В программном обеспечении к курсу вы найдете приложение GetChanges Method (Code\Glava6\ GetChangesMethod).



Метод Merge объекта DataSet


Метод Merge объекта DataSet предназначен для объединения имеющегося содержимого объекта DataSet с содержимым другого объекта DataSet, DataTable или набором строк DataRow:

myDataSet.Merge(newDataSet)

Здесь myDataSet - исходный объект DataSet, newDataSet - новый или другой объект. Представим себе такую ситуацию: у нас имеется объект DataSet, содержащий таблицу "Туристы". Другой объект DataSet содержит таблицу "Туры". Каждая из этих таблиц в своем объекте DataSet представлена соответствующими объектами DataTable. После вызова метода Merge основного объекта DataSet он будет содержать таблицу со своими исходными столбцами и новыми. Поскольку записи в обеих таблицах независимы, пустые ячейки будут заполнены значениями null (рис. 13.15):


увеличить изображение
Рис. 13.15.  Метод Merge. Объединение двух таблиц с различной структурой

На практике более интересен случай объединения таблиц с одинаковой структурой. При их объединении будет происходить формирование новой таблицы с распределением записей согласно значениям первичного ключа (рис. 13.16):


увеличить изображение
Рис. 13.16.  Метод Merge. Объединение двух таблиц с одинаковой структурой

Клиентское приложение, как правило, предоставляет несколько режимов изменения данных. После завершения редактирования возникает проблема объединения всех исправлений в один объект DataSet для последующей их отправки в базу данных. Создание нескольких объектов DataSet с идентичной структурой и применение метода Merge - оптимальный способ решения этой задачи.

Создайте новое Windows-приложение и назовите его "MergeMethod". При помощи визуальных средств студии настраиваем подключение к базе данных Microsoft SQL "BDTur_firm2" для извлечения таблицы "Туристы". Названия объектов ADO .NET оставляем по умолчанию. Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение Fill. Для отключения доступности элемента его свойству Enabled устанавливаем значение "False".
Переходим в окно Solution Explorer, щелкаем правой кнопкой на имени проекта MergeMethod, в появившемся меню переходим к пункту "Add \ Add New Item". В появившемся окне выбираем новый шаблон "Windows Form" и нажимаем кнопку "Open". Устанавливаем следующие значения свойств новой формы:

Form2, свойствоЗначение
FormBorderStyleFixedSingle
MaximizeBoxFalse
MinimizeBoxFalse
Size250; 220
TextДобавление туриста
Подключаем пространство имен Data:

using System.Data;

В классе формы создаем объект DataTable:

public DataTable dtNewTourist = null;

Открываем проект Change_Data (мы работали с ним в этой лекции), выделяем четыре текстовых поля и четыре соответствующих надписи, копируем их, затем размещаем их на форме Form2 в текущем проекте. Добавляем на форму кнопку, в свойстве Name вводим "btnOK", в свойстве Text - "&OK". В обработчике этой кнопки создаем запись DataRow, в которую будут помещаться значения, вводимые в текстовые поля:

private void btnOK_Click(object sender, System.EventArgs e) { DataRow drNewTourist = dtNewTourist.NewRow(); drNewTourist["Кодтуриста"] = txtID.Text; drNewTourist["Фамилия"] = txtLastName.Text; drNewTourist["Имя"] = txtFirstName.Text; drNewTourist["Отчество"] = txtMiddleName.Text; dtNewTourist.Rows.Add(drNewTourist); this.Close(); }

Переключаемся в режим дизайна главной формы. Добавляем на форму элемент управления Panel, его свойству Dock устанавливаем значение "Bottom". Из окна Toolbox перетаскиваем на панель кнопку, в свойстве Name вводим "btnAdd", в свойстве Text - "&Добавить". В обработчике этой кнопки вызываем вторую форму и в случае подтверждения диалога объединяем изменения, вызывая метод Merge:

private void btnAdd_Click(object sender, System.EventArgs e) { Form2 f2 = new Form2(); //Определяем структуру объекта dtNewTourist f2.dtNewTourist = dataSet11.Tables["Туристы"]; if(f2.ShowDialog() == DialogResult.OK) { //Создаем объект dtForMerge для передачи // конструктору метода Merge DataTable dtForMerge = f2.dtNewTourist; dataSet11.Merge(dtForMerge); } }



Здесь мы применили четвертый из семи возможных конструкторов метода Merge (рис. 13.17):


Рис. 13.17.  Конструктор метода Merge

В конструкторе формы заполняем объект DataSet и определяем источник данных для элемента DataGrid:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dataSet11); dataGrid1.DataSource = dataSet11.Tables[0].DefaultView; }

Наконец, в обработчике события Closing формы передаем изменения в базу данных:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { sqlDataAdapter1.Update(dataSet11); }

Запускаем приложение. При нажатии на кнопку "Добавить" появляется окно "Добавление туриста", в котором вводятся значения полей. После закрытия этого окна новая запись появляется в таблице (рис. 13.18):


Рис. 13.18.  Готовое приложение MergeMethod

В программном обеспечении к курсу вы найдете приложение Merge Method (Code\Glava6\MergeMethod).


Объект Command Builder


Самым сложным во всех рассмотренных примерах передачи изменений в базу данных является описание параметров. В самом деле, мы должны указывать название, тип данных, а также значение передаваемого параметра - поскольку их может быть много, понятно, что наибольшую часть кода будет занимать именно создание набора Parameters. С другой стороны, данные, которые нуждаются в обновлении, каким-то способом уже прочитаны из базы и выведены на форму. Следовательно, сам факт их вывода указывает на успешное взаимодействие с базой. Возникает логичный вопрос: а можно ли вообще избежать описания параметров для передачи изменений, раз уж данные прочитаны? Ответ - можно, для этого мы должны применять объект CommandBuilder, который предоставит возможность сделать это, если выполняются следующие условия:

запрос возвращает данные только из одной таблицы;таблица содержит первичный ключ;возвращаемый запрос содержит первичный ключ.

В коде объект CommandBuilder связывается с уже имеющимся экземпляром dataAdapter так:

SqlCommandBuilder commandBuilder = new SqlCommandBuilder(dataAdapter);

Создайте новое приложение и назовите его "CommandBuilder". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой данных:

using System.Data.SqlClient;

В классе формы создаем строку подключения, а также объекты DataSet и dataAdapter:

string connectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; DataSet dsTourists; SqlDataAdapter dataAdapter;

В классе формы мы создаем самый обычный набор объектов ADO .NET и добавляем одной строкой объект CommandBuilder:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandText = "SELECT * FROM Туристы"; dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; SqlCommandBuilder commandBuilder = new SqlCommandBuilder(dataAdapter); }


В обработчике события Closing формы передаем изменения в базу данных:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { if (dsTourists.HasChanges()) { dataAdapter.Update(dsTourists); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } }

Запускаем приложение. Это кажется странным, но мы снова можем вставлять, изменять и удалять записи!

В программном обеспечении к курсу вы найдете приложение Command Builder (Code\Glava6\CommandBuilder).

Объект CommandBuilder применим для обновления данных, полученных в результате извлечения хранимой процедуры. Создайте новое приложение и назовите его "CommandBuilderSP". Переходим на вкладку "ServerExplorer" в узле базы данных BDTur_firm2 и создаем новую хранимую процедуру:

CREATE PROCEDURE mySelectSP

AS SELECT * FROM Туристы RETURN

Конструктор формы будет иметь следующий вид:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandType = CommandType.StoredProcedure; mySelectCommand.CommandText = "[mySelectSP]"; dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; SqlCommandBuilder commandBuilder = new SqlCommandBuilder(dataAdapter); }

Все остальные части приложения будут в точности такими же, как и в CommandBuilder. Конечно, в этом примере можно обойтись и без хранимой процедуры, поскольку SQL-конструкция в ней предельно проста. Однако для нас важна методика применения объекта CommandBuilder - в случае даже самых изощренных запросов на выборку она будет той же самой.

В программном обеспечении к курсу вы найдете приложение Command BuilderSP (Code\Glava6\CommandBuilderSP).

Для работы с базой данных Microsoft Access применяется объект OleDbCommandBuilder:

OleDbCommandBuilder commandBuilder1 = new OleDbCommandBuilder(dataAdapter);

Несмотря на простоту использования объекта CommandBuilder, этот способ передачи изменений не является максимально производительным. Один из его главных недостатков также - невозможность передачи обновлений с помощью хранимых процедур типа INSERT, UPDATE и DELETE.


Обновление связанных таблиц


В лекции 1, при создании схемы базы данных BDTur_firm.mdb, мы определили вложенную группу "Туры" 1 - °"Сезоны" 1 - °"Путевки" 1 - °"Оплата". Далее, при экспорте этой базы в формат Microsoft SQL получился набор отдельных таблиц - мастер преобразований не перенес связи между таблицами. Рассмотрим вывод на форму и обновление этих таблиц, связанных между собой в типизированном объекте DataSet. Программное определение всех полей и параметров заняло бы слишком много места, поэтому на этот раз будем использовать визуальные средства студии. При необходимости вы сможете самостоятельно переделать приложение - изученный материал настоящей лекции позволит вам это сделать.

Запускаем SQL Server Enterprise Manager, раскрываем узел базы BDTur_firm2 и в режиме дизайна таблиц задаем ключевые поля (рис. 13.9):


Рис. 13.9.  Задание ключевых полей в SQL Server Enterprise Manager

Создайте новое Windows-приложение и назовите его "MultiFilling AndUpdating". Свойству "Size" формы устанавливаем значение "500;300". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Добавляем на форму элемент Panel, его свойству Dock устанавливаем значение "Bottom". На панели размещаем четыре элемента RadioButton со следующими свойствами:

radioButton1, свойствоЗначение
NamerbTours
Location26; 16
TagТуры
TextТуры
radioButton2, свойствоЗначение
NamerbSeasons
Location138; 16
TagСезоны
TextСезоны
radioButton3, свойствоЗначение
NamerbPasses
Location250; 16
TagПутевки
TextПутевки
radioButton4, свойствоЗначение
NamerbPayments
Location362; 16
TagОплата
TextОплата

Свойство "Tag" элементов RadioButton будет использоваться для задания выводимого содержимого при выборе элементов. Переходим на вкладку "Server Explorer", настраиваем подключение к базе Microsoft SQL "BDTur_firm2", из узла "Tables" перетаскиваем на форму таблицы "Туры", "Сезоны", "Путевки", "Оплата".
Соответствующие объекты DataAdapter называем "daTours", "daSeasons", "daPasses" и "daPayments". Выделив любой из объектов DataAdapter, в окне его свойств нажимаем на ссылку "Generate Dataset". В появившемся окне Generate Dataset вводим название "dsBDTur_firm" и отмечаем галочками все таблицы - они будут находиться в объекте DataSet (рис. 13.10):


Рис. 13.10.  Создание объекта DataSet

В результате получился типизированный объект DataSet. Переходим в окно "Solution Explorer", дважды щелкаем на файле dsBDTur_firm.xsd. В режиме дизайна XSD-схемы связываем таблицы по ключевым полям, оставляя названия связей по умолчанию2). Готовая схема будет иметь следующий вид (рис. 13.11):


Рис. 13.11.  Готовая XSD-схема объекта dsBDTur_firm

Переходим в код формы. В конструкторе заполняем объект DataSet:

public Form1() { InitializeComponent(); //Отключаем ограничения на время заполнения объекта DataSet dsBDTur_firm1.EnforceConstraints = false; sqlConnection1.Open(); //Заполняем объект DataSet daTours.Fill(dsBDTur_firm1); daSeasons.Fill(dsBDTur_firm1); daPasses.Fill(dsBDTur_firm1); daPayments.Fill(dsBDTur_firm1); sqlConnection1.Close(); //Включаем ограничения dsBDTur_firm1.EnforceConstraints = true; //Привязываем все обработчики всех элементов RadioButton к одному rbTours.CheckedChanged += new EventHandler(rbTours_CheckedChanged); rbSeasons.CheckedChanged += new EventHandler(rbTours_CheckedChanged); rbPasses.CheckedChanged += new EventHandler(rbTours_CheckedChanged); rbPayments.CheckedChanged += new EventHandler(rbTours_CheckedChanged); }

Обратите внимание на порядок заполнения DataSet. Вначале поступают данные из родительских таблиц, затем из дочерних. Это позволяет не нарушать целостность записей. Создаем метод rbTours_CheckedChanged, в котором реализуем переключатель содержимого объекта DataGrid:

private void rbTours_CheckedChanged(object sender, EventArgs e) { RadioButton rb = (RadioButton)sender; if(!rb.Checked) return; dataGrid1.DataSource = dsBDTur_firm1; dataGrid1.DataMember = rb.Tag.ToString(); }



В обработчике события Closing формы передаем изменения в базу данных:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { sqlConnection1.Open(); daTours.Update(dsBDTur_firm1); daSeasons.Update(dsBDTur_firm1); daPasses.Update(dsBDTur_firm1); daPayments.Update(dsBDTur_firm1); } catch(Exception ex) { MessageBox.Show(ex.ToString()); } finally { sqlConnection1.Close(); } }

Здесь снова вначале передаются изменения из родительских таблиц, затем из дочерних. В готовом приложении можно вставлять, изменять и удалять записи (рис. 13.12):


увеличить изображение
Рис. 13.12.  Готовое приложение MultiFillingAndUpdating

В программном обеспечении к курсу вы найдете приложение MultiFilling AndUpdating (Code\Glava6\MultiFillingAndUpdating).

При реализации обновления связанных таблиц программным образом следует передавать изменения в особом порядке3). Обычным сценарием является добавление родительских и относящихся к ним дочерних записей в набор данных, - например, запись о новом клиенте и одна или две относящиеся к ней записи о заказах. Если сам набор данных заставляет использовать правила реляционной целостности, возникнут ошибки при отсылке дочерних записей в базу данных до создания родительских записей.

И наоборот, при удалении связанных записей из набора данных обычно необходимо отправлять обновления в обратном порядке: сначала дочерние таблицы, а потом родительские таблицы. В противном случае в наборе данных может возникнуть ошибка, так как правила ссылочной целостности не дадут удалить родительскую запись, если существуют дочерние записи.

Существует следующие правила отсылки обновлений в таблицы:

Дочерняя таблица: удаление записей.Родительская таблица: вставка, обновление и удаление записей.Дочерняя таблица: вставка и обновление записей.


Обработка исключений


В процессе передачи изменений в базу данных могут возникать многочисленные исключения. Объекты DataSet, DataTable и DataRow имеют свойство HasErrors, позволяющее обрабатывать некоторые из них. Скопируйте папку приложения GetChangesMethod и назовите ее "Exceptions". Изменим обработчик события Closing формы следующим образом:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { sqlDataAdapter1.Update(dataSet11); } catch(Exception ex) { if(dataSet11.HasErrors) { foreach( DataTable myTable in dataSet11.Tables) { if(myTable.HasErrors) { foreach(DataRow myRow in myTable.Rows) { if(myRow.HasErrors) { MessageBox.Show("Ошибка в записи #: " + myRow["Кодтуриста"], myRow.RowError); foreach(DataColumn myColumn in myRow.GetColumnsInError()) { MessageBox.Show(myColumn.ColumnName, " - в этом столбце ошибка"); } myRow.ClearErrors(); myRow.RejectChanges(); } } } } } } }

Здесь мы пробегаемся по каждому объекту6) , входящему в набор DataTable, DataRow или DataColumn.

Метод ClearErrors удаляет все ошибки из объекта myRow, а метод RejectChanges производит откат всех изменений. Запускаем приложение. Переходим в SQL Server Enterprise Manager, в режиме дизайна таблицы "Туристы" изменим название поля "Фамилия". При закрытии формы и попытке внести изменения появляется диагностическое сообщение (рис. 13.22, А):


Рис. 13.22.  Обработка исключений

Закроем приложение, восстановим название поля - правильное название нам также нужно для получения данных. Запустим его снова, изменим название таблицы. В этом случае появляется другое сообщение (рис. 13.22, Б).

В программном обеспечении к курсу вы найдете приложение Exceptions (Code\Glava6\Exceptions).

  1)

  Если вы хорошо знаете Microsoft Access, вы можете самостоятельно создать эти запросы в режиме конструктора, выбирая их соответствующие типы.

  2)

  Если вы забыли, как это делается, вернитесь к пятой Главе.

  3)

  Этот фрагмент является переводом статьи "Database Updates from Datasets" из MSDN (заголовок раздела "Updating Related Tables").

  4)

  Просто перейдите в окно "Server Explorer" и перетащите на форму таблицу из узла базы данных имеющегося подключения, затем сгенерируйте объект DataSet.

  5)

  Для последующего сравнения будет более удобным установить цвет шрифта обоих документов черным.

  6)

  Разумеется, для отображения одной таблицы "Туристы" у нас будет всего один объект DataTable.

в свойстве Name вводим для


В окне Properties в свойстве Name вводим для него значение "mySelectCommand", а в свойстве Connection выбираем имеющееся соединение "sqlConnection1". В поле свойства CommandText нажимаем на кнопку
(...), в запустившемся построителе выражений настраиваем выборку всех записей из таблицы "Туристы". В результате всех проделанных действий окна Properties четырех объектов Command принимают следующий вид (рис. 13.1):


увеличить изображение
Рис. 13.1.  Свойства объектов Command

В окне Toolbox переходим на вкладку Data, перетаскиваем на форму объект SqlDataAdapter. В появившемся мастере Data Adapter Configuration Wizard нажимаем кнопку "Cancel". В окне Properties объекта DataAdapter устанавливаем значения свойств "DeleteCommand", "InsertCommand", "SelectCommand" и "UpdateCommand", выбирая из выпадающего списка названия соответствующих объектов (см. рис. 12.37). Нажимаем на ссылку "Generate Dataset". В появившемся окне вводим название объекта "dsTourists". Добавляем на форму элемент DataGrid, его свойству Dock устанавливаем значение "Fill". В конструкторе формы заполняем объект DataSet и устанавливаем источник данных для элемента DataGrid:

public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dsTourists1); dataGrid1.DataSource = dsTourists1.Tables[0].DefaultView; }

В обработчике события Closing формы вызываем метод Update объекта DataAdapter:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { if (dsTourists1.HasChanges()) { sqlDataAdapter1.Update(dsTourists1); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } }

Запускаем приложение. При попытке ввести новую запись и сохранить изменения (закрыть форму) появляется сообщение об ошибке (рис. 13.2):


Рис. 13.2.  Приложение VisualUpdateWithSP. Сообщение об ошибке

Дело в том, что среда сама не определяет источник данных для параметров хранимых процедур.

Применение хранимых процедур - наиболее надежный и безопасный способ взаимодействия с базой данных. Создадим хранимые процедуры для передачи изменений в базу Microsoft SQL Server BDTur_firm2. Запускаем Visual Studio .NET, переходим на вкладку "Server Explorer", щелкаем правой кнопкой на узле "Stored Procedures" и в появившемся меню выбираем "New Stored Procedure". Вводим текст процедуры типа INSERT:

CREATE PROCEDURE myInsertSP

( @Кодтуриста int, @Фамилия NVarChar (50), @Имя NVarChar (50), @Отчество NVarChar (50) )

AS INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (@Кодтуриста, @Фамилия, @Имя, @Отчество)

RETURN

Сохраняем процедуру - ее заголовок изменился на ALTER PROCEDURE. Это означает, что она записалась в базу данных, и текущее окно можно закрыть. Создаем процедуру типа UPDATE:

CREATE PROCEDURE myUpdateSP ( @Кодтуриста int, @Фамилия NVarChar (50), @Имя NVarChar (50), @Отчество NVarChar (50) ) AS UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия, Имя = @Имя, Отчество = @Отчество WHERE (Кодтуриста = @Кодтуриста) RETURN

И, наконец, создаем процедуру типа DELETE:

CREATE PROCEDURE myDeleteSP ( @Кодтуриста int, @Фамилия NVarChar (50), @Имя NVarChar (50), @Отчество NVarChar (50) ) AS DELETE FROM Туристы WHERE (Кодтуриста = @Кодтуриста) OR (Фамилия = @Фамилия) OR (Имя = @Имя) OR (Отчество = @Отчество)

RETURN

Содержимое SQL-запросов взято из приложения VisualAllSql Commands. Теперь у нас есть хранимые процедуры, которые мы можем вызывать. Создайте новое Windows-приложение и назовите его "Visual UpdateWithSP". Снова переходим на вкладку "Server Explorer", перетаскиваем созданные процедуры на форму. На панели компонент появляется подключение sqlConnection1 и объекты Command, в свойстве Name которых мы вводим значения "myInsertCommand", "myUpdateCommand" и "myDeleteCommand". Для извлечения записей из базы данных необходим еще один объект Command. В окне Toolbox переходим на вкладку Data, перетаскиваем на форму объект SqlCommand.


В лекции 3 мы вызывали запрос файла базы данных MS Access из внешнего приложения. Создадим теперь запросы для добавления, изменения и удаления записей. Скопируйте файл BDTur_firm2.mdb из лекции 3, назовите его "BDTur_firm3.mdb". Открываем базу, переходим на вкладку "Запросы" и нажимаем дважды на заголовок "Создание запроса в режиме конструктора". Закрываем появившееся окно1) "Добавление таблицы", в главном меню выбираем "Вид \ Режим SQL". В появившееся текстовое поле вводим текст SQL-конструкции:

INSERT INTO Туристы ( Фамилия, Имя, Отчество ) VALUES (@Фамилия, @Имя, @Отчество);

Сохраняем запрос, называя его "myInsertSP". Текст запроса взят из приложения "VisualAllOleDbCommands", и Microsoft Access корректно сохранил его. Однако, если снова открыть "myInsertSP" в режиме конструктора, мы заметим, что его вид изменился (рис. 13.4):

INSERT INTO Туристы ( Фамилия, Имя, Отчество ) VALUES ([@Фамилия], [@Имя], [@Отчество]);


Рис. 13.4.  Запрос myInsertSP в режиме конструктора

Дело в том, что программа незначительным образом изменяет синтаксис SQL-конструкций, приводя его к своему внутреннему формату. Но сам запрос должен быть верным. Попытка сохранить некорректный запрос приводит к ошибке (рис. 13.5):

INSERT Туристы ( Фамилия, Имя, Отчество ) VALUES ([@Фамилия], [@Имя], [@Отчество]);


Рис. 13.5.  Сообщение об ошибке

Аналогичным образом создаем запрос на обновление myUpdateSP:

UPDATE Туристы SET Имя = @Имя, Отчество = @Отчество, Фамилия = @Фамилия WHERE (Кодтуриста = @Кодтуриста);

Последний запрос myDeleteSP на удаление записей:

DELETE * FROM Туристы WHERE (Фамилия=@Фамилия) Or (Имя=@Имя) Or (Отчество=@Отчество);

В результате в окне базы данных будет четыре запроса (с учетом ранее созданного "Сортировка_туристов"), каждый из них будет отмечен значком, соответствующим его типу (рис. 13.6):


Рис. 13.6.  Окно базы данных BDTur_firm3.mdb после создания всех запросов




Переключаемся в режим дизайна, выделяем объект myInsertCommand, в окне Properties нажимаем на кнопку
(...) в поле свойства Parameters. В редакторе SqlParameter Collection Editor для параметров "@Кодтуриста", "@Имя", "@Фамилия", "@Отчество" в свойстве SourceColumn определяем одноименные названия полей (рис. 13.3):


увеличить изображение
Рис. 13.3.  Определение свойства SourceColumn

Свойство SourceColumn этих же самых параметров необходимо определить для объектов myUpdateCommand и myDeleteCommand.

Снова запускаем приложение. Теперь мы можем добавлять, изменять и удалять записи.

В программном обеспечении к курсу вы найдете приложение Visual UpdateWithSP (Code\Glava6\ VisualUpdateWithSP).

Приступим к созданию приложения без применения визуальных средств студии для работы с объектами Command. Создайте новый Windows-проект и назовите его "ProgrammUpdateWithSP". Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:

using System.Data.SqlClient;

В классе формы создаем строку подключения, объекты DataSet и DataAdapter:

string connectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; DataSet dsTourists; SqlDataAdapter dataAdapter;

В конструкторе формы создаем все объекты ADO .NET:

public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandText = "SELECT * FROM Туристы"; dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; //InsertCommand SqlCommand myInsertCommand = conn.CreateCommand(); myInsertCommand.CommandType = CommandType.StoredProcedure; myInsertCommand.CommandText = "[myInsertSP]"; myInsertCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myInsertCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myInsertCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myInsertCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.InsertCommand = myInsertCommand; //UpdateCommand SqlCommand myUpdateCommand = conn.CreateCommand(); myUpdateCommand.CommandType = CommandType.StoredProcedure; myUpdateCommand.CommandText = "[myUpdateSP]"; myUpdateCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myUpdateCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myUpdateCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myUpdateCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.UpdateCommand = myUpdateCommand; //DeleteCommand SqlCommand myDeleteCommand = conn.CreateCommand(); myDeleteCommand.CommandType = CommandType.StoredProcedure; myDeleteCommand.CommandText = "[myDeleteSP]"; myDeleteCommand.Parameters.Add("@Кодтуриста", SqlDbType.Int, 4, "Кодтуриста"); myDeleteCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50, "Фамилия"); myDeleteCommand.Parameters.Add("@Имя", SqlDbType.NVarChar, 50, "Имя"); myDeleteCommand.Parameters.Add("@Отчество", SqlDbType.NVarChar, 50, "Отчество"); dataAdapter.DeleteCommand = myDeleteCommand; }

В обработчике события Closing формы вызываем метод Update объекта DataAdapter:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { if (dsTourists.HasChanges()) { dataAdapter.Update(dsTourists); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } }

В программном обеспечении к курсу вы найдете приложение Programm UpdateWithSP (Code\Glava6\ProgrammUpdateWithSP).

В лекции 3 при помощи мастера SQL Server Enterprise Manager мы создавали хранимые процедуры "insert_Туристы_1", "update_Туристы_1" и "delete_Туристы_1". Попробуйте самостоятельно разработать приложение, которое будет использовать эти процедуры для вставки, изменения и удаления записей.



Закрываем файл базы данных. Создайте новое приложение и назовите его "StoredProcedures_in_Access". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:

using System.Data.OleDb;

В классе формы создаем строку connectionString, а также объекты DataSet и DataAdapter:

string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0"";Data Source=""D:\Uchebnik\Code\Glava6\BDTur_firm3.mdb" ";User ID=Admin;Jet OLEDB:Encrypt Database=False"; DataSet dsTourists; OleDbDataAdapter dataAdapter;

В конструкторе формы создаем все объекты ADO .NET:

public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; OleDbCommand mySelectCommand = conn.CreateCommand(); mySelectCommand.CommandText = "SELECT * FROM Туристы"; dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = mySelectCommand; dsTourists = new DataSet(); dataAdapter.Fill(dsTourists); dataGrid1.DataSource = dsTourists.Tables[0].DefaultView; //InsertCommand OleDbCommand myInsertCommand = conn.CreateCommand(); myInsertCommand.CommandType = CommandType.StoredProcedure; myInsertCommand.CommandText = "[myInsertSP]"; myInsertCommand.Parameters.Add("@Фамилия", OleDbType.VarWChar, 50, "Фамилия"); myInsertCommand.Parameters.Add("@Имя", OleDbType.VarWChar, 50, "Имя"); myInsertCommand.Parameters.Add("@Отчество", OleDbType.VarWChar, 50, "Отчество"); dataAdapter.InsertCommand = myInsertCommand;

//UpdateCommand OleDbCommand myUpdateCommand = conn.CreateCommand(); myUpdateCommand.CommandType = CommandType.StoredProcedure; myUpdateCommand.CommandText = "[myUpdateSP]"; myUpdateCommand.Parameters.Add("@Имя", System.Data.OleDb.OleDbType.VarWChar, 50, "Имя"); myUpdateCommand.Parameters.Add("@Отчество", System.Data.OleDb.OleDbType.VarWChar, 50, "Отчество"); myUpdateCommand.Parameters.Add("@Фамилия", System.Data.OleDb.OleDbType.VarWChar, 50, "Фамилия"); myUpdateCommand.Parameters.Add(new OleDbParameter("@Кодтуриста", System.Data.OleDb.OleDbType.Integer, 0, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); dataAdapter.UpdateCommand = myUpdateCommand;



//DeleteCommand OleDbCommand myDeleteCommand = conn.CreateCommand(); myDeleteCommand.CommandType = CommandType.StoredProcedure; myDeleteCommand.CommandText = "[myDeleteSP]"; myDeleteCommand.Parameters.Add("@Фамилия", OleDbType.VarWChar, 50, "Фамилия"); myDeleteCommand.Parameters.Add("@Имя", OleDbType.VarWChar, 50, "Имя"); myDeleteCommand.Parameters.Add("@Отчество", OleDbType.VarWChar, 50, "Отчество"); dataAdapter.DeleteCommand = myDeleteCommand; }

В обработчике события Closing формы вызываем метод Update объекта DataAdapter:

private void Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e) { try { i f (dsTourists.HasChanges()) { dataAdapter.Update(dsTourists); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } }

Запускаем приложение. Мы снова можем добавлять, изменять и удалять записи (рис. 13.7):


Рис. 13.7.  Готовое приложение StoredProcedures_in_Access

В программном обеспечении к курсу вы найдете файл BDTur_firm3.mdb и приложение StoredProcedures_in_Access (Code\Glava6\BDTur_firm3.mdb и StoredProcedures_in_Access).

Мы рассмотрели более сложный, программный способ работы с внутренними запросами Microsoft Access. Среда Visual Studio .NET предоставляет также графический интерфейс для работы с ними. Переходим на вкладку "Server Explorer", щелкаем правой кнопкой мыши на заголовке "Data Connections" и выбираем пункт меню "Add Connection". В появившемся окне "Свойства связи с данными" настраиваем подключение к базе данных BDTur_firm3.mdb. Раскрываем затем узел базы данных для просмотра его содержимого (рис. 13.8):


увеличить изображение
Рис. 13.8.  База данных BDTur_firm3.mdb в окне Server Explorer

Запрос "Сортировка_туристов" был отнесен к представлениям базы (Views). Его можно просмотреть, как и таблицы базы данных, дважды щелкая на него. Запросы модификации данных, отнесенные к узлу Stored Procedures, недоступны для непосредственного изменения в среде, в отличие от хранимых процедур MS SQL Server.Мы можем только просмотреть их свойства в окне Properties.

При перетаскивании хранимых процедур на форму создаются соответствующие объекты OleDbCommnad. Дальнейшее создание приложения практически не отличается от рассмотренного выше описания VisualUpdateWithSP.


Проблемы, связанные с обновлением базы данных


Представим себе, что некоторый пользователь при помощи своего приложения подключился к базе и получил данные. Объект DataSet способен хранить полученные данные достаточно долго. За это время другой пользователь или группа пользователей могут загрузить эти же данные в свои объекты DataSet. При редактировании локальных данных различными пользователями и последующей их синхронизации возникает проблема: какие именно изменения база данных должна записывать? Ответ на этот вопрос зависит от структуры обновляющих запросов. По умолчанию при создании запросов с помощью мастера Data Adapter Configuration Wizard исключается возможность перезаписи первым пользователем изменений, внесенных в базу другими пользователями за время его автономной работы. Это реализация так называемого оптимистического параллелизма (optimistic concurrency). Создайте новое Windows-приложение и назовите его "UseOptimConcur". Переходим на вкладку Data панели инструментов Toolbox, добавляем на форму объект SqlDataAdapter. В появившемся мастере настраиваем подключение к базе данных Microsoft SQL "BDTur_firm2" для извлечения всего двух полей - "Код туриста" и "Фамилия" из таблицы "Туристы". Завершив работу мастера, создайте новое приложение и назовите его "NoUseOptimConcur". Проделываем те же самые действия, только на этот раз в окне Generate the SQL statements нажимаем на кнопку "Advanced Options_". В появившемся окне Advanced SQL Generation Options снимаем галочку "Use optimistic concurrency" (рис. 13.19):


увеличить изображение
Рис. 13.19.  Окно "Advanced SQL Generation Options" мастера настройки объекта DataAdapter

Закрываем окна настройки и завершаем работу мастера. Итак, у нас получилось два приложения, и для того чтобы разобраться с оптимистическим параллелизмом, нам нужно сравнить их листинги. Скопируем весь код приложений5) в два отдельных документа Microsoft Word, которые затем сохраним, называя так же, как проекты.
Открываем документ UseOptimConcur.doc в главном меню переходим "Сервис \ Сравнить и объединить исправления", в появившемся окне выбираем документ NoUseOptimConcur.doc, отмечаем галочку "Черные строки" (рис. 13.20):


увеличить изображение
Рис. 13.20.  Окно "Сравнить и объединить документы"

Различия появляются в новом документе, выделенные цветом и сопровожденные комментариями (рис. 13.21):


увеличить изображение
Рис. 13.21.  Различия документов "UseOptimConcur.doc" и "NoUseOptimConcur.doc"

Сразу замечаем, что мастер генерирует различные SQL-конструкции для команд UPDATE и DELETE. Теперь можно расположить эти фрагменты рядом для детального рассмотрения (таблица 13.1).

Таблица 13.1. Фрагменты листингов приложений "UseOptimConcur" и "NoUseOptimConcur"Приложение "UseOptimConcur"Приложение "NoUseOptimConcur"


// // sqlUpdateCommand1 // this.sqlUpdateCommand1.CommandText = @"UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия WHERE (Кодтуриста = @Original_Кодтуриста) AND (Фамилия = @Original_Фамилия OR @Original_Фамилия IS NULL AND Фамилия IS NULL); SELECT Кодтуриста, Фамилия FROM Туристы WHERE (Кодтуриста = @Кодтуриста)"; this.sqlUpdateCommand1.Connection = this.sqlConnection1; this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Кодтуриста", System.Data.SqlDbType.Int, 4, "Кодтуриста")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Фамилия", System.Data.SqlDbType.NVarChar, 50, "Фамилия")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Фамилия", System.Data.SqlDbType.NVarChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Фамилия", System.Data.DataRowVersion.Original, null));
// // sqlDeleteCommand1 // this.sqlDeleteCommand1.CommandText = "DELETE FROM Туристы WHERE (Кодтуриста = @Original_Кодтуриста) AND (Фамилия = @Ori" + "ginal_Фамилия OR @Original_Фамилия IS NULL AND Фамилия IS NULL)"; this.sqlDeleteCommand1.Connection = this.sqlConnection1; this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null)); this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Фамилия", System.Data.SqlDbType.NVarChar, 50, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Фамилия", System.Data.DataRowVersion.Original, null));


// // sqlUpdateCommand1 // this.sqlUpdateCommand1.CommandText = "UPDATE Туристы SET Кодтуриста = @Кодтуриста, Фамилия = @Фамилия WHERE (Кодтуриста" + " = @Original_Кодтуриста); SELECT Кодтуриста, Фамилия FROM Туристы WHERE (Кодтури" + "ста = @Кодтуриста)"; this.sqlUpdateCommand1.Connection = this.sqlConnection1; this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Кодтуриста", System.Data.SqlDbType.Int, 4, "Кодтуриста")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Фамилия", System.Data.SqlDbType.NVarChar, 50, "Фамилия")); this.sqlUpdateCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));


// // sqlDeleteCommand1 // this.sqlDeleteCommand1.CommandText = "DELETE FROM Туристы WHERE (Кодтуриста = @Original_Кодтуриста)"; this.sqlDeleteCommand1.Connection = this.sqlConnection1; this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@Original_Кодтуриста", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.Input, false, ((System.Byte)(0)), ((System.Byte)(0)), "Кодтуриста", System.Data.DataRowVersion.Original, null));
<


Внимательно изучив рис. 13.21 и таблицу 13.1, делаем вывод, что по умолчанию мастер Data Adapter Configuration Wizard включает в раздел WHERE все поля и добавляет соответствующие параметры. Такая логика исключает перезапись изменений, сделанных другими пользователями в интервал времени между выборкой данных и последующей через некоторое время отправкой изменений. Действительно, если за время работы в отсоединенном режиме запись будет изменена, значения ее полей уже не будут соответствовать условиям раздела WHERE. Следовательно, объект Data Adapter, обратившись к базе и не обнаружив нужной записи, не будет вносить изменения.

Если вам, наоборот, нужно обновлять данные, "затирая" внесенные изменения, в SQL-запросы UPDATE и DELETE следует включать только поля первичного ключа. Это пример деструктивного параллелизма (destructive concurrency), модель которого характеризуют фразой "побеждает пришедший последним".

В программном обеспечении к курсу вы найдете папку "Concurrency" с приложениями UseOptimConcur, NoUseOptimConcur, а также с соответствующими документами Microsoft Word (Code\Glava6\Concurrency).


Изменение стандартного отчета


Основная задача web-служб - предоставление методов и данных. Они могут совсем не иметь пользовательского интерфейса, поскольку клиентское приложение все равно его не отобразит. Тем не менее при размещении в Интернете аскетичная страничка-отчет будет "лицом" web-сервиса, по крайней мере для разработчиков, которые будут его применять. Изменим немного стандартное оформление. Шаблон "DefaultWsdlHelpGenerator.aspx", по которому среда Visual Studio .NET генерирует отчеты, находится в каталоге "C:\WINDOWS\Microsoft.NET\ Framework\v1.1.4322\CONFIG". Поместим в этот каталог логотип "logoINTUIT.gif" (он находится в программном обеспечении7) к курсу: Code\Glava7\ logoINTUIT.gif). Для того чтобы не испортить шаблон в процессе экспериментирования, лучше сделать запасную копию. Открываем файл DefaultWsdlHelpGenerator.aspx при помощи студии, прокручиваем страницу почти до самого конца. В описании CSS изменяем цвет заголовка:

<style type="text/css">

... .heading1 { <%#GetLocalizedText("Styleheading1")%> background-color: #ff9900; } .... </style>

После тега "body" вставляем рисунок и ссылку:

<body> <a href="http://www.intuit.ru/"> <img src="C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\CONFIG\logoINTUIT.gif" border="0" width="285" height="52" alt="Интернет Университет информационных технологий"> </a> ...

Сохраняем страницу, запускаем web-сервис DataNorthwind, и теперь его отчет выглядит следующим образом (рис. 14.29):


Рис. 14.29.  Отчет web-сервиса DataNorthwind, созданный на основе измененного шаблона

Изменяя параметры CSS, можно задавать оформление текста, ссылок, а также внешний вид страницы. Отчеты всех web-сервисов, создаваемых в дальнейшем на данном компьютере, будут иметь соответствующий вид.

  1)

  При установке среды разработки Visual Studio. NET мастер предлагает предварительно установить IIS (с установочного диска системы).

  2)

  Если же вы установили его полностью, вам придется зарегистрировать библиотеку .NET Framework (см. далее в этой лекции "Проблема, связанная с переустановкой IIS").

  3)

  Здесь я снова буду писать названия методов этим ужасным translitom.

  4)

  Более подробное описание оформления пакета установки вы можете найти здесь: http://www.intuit.ru/department/pl/visualcsharp/9/visualcsharp_9.html

  5)

  Подробное описание работы с сервером IIS вы можете найти в курсе "Администрирование web-серверов в IIS" на сайте www.intuit.ru

  6)

  Веб-сервис прописался, таким образом, в списке программ, для его последующего удаления можно использовать стандартное приложение "Установка и удаления программ".

  7)

  Вы также можете получить этот логотип, сохранив главную страницу сайта www.intuit.ru

Подготовка сервера IIS


Web-служба размещается на сервере IIS (Internet Information Services). При установке Microsoft Windows XP Professional (SP2) по умолчанию сервер IIS устанавливается не полностью и нам нужно завершить его комплектацию1). Переходим в меню "Пуск \ Панель управления \ Установка и удаление программ". В категории "Установка компонентов Windows" выбираем пункт "Internet Information Services (IIS)", нажимаем кнопку "Состав", отмечаем галочками все компоненты. Затем вставляем установочный диск Windows XP и нажимаем кнопку "Далее". Установка занимает пару минут, полностью настроенный сервер IIS не имеет затененного флажка (рис. 14.1):


Рис. 14.1.  Установка сервера IIS

Скорее всего, у вас имеется частично установленный сервер2). Теперь можно приступать к разработке web-служб.



Проблема, связанная с переустановкой IIS


В процессе работы может потребоваться переустановить IIS с компьютера. Переходим в меню "Пуск \ Панель управления \ Установка и удаления программ", в категории "Установка компонентов Windows" снимаем галочку "Internet Information Services (IIS)" нажимаем кнопку "Далее". Сервер IIS будет удален с компьютера. Для его установки повторяем эти же действия, отмечая его галочкой, затем вставляем установочный диск "Windows XP" и снова нажимаем кнопку "Далее". После установки приступаем к созданию нового web-сервиса, и при попытке его создания возникает сообщение об ошибке (рис. 14.27):


Рис. 14.27.  Ошибка при создании web-сервиса после переустановки IIS

Дело в том, что новый IIS, если он устанавливается поверх Visual Studio. NET, не содержит регистрации библиотеки .NET Framework. Это нужно проделать вручную. Переходим "Пуск \ Все программы \ Microsoft Visual Studio .NET 2003 \ Visual Studio .NET Tools \ Visual Studio .NET 2003 Command Prompt". В появившемся окне вводим следующую команду:

aspnet_regiis /i

Через некоторое время регистрация завершается (рис. 14.28) и можно приступать к разработке web-служб.


увеличить изображение
Рис. 14.28.  Регистрация библиотеки .NET Framework



Расположение файлов web-сервиса


При создании Windows-приложений нам не приходится задумываться о местоположении наших файлов и проектов, об их взаимодействии со службами компьютера, - все достаточно просто. Имеется папка, в которой располагается проект, в нем есть папка "Debug" (или "Release"), где размещено скомпилированное приложение. Немного по-другому обстоят дела с web-службами. После создания проекта web-сервиса Northwind Service все рабочие файлы располагаются по умолчанию в каталоге "C(Имя системного диска):\Inetpub\wwwroot\NorthwindService" (рис. 14.18):


Рис. 14.18.  Файлы web-сервиса

В папке "bin" находится скомпилированный файл библиотеки динамической компоновки NorthwindService.dll. Файл Solution, по нажатию на который запускается среда Visual Studio .NET и открывается web-сервис в режиме разработки, расположен в каталоге "C:\Documents and Settings\Chingiz Kariev(Имя пользователя)\Мои документы\Visual Studio Projects\NorthwindService.sln" (рис. 14.19):


Рис. 14.19.  Файл Solution web-сервиса NorthwindService

Рабочие файлы web-сервиса, которые используются приложениями-клиентами (например, TestNorthwindWS), должны быть помещены в web-узел сервера IIS. Переходим в меню "Пуск \ Панель управления \ Администрирование \ Internet Information Services". В группе "Web-узел по умолчанию" мы видим папку "NorthwindService", в которой располагаются все рабочие файлы (рис. 14.20):


увеличить изображение
Рис. 14.20.  Сервер Internet Information Services (IIS)

При переходе по ссылке "Web services on the local machine" мы видели ссылку на сервис DataNorthwind только лишь потому, что папка сервиса уже находилась в каталоге сервера IIS. Откуда взялась эта папка? При создании нового проекта web-сервиса среда Visual Studio .NET автоматически помещает его файлы в директорию "C:\Inetpub\wwwroot". Сервер IIS сканирует эту директорию и при наличии подходящих файлов предоставляет соответствующие услуги web-сервиса.

В программном обеспечении к курсу вы найдете папку NorthwindService, скопированную из узла "C:\Inetpub\wwwroot". (Code\Glava7\ Northwind Service).



Разработка приложения, использующего web-сервис


В качестве клиента, вызывающего web-методы, применим Windows-приложение. Создайте новый Windows-проект и назовите его "Test NorthwindWS". Добавляем на форму элементы управления DataGrid и Panel, свойствам Dock этих элементов устанавливаем значения "Fill" и "Bottom" соответственно.

На панели размещаем две кнопки, устанавливая им следующие свойства:

button1, свойствоЗначение
NamebtnFill
Location26; 16
TextЗаполнить
button2, свойствоЗначение
NamebtnUpdate
Location122; 16
Size144; 23
TextПередать изменения

Интерфейс готов. В окне Solution Explorer щелкаем правой кнопкой на узле "References" и в появившемся контекстном меню выбираем пункт "Add Web Reference_" (рис. 14.13):


Рис. 14.13.  Окно Solution Explorer

В появившемся окне Add Web Reference доступен просмотр всех web-служб. Поскольку наш сервис расположен на локальном компьютере, нажимаем на ссылку "Web services on the local machine". Далее снова переходим по ссылке "DataNorthwind" (рис. 14.14):


увеличить изображение
Рис. 14.14.  Окно Add Web Reference, просмотр web-служб

Появляется уже знакомый нам отчет, на котором можно просмотреть описание сервиса и его методов. В поле Web reference name можно ввести имя создаваемой ссылки. Мы оставляем предложенное название "localhost" и нажимаем кнопку "Add Reference" (рис. 14.15):


увеличить изображение
Рис. 14.15.  Добавление ссылки на сервис DataNorthwind

В окне Solution Explorer появилась папка Web Reference с узлом localhost. Дважды щелкаем на этом узле. В окне Object Browser мы можем просмотреть описание web-методов, предоставляемых сервисом (рис. 14.16):


увеличить изображение
Рис. 14.16.  Просмотр web-методов в окне Object Browser

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

using TestNorthwindWS.localhost;

В классе формы создаем экземпляр myService:


DataNorthwind myService = new DataNorthwind();

В обработчиках кнопок "btnFill" и "btnUpdate" вызываем соответствующие web-методы:

private void btnFill_Click(object sender, System.EventArgs e) { DataSet ds = myService.ZapolnenieDannimi(); dataGrid1.DataSource = ds.Tables[0].DefaultView; }

private void btnUpdate_Click(object sender, System.EventArgs e) { DataSet ds = (DataSet)dataGrid1.DataSource; if(!myService.PeredachaIzmeneniy(ds)) MessageBox.Show("Ошибка обновления", " При передаче изменений возникла ошибка" ); }

Запускаем приложение. Мы можем просматривать и изменять содержимое таблицы Customers (рис. 14.17):


Рис. 14.17.  Готовое приложение TestNorthwindWS

Как работает наше приложение? Мы добавили ссылку на web-службу, аналогично добавлению обычной библиотеки. Не имеет значения, где эта служба находится. Далее мы просто создаем объект класса службы и вызываем соответствующие методы. При нажатии на кнопку "Заполнить" происходит обращение к серверу IIS, который, в свою очередь, обращается к MS SQL Server и получает данные для объекта DataSet. Наше приложение устанавливает полученный DataSet в качестве источника данных для элемента управления DataGrid. Аналогично выполняется метод, привязанный к кнопке "Передать изменения".

В программном обеспечении к курсу вы найдете приложение Test NorthwindWS (Code\Glava7\ TestNorthwindWS).


Создание пакета установки web-сервиса


Для переноса web-службы на другой компьютер достаточно разместить в его каталоге IIS (по умолчанию "C:\Inetpub\wwwroot") рабочие файлы. Это можно сделать непосредственно простым копированием, но лучше подготовить пакет установки. Где-нибудь в рабочей директории создаем папку "Пакет_установки" (у меня это "D:\Uchebnik\Code\Glava7") - в нее будем помещать файлы пакета установки. Запускаем Visual Studio .NET, создаем новый проект, в окне "New Project" переходим в категорию "Setup and Deployment Projects" и выбираем шаблон "Web Setup Project" (рис. 14.21):


Рис. 14.21.  Окно New Project. Создание пакета установки

В окне File System щелкаем правой кнопкой на папке "Web Application Folder", в появившемся меню выбираем "Add \ File" (рис. 14.22, А). Далее, в окне "Add Files" переходим в текущий каталог IIS, из папки "NorthwindService" добавляем следующие файлы (рис. 14.22, Б):


увеличить изображение
Рис. 14.22.  Добавление файлов веб-службы. А - Папка "Web Application Folder", Б - выделение файлов папки "NorthwindService"

Теперь нужно определить содержимое папки "bin". Щелкаем на ней правой кнопкой, снова переходим в меню "Add \ File" (рис. 14.23, А). В окне Add Files переходим в папку "bin", которая расположена в папке "NorthwindService", и добавляем следующие два файла (рис. 14.23, Б):


увеличить изображение
Рис. 14.23.  Добавление файлов веб-службы. А - Папка "bin". Б - выделение файлов папки "bin", находящейся в папке "NorthwindService"

Все нужные файлы собраны. На панели инструментов Standart выбираем режим

("Release") отладки проекта, а затем компилируем его, нажимая сочетание клавиш Ctrl+Shift+B (или выбирая пункт главного меню "Build \ Build Solution"). Пакет установки готов4). Для переноса на другой компьютер будут нужны три файла, которые появились в папке "Release" (рис. 14.24):



Рис. 14.24.  Готовый пакет установки

Проделаем операцию установки на локальном компьютере. На сервере IIS у нас уже есть web-сервис NorthwindService, возникший в процессе его создания в среде Visual Studio .NET. Нам нужно его вначале удалить, а потом установить с помощью пакета установки. Переходим в папку "C:\Inetpub\wwwroot", выделяем папку, нажимаем кнопку "Delete" и... Появляется сообщение об ошибке (рис. 14.25):


Рис. 14.25.  Ошибка, возникающая при попытке удалить папку из каталога IIS

Причина ошибки заключается в том, что сервер IIS использует содержимое этой папки. Удалять папку следует с помощью утилиты администрирования сервера. Переходим в меню "Пуск \ Панель управления \ Администрирование", запускаем утилиту "Internet Information Services". Она предназначена для управления5) сервером IIS, расположенным на данном компьютере. Для удаления web-службы раскрываем узел локального компьютера, щелкаем правой кнопкой на заголовке "NorthwindService", в выпадающем меню выбираем "Все задачи \ Удалить web-узел серверных расширений" (рис. 14.26):


Рис. 14.26.  Удаление web-службы NorthwindService

После подтверждения удаления служба будет удалена из папки "C:\Inetpub\wwwroot". Для установки web-службы запускаем файл Setup.Exe, расположенный в папке "Пакет_установки". В процессе установки мастер предложит выбрать название создаваемой директории и порт подключения. Оставим эти значения по умолчанию. Завершив установку6), запускаем утилиту Internet Information Services - в ней снова появился узел NorthwindService. Можно проверить его работу с помощью приложения TestNorthwindWS.

В программном обеспечении к курсу вы найдете папку Пакет_установки (Code\Glava7\Пакет_установки).


Создание web-сервиса


Рассмотрим создание web-службы, предоставляющей возможность получать и обновлять данные из базы Northwind MS SQL Server. Запускаем Visual Studio .NET, создаем новый проект, в списке шаблонов выбираем "ASP.NET Web Service", в строке "Location" вводим название web-сервиса "NorthwindService" (рис. 14.2):


Рис. 14.2.  Создание web-сервиса NorthwindService

Среда генерирует шаблон web-сервиса, который открывается в режиме дизайна (рис. 14.3). Сам по себе web-сервис не имеет пользовательского интерфейса, поэтому у него достаточно специфичный вид. Но в процессе разработки можно использовать визуальные средства студии, размещая на поверхности элементы управления или объекты. В этом смысле режим дизайна web-сервиса аналогичен панели компонент в обычных Windows-приложениях.


Рис. 14.3.  Шаблон web-сервиса

Файл web-сервиса имеет расширение .asmx. Переходим в окно Solution Explorer и переименовываем файл "Service1.asmx" в "DataNorthwind.asmx". Переключаемся в режим кода, нажимая на ссылку "click here to switch to code view" (или просто нажимая клавишу F7). Дополнительно изменяем название класса и конструктора. Разработчики Visual Studio .NET оставили тестовый метод web-сервиса Hello World, который возвращает ставшую классической строку "Hello World". Снимаем с него комментарии. Полностью листинг web-службы будет выглядеть теперь следующим образом:

using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace NorthwindService { /// <summary> /// Summary description for Service1. /// </summary> public class DataNorthwind : System.Web.Services.WebService { public DataNorthwind() { //CODEGEN: This call is required by the ASP.NET //Web Services Designer InitializeComponent(); } #region Component Designer generated code //Required by the Web Services Designer private IContainer components = null; /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if(disposing && components != null) { components.Dispose(); } base.Dispose(disposing); } #endregion // WEB SERVICE EXAMPLE // The HelloWorld() example service returns the string Hello World // To build, uncomment the following lines then save and build the project // To test this web service, press F5 [WebMethod] public string HelloWorld() { return "Hello World"; } } }


Компилируем сервис, нажимая кнопку F5. Среда генерирует отчет, содержащий описание сервиса, рекомендации, а также его методы и их тесты. Переходим по ссылке "HelloWorld" (рис. 14.4):


Рис. 14.4.  Отчет web-сервиса, генерируемый средой

Появляется окно, в котором можно протестировать результат метода, щелкнув на кнопку "Invoke" (рис. 14.5):


Рис. 14.5.  Тестирование метода HelloWorld

Метод возвращает строку "Hello World" в формате XML, она открывается в отдельном окне (рис. 14.6):


Рис. 14.6.  Результат выполнения метода

Вернемся к коду. При тестировании появлялась рекомендация (см. рис. 14.4) изменить пространство имен web-сервиса перед его публикацией. По умолчанию мастер подставляет пространство из шаблона:

http://tempuri.org/

Перед классом задаем собственное пространство имен и описание web-сервиса при помощи атрибутов Namespace и Desription:

[WebService(Namespace= "http://www.somesite.com/mywebservices", Description="Web-сервис для работы с базой данных Northwind")] public class DataNorthwind : System.Web.Services.WebService { ...

Web-методы также обладают атрибутом "Description", с помощью которого можно задавать описание каждого метода:

[WebMethod (Description="Этот метод разработчики оставили в качестве примера.")] public string HelloWorld() { return "Hello World"; }

Снова компилируем проект. Теперь отчет стал более понятным (рис. 14.7):


Рис. 14.7.  Применение атрибутов Description

Приступим к созданию собственных методов. Переходим в режим дизайна, из вкладки Data окна Toolbox перетаскиваем объект SqlDataAdapter. В появившемся мастере Data Adapter Configuration Wizard настраиваем извлечение всех записей таблицы Customers базы данных Northwind. На поверхности появляются два объекта - sqlDataAdapter1 и sqlConnection1, они выглядят точно так же, как аналогичные объекты на панели компонент обычного Windows-приложения.


Выделяем объект sqlDataAdapter1, в его окне Properties щелкаем по ссылке Generate Dataset. В появившемся окне оставляем предложенное название DataSet. У нас имеются все объекты для написания собственных web-методов. Если мы просмотрим в коде область Component Designer generated code, то обнаружим, что среда сгенерировала описание объектов ADO .NET в точности такое же, как и в обычном Windows-приложении. Добавляем методы3) в классе формы:

[WebMethod (Description="Метод для заполнения данными объекта DataSet")] public DataSet ZapolnenieDannimi() { sqlDataAdapter1.Fill(dataSet11); return dataSet11; }

[WebMethod (Description="Метод для передачи изменений в базу данных")] public bool PeredachaIzmeneniy(DataSet ds) { if (ds.HasErrors) return false; sqlDataAdapter1.Update(ds); return true; }

Параметр [WebMethod] является обязательным при создании web-методов, а атрибут Description - нет. Впрочем, его следует добавлять для удобства работы. Протестируем наш сервис. Переходим по ссылке к методу ZapolnenieDannimi (рис. 14.8):


Рис. 14.8.  Тестирование после добавления собственных методов

При извлечении метода - нажатии кнопки "Invoke" - на его странице появляется сообщение об ошибке:

System.Data.SqlClient.SqlException: Login failed for user 'F202406B87AB496\ASPNET'.

При настройке объекта DataAdapter для подключения к MS SQL Server я выбрал тип аутентификации Windows. Web-служба подключается, используя учетную запись "ASPNET", которая создается при установке Visual Studio .NET. Проблема заключается в том, что эта учетная запись не обладает полномочиями для подключения к базе данных Northwind. Поэтому при отладке и появляется сообщение об ошибке, где "F202406B87AB496" - имя моего локального компьютера. Если мы не хотим использовать аутентификацию SQL-сервера, нужно будет изменить права учетной записи "ASPNET". Запускаем SQL Server Enterprise Manager, раскрываем узел текущего сервера, на вкладке Security щелкаем правой кнопкой и выбираем New Login.


В появившемся окне SQL Server Login Properties нажимаем кнопку обзора (_), в появившемся окне добавляем учетную запись "ASPNET", затем нажимаем "OK" (рис. 14.9):


Рис. 14.9.  Добавление пользователя "ASPNET"

Далее, на вкладке Server Roles отмечаем галочкой "Database Creators" (рис. 14.10). Эта роль предоставляет права создавать и изменять базы данных на сервере.


Рис. 14.10.  Определение роли пользователя "ASPNET"

На вкладке Database Access определяем, к какой именно базе данных будет иметь доступ пользователь "ASPNET". Отмечаем только базу Northwind (рис. 14.11):


Рис. 14.11.  Предоставления доступа к базе "Northwind"

Завершаем настройку учетной записи, нажимая "OK" в окне SQL Server Login Properties. Проверим снова web-сервис - метод ZapolnenieDannimi возвращает теперь содержимое таблицы Customers (рис. 14.12):


Рис. 14.12.  Выполнение метода "ZapolnenieDannimi"

В документе находится XSD-схема объекта DataSet, а также само содержимое в формате XML.

Мы завершили разработку web-сервиса.