Технология Microsoft ADO.NET
Агрегатные функции
При статистическом анализе баз данных необходимо получать такую информацию, как общее количество записей, наибольшее и наименьшее значения заданного поля записи, усредненное значение поля. Это делается с помощью запросов, содержащих так называемые агрегатные функции. Агрегатные функции производят одиночное значение для всей группы таблицы. Имеется список этих функций.Для определения общего числа записей в таблице Products используем запрос
select count (*) from Products;
результатом которого будет следующее (рис. 1.42):

Рис. 1.42. Результат запроса с функцией count
Обратите внимание: на вкладке "Messages" возникает сообщение, что только одна запись извлечена:
(1 row(s) affected)
Это означает, что агрегатные функции возвращают всего одну запись, называемую скалярным значением, в отличие от других запросов, возвращающих наборы записей.
Для определения количества записей поля ProductName таблицы Products используем следующий запрос:
select count (ProductName) from Products;
Таблица Products имеет все заполненные значения полей, поэтому результат этого запроса будет совпадать с результатом извлечения всех записей.
Как быть со значениями полей, которые были незаполненными? Обращение к таким полям осуществляет оператор null. Величина null не означает, что в поле стоит число 0 (нуль) или пустая текстовая строка - " ". Как возникают значения полей null? Существует два способа образования таких значений:
Вы можете самостоятельно попробовать определить, содержатся ли в какой-либо таблице базы данных Northwind поля, имеющие значения
select *( или название столбца(ов)) from название_таблицы where название столбца is null;
Для обратной задачи - используем запрос типа
select *( или название столбца(ов)) from название_таблицы where название столбца is not null;
Оператор count учитывает записи со значением поля null.
Синтаксис использования других операторов одинаков - следующие запросы извлекают сумму, арифметическое среднее, наибольшее и наименьшее значения поля UnitPrice таблицы Products:
select sum(UnitPrice) from Products; select avg(UnitPrice) from Products; select max(UnitPrice) from Products; select min(UnitPrice) from Products;
Выполните самостоятельно эти запросы и просмотрите результаты.
Что такое реляционная база данных?
Базы данных - это совокупность сведений (об объектах, процессах, событиях или явлениях), относящихся к определенной теме или задаче. Она организована таким образом, чтобы обеспечить удобное представление этой совокупности, как в целом, так и любой ее части.Реляционная база данных представляет собой множество взаимосвязанных таблиц, каждая из которых содержит информацию об объектах определенного типа. Каждая строка таблицы содержит данные об одном объекте (например товаре, фирме, клиенте), а столбцы таблицы содержат различные характеристики этих объектов - атрибуты (например, наименования и цены товаров, адреса и телефоны фирм или клиентов). Строки таблицы называются записями; все записи имеют одинаковую структуру - они состоят из полей, в которых хранятся атрибуты объекта (рис. 1.1). Каждое поле записи содержит одну характеристику объекта и имеет строго определенный тип данных (например, текстовая строка, число, дата). Все записи имеют одни и те же поля, только в них содержатся разные значения атрибутов. Для идентификации записей используется первичный ключ. Первичный ключ - это одно или несколько полей (столбцов), комбинация значений которых однозначно определяет каждую запись в таблице.

Рис. 1.1. Названия объектов в таблице
Для работы с данными используются системы управления базами данных (СУБД). Основные функции СУБД - это определение данных (описание структуры баз данных), обработка данных и управление данными.
Любая СУБД позволяет выполнять следующие операции с данными:
Для выполнения этих операций применяется механизм запросов. Результатом выполнения запросов является либо отобранное по определенным критериям множество записей, либо изменения в таблицах. Запросы к базе формируются на специально созданном для этого языке, который так и называется "язык структурированных запросов" (SQL - Structured Query Language).
Важнейшей функцией СУБД является управление данными. Под управлением данными обычно понимают защиту данных от несанкционированного доступа, поддержку многопользовательского режима работы с данными и обеспечение целостности и согласованности данных.
Элементы языка SQL
Термин SQL6) символизирует собой Структурированный Язык Запросов. Это язык, который дает нам возможность работать с данными в реляционных базах данных.Мир баз данных становится все более и более единым, что привело к необходимости создания стандартного языка, который мог бы использоваться для функционирования в большом количестве различных видов компьютерных сред. Стандартный язык позволит пользователям, знающим один набор команд, применять их, чтобы создавать, отыскивать, изменять и передавать информацию независимо от того, работают ли они на персональном компьютере, сетевой рабочей станции или на сервере.
Элегантность и независимость от специфики компьютерных технологий, а также его поддержка лидерами промышленности в области технологии реляционных баз данных, сделали SQL, и, вероятно, в течение обозримого будущего оставят его, основным стандартным языком. По этой причине любой, кто хочет работать с современными базами данных, должен знать SQL.
Стандарт SQL определяется ANSI (Американским Национальным Институтом Стандартов), а также ISO (Международной организацией по стандартизации). Однако большинство коммерческих программ баз данных расширяют SQL без уведомления ANSI, добавляя разные особенности в этот язык, которые, как они считают, будут полезны. Иногда они несколько нарушают стандарт языка, хотя хорошие идеи имеют тенденцию развиваться и вскоре становиться стандартами сами по себе в силу полезности своих качеств.
Для обращения к базе данных используются запросы, написанные на языке SQL. Запрос - команда, которую вы даете вашей программе базы данных, и которая сообщает ей, что нужно вывести определенную информацию из таблиц в память. Эта информация обычно посылается непосредственно на экран компьютера или терминала, которым вы пользуетесь, хотя в большинстве случаев ее можно также послать принтеру, сохранить в файле (как объект в памяти компьютера) или представить как вводную информацию для другой команды или процесса.
Несмотря на большое количество разновидностей этого языка, существующих в настоящее время, логика его работы проста.
Достаточно освоить основные команды хотя бы в одной из его версий, чтобы впоследствии без труда разобраться в любой другой его реализации.
Microsoft Query Analyzer, входящий в комплект Microsoft SQL Server 2000, - отличный инструмент для изучения языка SQL. Если у вас установлен этот пакет, то можно приступать к работе. Запускаем Query Analyzer и в появившемся окне указываем следующие параметры (рис. 1.32).

Рис. 1.32. Задание параметров подключения
Мы задействуем подключение к серверу, находящемуся на нашем компьютере, - поэтому указываем имя сервера (local) (можно также использовать знак точки - ( . )). В других случаях - для подключения по локальной сети, например, - используйте кнопку обзора для поиска нужного сервера. Установка галочки "Запускать SQL сервер в случае его остановки (Start SQL Server if it is stopped)" позволяет поддерживать бесперебойную связь с сервером. Параметр "Connect using" задает аутентификацию при подключении к серверу - при выбранном значении "Windows authentification", нет надобности вводить имя пользователя и пароль.
Если вы все сделали правильно, то появляется главное окно программы, содержащее чистый бланк для записи SQL-запросов. Добавим окно, в котором мы будем просматривать содержимое базы данных: пункт меню Tools / Object Browser / (Show/hide) или клавиша F8. Нам также потребуется окно для отображения результатов запросов - Window / /Show Results Pane (Ctrl+R). В результате получим следующее (рис. 1.33).

увеличить изображение
Рис. 1.33. Главное окно программы
Первое, что мы видим в окне Object Browser, - имя компьютера и связанного с ним сервера. На рисунке это 7EA2B2F6068D473. Это имя формируется, когда мы устанавливаем операционную систему Windows. Далее располагается древовидная структура - содержимое пакета SQL Server 2000, состоящего из баз данных master, model, msdb, NorthwindCS и tempdb. В отличие от других баз данных, обеспечивающих работу самой программы, NorthwindCS является специальной учебной базой.
С ней мы и будем работать. На панели инструментов из выпадающего списка выбираем базу, для подключения -
. Можно этого не делать, но тогда в окне бланка необходимо будет каждый раз указывать строку - use NorthwindCS; Будем полагать в дальнейшем, что на панели инструментов определена база данных NorthwindCS;.Итак, напишем первый запрос7) и нажмем клавишу F5 (пункт меню Query - Execute):
select * from Customers;
В результате возвращаются все записи из таблицы Customers базы данных NorthwindCS. Для просмотра содержимого базы данных используем Object Browser, щелкая на знак (+) возле соответствующего объекта. Переключившись на вкладку
, видим сообщение, означающее, что была извлечена 91 запись:(91 row(s) affected)
Главное окно программы принимает вид (рис. 1.34)

Рис. 1.34. Запрос извлек таблицу Customers
Вы можете менять вид данных, отображаемых на панели результатов: пункт меню Query / Results in Text (Ctrl+T) - результат в виде текста, Results in Grid (Ctrl+D) - в виде таблицы (по умолчанию), Results to File (Ctrl+Shift+F) - сохранение результата в виде файла в собственном формате программы - *.rpt.
Таблица Customers состоит из следующих полей (рис. 1.35):

Рис. 1.35. Содержание таблицы Customers
Для извлечения не всей таблицы, а столбцов СustomerID и Address, напишем запрос:
select CustomerID, Address from Customers;
Результатом будет (рис. 1.36)

Рис. 1.36. Извлечение столбцов CustomerID и Address
Если мы сделаем ошибку и укажем поле, которого нет в таблице Customers, например AddressID, в окне результатов на вкладке Messages появится соответствующее предупреждение:
Server: Msg 207, Level 16, State 1, Line 3 Invalid column name 'AddressID'.
Для вывода определенного количества записей используем запрос (рис. 1.37)
select top 5 CustomerID from Customers;

Рис. 1.37. Извлечение нескольких записей
Извлекаются первые пять записей поля CustomerID, расположенные в самой таблице Customers в алфавитном порядке, - этот запрос не производит сортировки!
Вводя оператор percent, получаем указанный процент записей от общего числа:
select top 5 percent CustomerID from Customers;
В данном случае результат будет в точности таким же, как и при использовании запроса без оператора percent. В чем же дело? Общее число записей поля CustomerID таблицы Customers - 91 (в этом нетрудно убедиться, введя запрос select * from Customers; и переключившись на вкладку
, увидим сообщение: (91 row(s) affected)). Простой подсчет показывает, что пять процентов от 91 равняется 4,55; Query Analyzer округляет это число до пяти и возвращает результат.Для вывода записей, отвечающих заданному условию, используем оператор where:
select * from Products where UnitPrice > 100;
Этот запрос возвращает все записи из таблицы Products в которых Столбец (поле) UnitPrice имеет значение, большее 100 (рис. 1.38):

Рис. 1.38. Отбор записей со всеми полями по заданному значению
Можно группировать операторы так:
select ProductName,UnitPrice from Products where UnitPrice > 100;
Здесь извлекаются поля ProductName и UnitPrice из таблицы Products где поле UnitPrice > 100 (рис. 1.39):

Рис. 1.39. Отбор записей с несколькими полями по заданному значению
Оператор where поддерживает работу со знаками <, >, >=, <=.
Точную выборку осуществляет оператор in, в следующем примере извлекаются лишь те записи, в которых значение поля UnitPrice в точности равно либо 10, либо 15, либо 23 (рис. 1.40):
select ProductName,UnitPrice from Products where UnitPrice in (10,15,23);

Рис. 1.40. Отбор записей по точному совпадению значений поля UnitPrice
Выборка для значений, лежащих в указанном интервале, осуществляется оператором between первое _значение and второе_значение (рис. 1.41):
select ProductName,UnitPrice from Products where UnitPrice between 10 and 13;

Рис. 1.41. Отбор записей по значениям в указанном интервале
Элементы работы с СУБД Microsoft Access
Рассмотрим применение концепции реляционных баз данных на практике. Представим себе деятельность туристической фирмы. Очевидно, что для работы необходимо вести какие-то записи. Их можно производить в обычной бумажной тетради, но со временем поиск нужных записей и финансовая отчетность будет представлять довольно рутинную, длительную работу. Итак, необходимо создать электронную базу данных, которая должна значительно облегчить ведение всей статистики. Запускаем Microsoft Access 2003, в главном меню переходим "Файл - Создать", на появившейся панели "Создание файла" выбираем "Новая база данных". Далее указываем директорию, где будет сохранен файл базы данных, называем его "BDTur_firm.mdb" и нажимаем кнопку "Создать". В отличие от других офисных приложений, мы не можем начать работу с базой данных, предварительно не сохранив ее файл. В результате проделанных действий появляется окно базы данных (рис. 1.2).
Рис. 1.2. Окно базы данных
В заголовке окна базы данных указывается ее формат по умолчанию - Access 2000, обеспечивающий совместимость с программами Microsoft Access 2000 и 97. Если совместимость не важна и требуется получить все преимущества новых версий Access, можно преобразовать базу данных, для чего выбираем пункт меню "Сервис \ Служебные программы \ Преобразовать базу данных \ В формат Access 2002-2003". Мы продолжим работать с форматом по умолчанию.
Если вы закроете приложение, а затем снова продолжите работу, при открытии файла базы данных всякий раз будет появляться предупреждение системы безопасности. Дело в том, что в базе данных Microsoft Access могут храниться макросы, которые при открытии могут выполнить вредоносные действия. Система безопасности защищает, таким образом, от деструктивных действий, возможных при открытии чужого файла базы данных. При работе над своей собственной базой данных эти предупреждения излишни. Для их отключения в главном меню программы выбираем "Сервис \ Макрос \ Безопасность" и на вкладке "Уровни безопасности" отмечаем переключатель "Низкая".
Далее в появившемся диалоговом окне разрешаем выполнение небезопасных выражений. Теперь предупреждения появляться не будут, а для блокирования макросов при открытии чужой базы данных достаточно удерживать клавишу Shift.
Итак, приступим, наконец, к созданию базы данных. Самое сложное при этом - выстроить логическую структуру таблиц, избежать наличия повторяющихся записей, а также обеспечить удобство пользования с учетом специфики работы. В окне базы данных дважды щелкнем на надписи "Создание базы данных в режиме конструктора". В появившемся окне "Таблица1: таблица" предстоит определить названия полей, которые и станут заголовками в этой таблице. Введем следующие названия полей (рис. 1.3).

Рис. 1.3. Заполнение полей таблицы
При вводе названия поля по умолчанию определяется для него тип данных "текстовый". Для изменения типа следует выбрать нужное значение из выпадающего списка (рис. 1.4). Описания возможных типов данных Microsoft Access приводятся в таблице 1.1.

Рис. 1.4. Определение типа данных поля
| Текстовый | Текст или комбинация текста и чисел, например, адреса, а также числа, не требующие вычислений, например, номера телефонов, инвентарные номера или почтовые индексы. Сохраняет до 255 знаков. Свойство "Размер поля" (FieldSize) определяет максимальное количество знаков, которые можно ввести в поле |
| Поле МЕМО | Предназначено для ввода текстовой информации, по объему превышающей 255 символов. Такое поле может содержать до 65 535 символов. Этот тип данных отличается от типа Текстовый (Text) тем, что в таблице даются не сами данные, а ссылки на блоки данных, хранящиеся отдельно. За счет этого ускоряется обработка таблиц (сортировка, поиск и т. п.). Поле типа MEMO не может быть ключевым или проиндексированным |
| Числовой | Данные, используемые для математических вычислений, за исключением финансовых расчетов (для них следует использовать тип "Денежный").Сохраняет 1, 2, 4 или 8 байтов. Конкретный тип числового поля определяется значением свойства Размер поля (FieldSize) |
| Дата/время | Значения дат и времени. Сохраняет 8 байтов |
| Денежный | Используется для денежных значений и для предотвращения округления во время вычислений. Сохраняет 8 байтов |
| Счетчик | Автоматическая вставка уникальных последовательных (увеличивающихся на 1) или случайных чисел при добавлении записи. Сохраняет 4 байта |
| Логический | Данные, принимающие только одно из двух возможных значений, таких, как "Да/Нет", "Истина/Ложь", "Вкл/Выкл". Значения Null не допускаются. Сохраняет 1 бит |
| Поле объекта OLE | Объекты OLE1) (такие, как документы Microsoft Word, электронные таблицы Microsoft Excel, рисунки, звукозапись или другие данные в двоичном формате), созданные в других программах, использующих протокол OLE. Сохраняет до 1 Гигабайта (ограничивается объемом диска) |
| Гиперссылка | Гиперссылки. Гиперссылка может указывать на расположение файла на локальном компьютере либо адреса URL. Сохраняет до 64 000 знаков |
| Мастер подстановок | Создает поле, позволяющее выбрать значение из другой таблицы или из списка значений, используя поле со списком. При выборе данного параметра в списке типов данных запускается мастер для автоматического определения этого поля. Обычно сохраняет 4 байта |
Применение определенного типа данных позволяет избежать ошибок в работе с таблицами - в поле с форматом даты невозможно ввести значение суммы, а в поле с денежным форматом невозможно ввести дату. Кроме того, для различных данных требуется разный объем памяти, и резервирование полей с однородным составом позволяет значительно уменьшить общий размер базы данных.
Около поля "Код туриста" на рис. 1.2 находится изображение ключа. Это означает, что указанное поле будет первичным ключом для записей в таблице. Для того чтобы сделать данное поле ключевым, следует выделить его, щелкнуть правой кнопкой, а затем в появившемся контекстном меню выбрать "Ключевое поле" (рис. 1.5).

Рис. 1.5. Установка первичного ключа
Первая таблица готова. Сохраняем ее, называя "Туристы", и закрываем. Аналогичным образом создаем таблицы "Информация о туристах", "Туры", "Сезоны", "Путевки" и "Оплата" (таблица 1.2).
| 1 | Туристы | Код туриста | Счетчик | ![]() | Содержит основные сведения о туристе |
| Фамилия | Текстовый | ||||
| Имя | Текстовый | ||||
| Отчество | Текстовый | ||||
| 2 | Информация о туристах | Код туриста | Числовой | ![]() | Содержит дополнительные сведения о туристе, которые были вынесены в отдельную таблицу - для избегания повторяющихся записей |
| Серия паспорта | Текстовый | ||||
| Город | Текстовый | ||||
| Страна | Текстовый | ||||
| Телефон | Текстовый | ||||
| Индекс | Числовой | ||||
| 3 | Туры | Код тура | Счетчик | ![]() | Содержит общие сведения о странах для туров |
| Название | Текстовый | ||||
| Цена | Денежный | ||||
| Информация | Поле MEMO | ||||
| 4 | Сезоны | Код сезона | Счетчик | ![]() | Содержит сведения о сезонах - некоторые туры доступны лишь в определенный период |
| Код тура | Числовой | ||||
| Дата начала | Дата/время | ||||
| Дата конца | Дата/время | ||||
| Сезон закрыт | Логический | ||||
| Количество мест | Числовой | ||||
| 5 | Путевки | Код путевки | Числовой | ![]() | Содержит сведения о путевках, реализованных туристам. |
| Код туриста | Числовой | ||||
| Код сезона | Числовой | ||||
| 6 | Оплата | Код оплаты | Счетчик | ![]() | Содержит сведения об оплате за путевки |
| Код путевки | Числовой | ||||
| Дата оплаты | Дата/время | ||||
| Сумма | Денежный |
Теперь в окне базы данных есть несколько таблиц. Обратите внимание на наличие в нескольких таблицах одинаковых полей, например, в таблицах "Туристы" и "Информация о туристах" поле "Код туриста". Их назначение станет ясно немного позже. Приступим к связыванию таблиц. В окне базы данных щелкаем правой кнопкой мыши на чистом месте и в появившемся меню выбираем "Схема данных" (или в главном меню выбираем "Сервис \ Схема данных"). В появившемся окне "Добавление таблицы" выделяем все таблицы и нажимаем кнопки "Добавить" и "Закрыть". В окне "Схема данных" добавленные таблицы можно перетаскивать, располагая удобным способом. Выделив поле "Код туриста" в таблице "Туристы" и не отпуская левой кнопки мыши, перетащим его на поле "Код туриста" таблицы "Информация о туристах" (рис. 1.6).

Рис. 1.6. Создание связи между таблицами
После отпускания кнопки мыши появляется окно "Изменение связей", в котором отмечаем галочки "Обеспечение целостности данных", "Каскадное обновление связанных полей"" и "Каскадное удаление связанных записей", а затем нажимаем кнопку "Создать" (рис. 1.7):

Рис. 1.7. Определение параметров связи
Определение этих параметров позволит при изменении записи в одной таблице автоматически обновлять связанные записи в других таблицах. В окне появилась связь между таблицами, которая была создана Microsoft Access (рис. 1.8):

Рис. 1.8. Связь между таблицами
Эта связь была создана автоматически - так происходит тогда, когда две таблицы имеют одинаковые названия связанных полей и согласованные типы данных, причем хотя бы в одной из таблиц связанное поле является ключевым. Под согласованным типом данных понимается следующее: если ключевое поле имеет тип данных "Счетчик", то соответствующее ему поле в другой таблице должно иметь тип "Числовой". В остальных случаях типы данных должны просто совпадать.
Около полей "Код туриста" обеих таблиц на связи расположено изображение единицы, указывающее на принадлежность связи к отношению "один-к-одному".
Это означает, что одной записи в таблице "Туристы" будет соответствовать одна запись в таблице "Информация о туристах". Существуют также другие типы отношений - "один-ко-многим" и "многие-ко-многим". Отношение "один-ко-многим" далее появится у нас между таблицами "Информация о туристах" и "Путевки" - один турист может приобрести несколько путевок, что и находит логическое отражение в связи между таблицами. Другой возможный тип - "многие-ко-многим", его примером может служить связь между таблицами с преподавателями и предметами: один преподаватель может вести несколько предметов, но и один предмет могут вести несколько преподавателей. Для того чтобы рассмотреть практические примеры типов отношений, полезно открыть учебную базу данных "Борей 2)", входящую в комплект Microsoft Access 2003 (Справка \ Примеры баз данных \ Учебная база данных "Борей"), и отобразить схему базы данных.
В результате определения связей между таблицами должна получиться следующая схема базы данных (рис. 1.9):

увеличить изображение
Рис. 1.9. Схема данных базы BDTur_firm.mdb
Для дальнейшей работы с базой данных заполним ее вымышленными данными - достаточно будет создать по пять записей в каждой таблице. В окне базы данных дважды щелкнем на таблице "Туристы". Щелкая на значок "+" этой записи, можно отобразить и непосредственно вносить изменения в дочерних таблицах. Дочерние таблицы образуют группу, определенную в структуре базы данных. На рис. 1.10 приведена раскрытая группа таблиц - "Туристы" - "Информация о туристах" - "Путевки" - "Оплата" и соответствующая ветвь в структуре базы данных.

увеличить изображение
Рис. 1.10. Вложенная группа таблиц "Туристы" - "Информация о туристах" - "Путевки" - "Оплата" и соответствующая ветвь в структуре базы данных
Таблица "Туры" также содержит вложенную группу дочерних таблиц "Туры" - "Сезоны" - "Путевки" - "Оплата" (рис. 1.11).

увеличить изображение
Рис. 1.11. Вложенная группа таблиц "Туры" - "Сезоны" - "Путевки" - "Оплата" и соответствующая ветвь в структуре базы данных
При взгляде на ветви, приведенные на рис. 1.9 и рис. 1.10, появляется вопрос: почему в таблицах не возникает дочерних групп "Туристы" - "Информация о туристах" - "Путевки" - "Сезоны" - "Туры" (см рис. 1.9) или "Туры" - "Сезоны" - "Путевки" - "Информация о туристах" - "Туристы" (см. рис. 1.10)? Дело в том, что группы образуются при одностороннем переходе от таблицы со связью "один-ко-многим" или "один-к-одному", который условно можно изобразить так (таблица 1.3).
| Можно | 1 | "Туристы"1 - 1"Информация о туристах"1 - "Путевки" 1 - "Оплата" |
| 2 | "Туры" 1 - "Сезоны" 1 - "Путевки" 1 - "Оплата" | |
| Нельзя | 1 | "Туристы" 1 - "Информация о туристах" 1 - "Путевки"? -1"Сезоны"? -1"Туры" |
| 2 | "Туры" 1 - "Сезоны" 1 - "Путевки" -1"Информация о туристах" -1"Туристы" | |
| А так можно | 1 | "Туры" 1 - "Сезоны" 1 - "Путевки" |
| 2 | "Туристы"1 - "Информация о туристах"1 - "Путевки" |
Вносить данные можно сразу в группу таблиц, одновременно просматривая связанные записи в разных таблицах.
Мы создали базу данных Microsoft Access, которая может применяться для управления туристической фирмой. Непосредственное использование таблиц - простое их открытие и внесение данных - встречается крайне редко: отсутствие интерфейса, а главное - отсутствие защиты от случайных ошибок делает всю работу весьма ненадежной. Тем не менее саму базу данных можно считать готовой серверной частью двухуровневого приложения "клиент-сервер". СУБД Microsoft Access содержит все средства для эффективной разработки клиентской части приложения (форм, отчетов, страниц), рассмотрение которых, однако, выходит за рамки этого курса.
В программном обеспечении к курсу вы найдете файл базы данных Microsoft Access BDTur_firm.mdb (Code\Glava1\BDTur_firm.mdb).
Команды изменения языка DML
Значения могут быть помещены и удалены из полей тремя командами языка DML (Язык Манипулирования Данными):Команда insert имеет свои особенности:
Примеры:
insert into ClientInfo (FirstName, LastName, Address, Phone) values('Petr','Petrov','Chehova 13','1234567');
Однократное выполнение этого запроса (нажатие клавиши F5 один раз) приводит к добавлению одной записи. Добавляем еще несколько записей, изменяя значения value:
insert into ClientInfo (FirstName, LastName, Address, Phone) values('Ivan',Ivanov,'Naberejnaya 13','1234568');
insert into ClientInfo (FirstName, LastName, Address, Phone) values(null,'Sidorov','Naberejnaya 25','1234569');
Извлечем все записи созданной таблицы (рис. 1.45).
select * from ClientInfo;

Рис. 1.45. Все записи таблицы ClientInfo
Убедимся в том, что третья запись поля FirstName действительно содержит неопределенное значение null (а не строку NULL), c помощью запроса (рис. 1.46)
select * from ClientInfo where FirstName is null;

Рис. 1.46. Таблица ClientInfo действительно содержит запись со значением поля First Name "NULL"
Команда update позволяет изменять заданные значения записей:
update ClientInfo set FirstName = 'Andrey' where FirstName = 'Petr';
В этом случае в первой записи поля FirstName значение Petr изменится на Andrey (рис. 1.47):

Рис. 1.47. Изменение одной записи
Если не указывать значение, которое необходимо изменить, команда update затронет все записи (рис. 1.48).
update ClientInfo set FirstName = 'Andrey';

Рис. 1.48. Изменение всех записей
Команда delete позволяет изменять заданные значения записей.
delete from ClientInfo where LastName like 'Petrov';
Результатом этого запроса будет удаление первой записи из таблицы ClientInfo.
delete from ClientInfo;
Этот запрос удаляет все записи из таблицы, но не удаляет саму таблицу (рис. 1.49):

Рис. 1.49. Все записи удалены, но сама таблица осталась!
Запросы с командами insert, update и delete могут содержать в себе все прочие конструкции языка SQL.
![]() |
![]() |
![]() |
OLE - сокращение от Object Linking and Embedding (связывание и внедрение объектов) - применяется для обозначения технологии Microsoft, используемой для создания составных документов внедрением и связыванием.
2)
Слово "Борей" означает "Северный ветер". В комплект поставки Microsoft SQL Server и Microsoft Access традиционно входит база данных "Northwind", предназначенная специально для учебных целей. В переводе с англ. "Northwind" и есть северный ветер.
3)
SQL в оригинале произносится как "seequel". Впрочем, встречается и адаптированное произношение "эс-ку-эл(ь)".
4)
Если вы используете другую версию пакета Microsoft SQL, данные таблицы могут отличаться. Для определения соответствия импортируйте базу данных Types.mdb в свой пакет. База данных Types.mdb находится на диске, прилагаемом к книге (Code\Glava1 Types.mdb).
5)
Транзакция - серия запросов к базе, манипулирующая данными. Транзакцию, в отличие от обычного запроса, можно принять (и тогда вся серия запросов вступит в силу) или отклонить. Далее мы рассмотрим подробно это понятие.
6)
Здесь мы познакомимся только лишь с некоторыми понятиями языка SQL. Для полного изучения этого языка обращайтесь к специализированным руководствам.
7)
Вы можете также писать запросы, используя заглавные буквы, - SQL нечувствителен к регистру.
8)
Перед выполнением этого запроса убедитесь в том, что вы работаете с базой данных NorthwindCS. Или используйте команду use NorthwindCS.

Оператор сравнения like
Оператор сравнения like нужен для поиска записей по заданному шаблону. Это одна из наиболее часто встречаемых задач - например, поиск клиента с известной фамилией в базе данных.Предположим, что в таблице Customers требуется найти записи клиентов с фамилиями, начинающимися на букву "C" , и содержащие поля CustomerID, ContactName и Address:
select CustomerID, ContactName, Address from Customers where ContactName like 'C%';
Результатом этого запроса будет таблица (рис. 1.43)

Рис. 1.43. Запрос с оператором like
Оператор like содержит шаблоны, позволяющие получать различные результаты (таблица 1.5).
| like '5[%]' | 5% |
| like '[_]n' | _n |
| like '[a-cdf]' | a, b, c, d, или f |
| like '[-acdf]' | -, a, c, d, или f |
| like '[ [ ]' | [ |
| like ']' | ] |
| like 'abc[_]d%' | abc_d и abc_de |
| like 'abc[def]' | abcd, abce, и abcf |
Определение структуры базы данных в SQL Server Enterprise Manager
Для просмотра структуры таблицы выделяем нужную таблицу, щелкаем правой кнопкой мыши и в появившемся контекстном меню выбираем Design Table (рис. 1.19, А) . Аналогично, для просмотра содержимого в контекстном меню выбираем "Open Table \ Return all rows" ("Открыть таблицу \ Извлечь все записи") (рис. 1.19, Б).
увеличить изображение
Рис. 1.19. Просмотр структуры таблицы "Информация о туристах" (А) и ее содержимого (Б)
При просмотре импортированной таблицы в режиме дизайна обратите внимание на преобразование типов данных, - например, тип данных Microsoft Access "Текстовый" (Поле "Город" в таблице "Информация о туристах") был преобразован в тип данных nvarchar. Соотношения между конвертируемыми типами приведены в таблице 1.4:
| 1 | Текстовый | nvarchar | Тип данных для хранения текста до 4000 символов |
| 2 | Поле МЕМО | ntext | Тип данных для хранения символов в кодировке Unicode до 2^30 - 1 (1 073 741 823) символов |
| 3 | Числовой | int | Численные значения (целые) в диапазоне от -2^31 (-2 147 483 648) до 2^31 - 1 (+2 147 483 647) |
| 4 | Дата/время | smalldatetime | Дата и время от 1 января 1900 г. до 6 июня 2079 года с точностью до одной минуты |
| 5 | Денежный | money | Денежный тип данных значения которого лежат в диапазоне от -2^63 (-922 337 203 685 477.5808) до 2^63 - 1 (+922 337 203 685 477.5807), с точностью до одной десятитысячной |
| 6 | Счетчик | int | См. 3 |
| 7 | Логический | bit | Переменная, способная принимать только два значения - 0 или 1 |
| 8 | Поле объекта OLE | image | Переменная для хранения массива байтов от 0 до 231-1 (2 147 483 647) байт |
| 9 | Гиперссылка | ntext | См. 2 |
| 10 | Мастер подстановок | nvarchar | См. 1 |
Сведения о других типах данных Microsoft SQL можно найти в указателе справки SQL Server Enterprise Manager, щелкнув на кнопке
панели инструментов консоли управления. Чтобы установить связи, нам снова предстоит определить первичные ключи для записей в каждой таблице.
Открываем последовательно каждую таблицу в режиме структуры (Design Table), выделяя соответствующие поля, выбираем в контекстном меню Primary Key или нажимаем на кнопку
панели инструментов ( рис. 1.20):
Рис. 1.20. Определение первичного ключа
Установив первичные ключи для всех таблиц (см. табл. 1.2), переходим к построению схемы базы. Выбираем пункт Diagrams, щелкаем на нем правой кнопкой и выбираем пункт New Database Diagram. В появившемся мастере "Create Database Diagram Wizard" нажимаем кнопку "Далее". В следующем шаге мастера из общего списка, включающего в себя системные таблицы, выбираем пользовательские таблицы (рис. 1.21):

Рис. 1.21. Добавление пользовательских таблиц
Обратите внимание на галочку "Add related tables automatically" ("Добавлять связанные таблицы автоматически") - при выборе этой галочки в выбранные таблицы будут включаться все связанные ранее таблицы. У нас таких таблиц пока нет, поэтому нажимаем далее и завершаем работу мастера. Определение связей практически не отличается от аналогичного действия в Microsoft Access - следует "ухватиться" за одно поле и "перетащить" его на второе. При определении связи появляется окно "Create Relationship" (рис. 1.22), аналогичное окну Microsoft Access "Изменение связей" (сравните рис. 1.7).

Рис. 1.22. Определение связи между таблицами
В окне "Create Relationship" можно задать название связи в поле "Relationship name" (на рис. 1.22 приведено название по умолчанию), а также обеспечить каскадное обновление связанных полей, пометив галочку "Cascade Update Related Fields" и каскадное удаление связанных полей, пометив галочку "Cascade Delete Related Records". В этом окне указывается поле первичного ключа - "Primary key table", - в роли которого по-прежнему выступает столбец "Код туриста" таблицы "Туристы". В таблице "Информация о туристах" поле "Код туриста" выступает в качестве так называемого внешнего или вторичного ключа (Foreign key table).
Внешний ключ - это одно или несколько полей (столбцов) в таблице, содержащих ссылку на поле или поля первичного ключа в другой таблице. Внешний ключ определяет способ объединения таблиц.
После восстановления связей (см. рис. 1.9) схема данных будет выглядеть следующим образом (рис. 1.23):

увеличить изображение
Рис. 1.23. Схема базы данных BDTur_firmSQL
В отличие от схемы данных Microsoft Access, здесь линии, отображающие связи по умолчанию, не привязываются графически к первичным и вторичным полям. Однако при щелчке правой кнопкой на любой связи и последующем выборе пункта контекстного меню "Properties" открывается диалоговое окно, в котором сразу видны все отношения. На панели инструментов расположена кнопка "Show_"
, при нажатии на которую появляется список режимов отображения таблиц. Выделив все таблицы и установив режим "Keys" (Ключи), можно отобразить только первичные и вторичные ключи (рис. 1.24).
увеличить изображение
Рис. 1.24. Схема базы данных в режиме "Keys"
Завершив работу со схемой данных, сохраняем ее. В окне "Diagrams" может быть создано несколько различных схем одной и той же базы данных (рис. 1.25):

Рис. 1.25. SQL Server Enterprise Manager поддерживает много схем базы данных
Перенос файла Microsoft SQL на другой компьютер
В большинстве случаев вам придется разрабатывать приложения, использующие в качестве базы данных Microsoft SQL Server. Наиболее рациональным решением является разработка базы данных в формате Microsoft SQL на рабочем компьютере с установленной локальной версией Microsoft SQL Server. При сдаче проекта заказчику возникает необходимость переноса базы данных с локального компьютера. Как мы уже отмечали, файлы баз данных по умолчанию размещаются в каталоге C:\Program Files\Microsoft SQL Server\MSSQL\Data. Для переноса на другой компьютер нам потребуется скопировать два файла - саму базу данных BDTur_firmSQL.mdf и файл отчетов о транзакциях5) BDTur_firmSQL.ldf. Однако при попытке их простого копирования появляется ошибка при копировании - подобно си стемным файлам, они недоступны из операционной системы. Можно, конечно, скопировать нужные файлы из-под DOS, но к счастью, программа SQL Server Enterprise Manager предоставляет удобный интерфейс для этой задачи. Выделяем BDTur_firmSQL в окне программы и в контекстном меню выбираем "Все задачи \ Detach Database (Отсоединить базу)" (рис. 1.30).
Рис. 1.30. Отсоединение выбранной базы данных от сервера
Появляется диалоговое окно "Detach Database BDTur_firmSQL" в котором выводится количество текущих соединений к данной базе. Подтверждаем отсоединение, нажимая кнопку "ОК", - и база отсоединена. Теперь можно скопировать нужные файлы непосредственно.
После переноса файлов на другой компьютер желательно их расположить в папке по умолчанию. Для присоединения базы запускаем SQL Server Enterprise Manager на компьютере клиента, выделяем папку "Databases" и в контекстном меню выбираем "Все задачи \ Attach Database (Присоединить базу данных)" (рис. 1.31).

Рис. 1.31. Присоединение базы данных
В появившемся окне указываем расположение файла базы данных BDTur_firmSQL.mdf - файл отчетов присоединится автоматически, если он находится в той же самой директории, - и нажимаем "ОК". Присоединившаяся база данных немедленно отображается в папке "Databases".
В программном обеспечении к курсу вы найдете файлы, готовые для присоединения (Code\Glava1\ BDTur_firmSQL.mdf и BDTur_firmSQL.ldf).
Преобразование базы данных Microsoft Access в формат Microsoft SQL
Программа Microsoft Access с самого начала создавалась как средство управления и проектирования баз данных для офисной работы и задач небольших организаций. Ограничение максимального количества одновременно работающих пользователей (всего 255) делает невозможным использование базы данных даже для управления среднего по размерам Интернет-магазина или форума. Для обслуживания крупных проектов используются более мощные системы, например, Microsoft SQL3) Server. Рассмотрим вопрос преобразования базы данных Miscrosoft Access в формат Microsoft SQL.После установки пакета Microsoft SQL1 Server 2000 запускаем программу администрирования SQL Server Enterprise Manager ("Пуск \ Все программы \ Microsoft SQL Server \ Enterprise Manager"). Окно консоли управления, в которую загружается программа, разделяется на два окна, подобно проводнику Windows, в которые выводится содержимое сервера (рис. 1.12):

Рис. 1.12. Содержимое локального сервера в программе SQL Server Enterprise Manager
На рис. 1.12 выбрана папка "Databases", и поэтому в правом окне представлено семь баз данных - служебные: master, model, msdb, tempdb и учебные Northwind, NorthwindCS, pubs. Учебные базы данных не являются обязательными для функционирования сервера и предназначены лишь для изучения.
Для определения расположения файла базы данных, например Northwind, дважды щелкаем на нее и в появившемся окне Northwind Properties переходим на вкладку Data Files. По умолчанию при установке пакета определяется каталог C:\Program Files\Microsoft SQL Server\MSSQL\ Data (рис. 1.13):

Рис. 1.13. Расположение файла базы данных Northwind
Приступим к импорту базы данных Microsoft Access BDTur_firm.mdb. Щелкаем правой кнопкой на папке Databases и в появившемся меню выбираем "Все задачи \ Import Data" (рис. 1.14).

Рис. 1.14. Начало импорта базы данных
В появившемся мастере "Data Transformation Services Import/Export Wizard (DTS Import/Export Wizard)" нажимаем кнопку "Далее".
В следующем шаге мастера из выпадающего списка "Data Source" выбираем источник данных - Microsoft Access, затем указываем путь к файлу (нажимаем на кнопку (_)), а поля "Username" и "Password" оставляем пустыми (рис. 1.15). Нажимаем кнопку "Далее".

Рис. 1.15. Определение источника данных - Microsoft Access
В следующем шаге мастера предстоит определить, куда будет осуществляться импорт таблиц. В списке "Database" отображаются все имеющиеся базы данных на сервере - можно выбрать, например, базу данных pubs, однако выберем создание новой базы данных (рис. 1.16):

Рис. 1.16. Новая база данных в списке "Database"
В появившемся окне "Create Database" вводим имя базы данных - BDTur_firmSQL, также можно определить размер базы данных (Data file size) и размер файла отчетов (Log file size). Оставляем значения по умолчанию - размер базы данных BDTur_firm.mdb составляет всего 512 Кб - и нажимаем "OK" (рис. 1.16):

Рис. 1.17. Создание новой базы данных BDTur_firmSQL
Нажимаем кнопку "Далее", в шаге мастера "Specify Table Copy or Query" оставляем значение, предлагаемое по умолчанию, - "Copy table(s) and view(s) from the source databases" (копировать таблицу(ы) и представление(я) из источника данных) и снова нажимаем "Далее". В шаге мастера "Select Sources Tables and Views" выделяем все таблицы, нажав на кнопку "Select All" (рис. 1.18). Здесь же можно просмотреть содержимое выбранной таблицы (первые сто записей), нажав на кнопку "Preview_".

Рис. 1.18. Выбор таблиц для копирования
В шаге мастера "Save, schedule and replicate package" оставляем вариант немедленного запуска (Run immediately) и нажимаем далее. В последнем шаге мастера нажимаем кнопку "Готово". После подтверждения об успешном завершении задачи можно перезапустить SQL Server Enterprise Manager - в списке баз данных появится база данных BDTur_firmSQL, файл которой располагается в общем каталоге C:\Program Files\Microsoft SQL Server\MSSQL\Data.
Преобразование базы данных Microsoft SQL в формат Microsoft Access
Преобразование базы данных Microsoft SQL в один из распространенных форматов представляет собой довольно простую задачу. Преобразуем базу BDTur_firmSQL в формат Microsoft Access. Для этого выделяем ее, щелкаем правой кнопкой мыши и в появившемся меню выбираем Все задачи \ Export Data (см. рис. 1.14). Появляется уже знакомый нам мастер "Data Transformation Services Import/Export Wizard (DTS Import/Export Wizard)", в котором мы снова нажимаем кнопку "Далее". В шаге мастера "Choose a Data Source" ("Выбор источника данных") уже установлены верные значения: источник данных ("Data Source") - Microsoft OLE DB Provider for SQL Server, сервер - локальный ("Server"), а база данных ("Database") - BDTur_firmSQL (рис. 1.26):
Рис. 1.26. Выбор источника при экспорте базы данных
В следующем шаге мастера выбираем тип данных Microsoft Access ("destination") и указываем директорию, где предварительно создан файл Microsoft Access (я назвал его ExportBDTur_firm.mdb) (рис. 1.27).

Рис. 1.27. Определение файла для экспорта данных
В шаге "Specify Table Copy or Query" оставляем предложенное по умолчанию значение "Copy table(s) and view(s) from the source database" и нажимаем кнопку "Далее". В шаге "Select Source Tables and Views" выделяем все таблицы и переходим дальше. Выбираем тип "Запустить сейчас" ("Run immediately") и подтверждаем выполнение действий. Переходим к файлу ExportBDTur_firm.mdb - все таблицы были экспортированы, однако без схемы данных. Ее можно восстановить вручную.
В программном обеспечении к курсу вы найдете файл базы данных Microsoft Access ExportBDTur_firm.mdb (Code\Glava1\ ExportBDTur_firm.mdb).
Создание базы данных в SQL Server Enterprise Manager
Создать новую базу данных можно несколькими способами: нажать кнопку
"New Database" на панели инструментов, или кнопку
"New" при выделенной папке "Databases", выбрать пункт главного меню "Действие \ New Database", или выбрать пункт контекстного меню "New Database". Для первоначального знакомства можно также использовать Мастера создания базы - для этого следует нажать на кнопку
"Run Wizard", а затем выбрать из списка "Create Database Wizard". Поскольку применение мастера облегчает задачу предельным образом, рассмотрим непосредственное создание базы. В появившейся форме "Database Properties" вводим название базы, например "Somebase" (рис. 1.28).
Рис. 1.28. Свойства создаваемой базы данных
После этого на вкладках "Data Files" и "Transaction Log" доступны определения свойств файла базы данных и файла отчетов о транзакциях. Принимаем значения, предложенные по умолчанию, и нажимаем кнопку "ОК". В списке появляется новая база, раскрываем ее группу, выделяем "Tables" (таблицы) и в контекстном меню выбираем "New Table". Обратите внимание на созданные автоматически 20 таблиц - это системные объекты, необходимые для функционирования данной базы. Создание новой таблицы (рис. 1.29, А) очень похоже на аналогичный процесс в Microsoft Access, поэтому его мы рассматривать не будем. В отдельную таблицу можно также импортировать данные из файла - при выборе пункта меню "Все задачи \ Import Data" появляется уже знакомый нам мастер "Data Transformation Services Import/Export Wizard" (рис. 1.29, Б).

увеличить изображение
Рис. 1.29. Создание новой таблицы (А) или импорт данных (Б) в новой базе данных
При заполнении полей таблицы на первых порах достаточно сложно определить, какой тип данных использовать для заданного поля. Изучение базы данных Northwind, NorthwindCS, pubs а также сведений о самих типах данных, которые можно найти в справке, позволит разобраться с этой задачей.
Создание таблицы с помощью запросов
Вплоть до этого места мы выполняли команды по извлечению таблиц и данных из таблиц, полагая, что сами таблицы были созданы кем-то до нас. Это, действительно, наиболее реальная ситуация, когда небольшое количество людей создает таблицы, которые затем используются другими людьми.Тем не менее специальная область SQL, называемая DDL (Язык Определения Данных), специально работает с созданием объектов данных.
Таблицы создаются командой create table. Эта команда создает пустую таблицу - таблицу без строк. Команда create table в основном определяет имя таблицы, в виде описания набора имен столбцов, указанных в определенном порядке. Она также определяет типы данных и размеры столбцов. Каждая таблица должна иметь по крайней мере один столбец.
Синтаксис команды create table8):
create table ClientInfo ( FirstName varchar(20), LastName varchar(20), Address varchar(20), Phone varchar(15) );
Тип varchar предназначен для хранения символов не в кодировке Unicode. Число, указываемое в скобках, определяет максимальный размер поля и может принимать значение от 1 до 8000. Если введенное значение поля меньше зарезервированного, при сохранении будет выделяться количество памяти, равное длине значения. После выполнения этого запроса в окне "Messages" появляется сообщение
The command(s) completed successfully.
Перезапустите Query Analyzer. В базе данных NorthwindCS появилась созданная нами таблица (рис. 1.44):

Рис. 1.44. Созданная таблица находится в базе NorthwindCS
Итак, мы создали таблицу, состоящую из четырех полей типа varchar, причем для трех полей была определена максимальная длина 20 байт, а для одного - 15. Значение полей не заполнены - на это указывает величина Null.
Вы можете удалить созданную таблицу непосредственно в интерфейсе Query Analyzer, щелкнув правой кнопкой и выбрав "Delete".
Технология Microsoft ADO.NET
CommandText
Для извлечения таблиц и содержащихся в них данных используются SQL-запросы. Переменная CommandText содержит в себе SQL-запрос, синтаксис которого адаптирован для данного поставщика данных. Мы можем управлять извлечением данных, изменяя строку CommandText. Скопируйте папку проекта ProgrammDataSQL и назовите ее CommandText. Запустите проект и перейдите в код формы. Изменим string СommandText так, чтобы извлекать в DataGrid только поля CustomerID, ContactName, Country и Phone. Для этого удалим ненужные поля, в результате получится следующий SQL-запрос:string CommandText = "SELECT CustomerID, ContactName, Country, Phone FROM Customers";
Запустите приложение. Теперь на форму выводятся только соответствующие четыре поля (рис. 2.25):

Рис. 2.25. Ограничение выводимых полей
Выведем теперь все записи клиентов, имена которых начинаются на "М":
string CommandText = "SELECT CustomerID, ContactName, Country, Phone FROM Customers where ContactName like 'M%'";
Запускаем приложение. Запрос выбрал только записи на букву "M" (рис. 2.26).

Рис. 2.26. Ограничение выводимых полей
Вы можете использовать все возможности языка SQL для отбора данных и модификации строки CommandText для получения нужного результата. Не пробуйте только использовать команды insert, update или delete - изменение записей в базе данных мы рассмотрим позже.
В программном обеспечении к курсу вы найдете приложение СommandText (Code\Glava1\ СommandText).
ConnectionString
Строка соединения ConnectionString определяет параметры, необходимые для установления соединения с источником данных. Строка соединений при использовании мастеров генерируется средой, но можно (и желательно - во избежание неточностей и ошибок) писать эту строчку вручную. Рассмотрим еще раз строки соединения, которые были созданы при подключении к базам данных xtreme и Northwind.База данных xtreme, проект ProgrammDataMDB:
string ConnectionString = @"Jet OLEDB:Global Partial Bulk Ops=2; Jet OLEDB:Registry Path=;Jet OLEDB:Database Locking Mode=1; Jet OLEDB:Database Password=;Data Source=" "E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb"";Password=; Jet OLEDB:Engine Type=5;Jet OLEDB:Global Bulk Transactions=1;Provider=""Microsoft.Jet.OLEDB.4.0""; Jet OLEDB:System database=;Jet OLEDB:SFP=False;Extended Properties=; Mode=Share Deny None;Jet OLEDB:New Database Password=; Jet OLEDB:Create System Database=False;Jet OLEDB:Don' Jet Copy Locale on Compact=False; Jet OLEDB:Compact Without Replica Repair=False;User ID=Admin; Jet OLEDB:Encrypt Database=False";
База данных NorthwindCS, проект ProgrammDataSQL:
string ConnectionString = "workstation id=7EA2B2F6068D473;packet size=4096; integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False; initial catalog=NorthwindCS";
В этих строках через точку с запятой просто перечисляются параметры соединения. В таблице 2.1 приводятся основные значения этих параметров.
| Provider (Поставщик) | Свойство применяется для установки или возврата имени поставщика для соединения, используется только для объектов OleDbConnection |
| Connection Timeout или Connect Timeout (Время ожидания связи) | Длительность времени ожидания связи с сервером перед завершением попытки и генерацией исключения в секундах. По умолчанию 15 |
| Initial Catalog (Исходный каталог) | Имя базы данных |
| Data Source (Источник данных) | Имя используемого SQL-сервера, когда установлено соединение, или имя файла базы данных Microsoft Access |
| Password (Пароль) | Пользовательский пароль для учетной записи SQL Server |
| User ID (Пользовательский ID) | Пользовательское имя для учетной записи SQL Server |
| Workstation ID | Имя рабочей станции или компьютера |
| Integrated Security или Trusted Connection (Интегрированная безопасность или Доверительное соединение) | Параметр, который определяет, является ли соединение защищенным. True, False и SSPI - возможные значения. (SSPI - эквивалент True) |
| Persist Security Info (Удержание защитной информации | Когда установлено False, нуждающаяся в защите информация, такая как пароль, не возвращается как часть соединения, если связь установлена или когда-либо была установленной. Выставление этого свойства в True может быть рискованным в плане безопасности. По умолчанию False |
При создании мастером строки ConnectionString происходит генерирование большого количества лишних параметров. Нельзя сказать, что они не нужны, просто мастер предусматривает все возможности использования этого подключения и вставляет соответствующие значения. В действительности необходимых значений для простого извлечения данных всего несколько:
Скопируйте папки с проектами ProgrammDataMDB и Programm DataSQL. Переименуйте копии в ConnStringMDB и ConnStringSQL. Измените значения ConnectionString следующим образом:
База данных xtreme, проект ConnStringMDB:
string ConnectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0""; Data Source=""E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb""; User ID=Admin;Jet OLEDB:Encrypt Database=False";
База данных NorthwindCS, проект ConnStringSQL:
string ConnectionString = "workstation id=7EA2B2F6068D473;integrated security=SSPI; data source=\"(local)\";persist security info=False; initial catalog=NorthwindCS";
Мы значительно сократили количество параметров, получив прежнюю функциональность приложений.
Где определяется строка подключения, когда мы создаем объект DataAdapter не программно, а с помощью мастера? Среда генерирует строку подключения вместе с кодом для DataAdapter, и в этом нетрудно убедиться. Запустите приложение, которое мы делали, вообще ничего не зная об объектах ADO .NET - DataWizardMDB. Перейдите в код формы, откройте область Windows Form Designer generated code и найдите строку подключения:
this.oleDbConnection1.ConnectionString = @"Jet OLEDB:Global Partial Bulk Ops=2; Jet OLEDB:Registry Path=;Jet OLEDB:Database Locking Mode=1; Jet OLEDB:Database Password=;Data Source=" "D:\Uchebnik\Code\Glava4\RBProduct.mdb" ";Password=;Jet OLEDB:Engine Type=5; Jet OLEDB:Global Bulk Transactions=1;Provider=" "Microsoft.Jet.OLEDB.4.0""; Jet OLEDB:System database=;Jet OLEDB:SFP=False;Extended Properties=; Mode=Share Deny None;Jet OLEDB:New Database Password=; Jet OLEDB:Create System Database=False;Jet OLEDB:Don' Jet Copy Locale on Compact=False; Jet OLEDB:Compact Without Replica Repair=False; User ID=Admin;Jet OLEDB:Encrypt Database=False";
Когда мы переносим объект DataAdapter из панели инструментов Toolbox (со вкладки Data) на форму, вместе с ним образуется объект DBConnection, в свойствах которого указывается строка ConnectionString и другие параметры подключения (рис. 2.27):

увеличить изображение
Рис. 2.27. Свойства объекта sqlConnection1 проекта VisualDataSQL
DataAdapter
DataSet - это специализированный объект, содержащий образ базы данных. Для осуществления взаимодействия между DataSet и источником данных используется объект типа DataAdapter. Само название этого объекта - адаптер, преобразователь, - указывает на его природу. DataAdapter содержит метод Fill() для обновления данных из базы и заполнения DataSet.Использование визуальной среды для работы с ADO .NET
Когда мы перетаскиваем на форму элемент управления, Visual Studio .NET автоматически генерирует код, описывающий этот элемент. Если при размещении кнопок, текстовых полей и других элементов управления создавать их программно нецелесообразно, то при работе с элементами данных все как раз наоборот - лучше всего создавать все объекты ADO .NET вручную, что обеспечивает большую гибкость приложения. Тем не менее на первых порах использования ADO .NET полезно работать с визуальной средой разработки.Рассмотрим работу с базой данных Microsoft Acсess xtreme1), входящей в состав Microsoft Visual Studio.NET, и другую - с базой Microsoft SQL2) NorthwindCS . В каждой базе есть таблица Customer(s)3). Наша задача - вывести содержимое двух таблиц Customer на две Windows-формы.
| Microsoft Access, база данных xtreme | Microsoft SQL, база данных NorthwindCS |
Запустите Visual Studio .NET, создайте новый проект, тип проекта - Windows Application.
| Назовите его VisualDataMDB | Назовите его VisualDataSQL |
Размещаем на создавшейся форме элемент управления DataGrid, свойству Dock устанавливаем значение "Fill". Теперь в окне ToolBox переходим на закладку Data (рис. 2.8).

Рис. 2.8. На вкладке Data находятся все элементы управления для работы с ADO.NET
| Поместите на форму объект OleDbDataAdapter | Поместите на форму объект SqlDataAdapter |
Перед нами появляется мастер настройки элемента управления. Для OleDbDataAdapter этот мастер - тот же самый, который был описан и изображен на рис. 2.2 - 2.3. Вернитесь к этим рисункам и сконфигурируйте эти два шага OleDbDataAdapter самостоятельно. Для SqlDataAdapter на вкладке "Поставщик данных" по умолчанию выбран необходимый поставщик (рис. 2.9).

Рис. 2.9. Поставщик данных Microsoft OLE DB Provider for SQL Server
Нажимаем кнопку "Далее" (или переходим на вкладку "Подключение") (рис. 2.10).

Рис. 2.10. Определение параметров подключения
Для подключения к базе данных SQL, расположенной на вашем компьютере, в пункте 1 вводим (local) или просто точку - ".".
В пункте 2 используем учетные сведения системы: по умолчанию пароль учетной записи администратора SQL Server - пустой. В пункте 3 выбираем базу данных NorthwindCS.
В следующем шаге мастера - определение типа запроса. Параметры, предлагаемые мастером, будут отличаться (рис. 2.11 и рис. 2.12):

Рис. 2.11. Определение типа запросов объекта OleDbDataAdapter

Рис. 2.12. Определение типа запросов объекта SqlDbDataAdapter
Для базы данных Microsoft Access мы можем только использовать SQL-запросы - другие значения не активны. Для базы данных Microsoft SQL Server мы можем либо применять SQL-запросы, либо создать новую хранимую процедуру (stored procedure), либо использовать существующую хранимую процедуру. Различия подобного рода определяются типом баз данных. Для объекта SqlDataAdapter оставляем значение, предложенное по умолчанию: использование SQL-запросов.
В следующем шаге - создание SQL-запросов (Generate SQL statements) - мы можем вводить текст запроса непосредственно. Но удобнее всего воспользоваться "Построителем запросов": нажимаем на кнопку "Query Builder_" (рис. 2.13). Поскольку окна добавления таблиц для обеих баз данных одинаковы - незначительно различается лишь содержимое, - я привожу рисунок для OleDbDataAdapter:

Рис. 2.13. Добавление таблицы в окно Построителя запросов
Выбираем таблицу Customer (Customers), нажимаем кнопку Add и затем Close. Затем в окне Query Builder выбираем все столбцы (All Colimns), как показано на рис. 2.14:

Рис. 2.14. Добавление столбцов. Поставив галочку напротив пункта (All Columns), мы извлекаем все столбцы из таблицы
Нажимаем кнопку OK. В окне "Generate the SQl statements", к которому мы вернулись, приводится SQL- запрос, созданный мастером:
| SELECT Customer.* FROM Customer | SELECT Customers.* FROM Customers |
Нажимаем кнопку Next. В окне "Обзор результатов мастера" ( View Wizard Results) приводится список результатов работы мастера. Вид этого списка может отличаться, как это изображено на рис. 2.15 и 2.16:

Рис. 2.15. Список результатов работы мастера объекта OleDbDataAdapter

Рис. 2.16. Список результатов работы мастера объекта SqlDbDataAdapter
Чем обусловлено это различие? Для базы данных Access мастером создана возможность не только просмотра данных из базы данных - SQL-запрос SELECT, но и возможность ее изменения - запросы INSERT, UPDATE, DELETE. Для базы данных SQL Server создана лишь возможность просмотра базы данных. Для изменения данных необходимо проходить авторизацию (см. рис. 2.10) с учетной записью администратора базы данных.
Нажимаем кнопку Finish. Для объекта OleDbDataAdapter возникает окно (см. рис. 2.6), которое мы уже описывали выше.
Мастер создал на панели компонент по два элемента: oleDb DataAdapter1 и oleDbConnection1 для VisualDataMDB и sqlDbData Adapter1 и sqlDbConnection1 для VisualDataSQL. oleDbDataAdapter1 (или sqlDbDataAdapter1) является основным компонентом типа OleDbData Adapter (или SqlDataAdapter), который мы создавали при помощи мастера. Объект oleDbConnection1 (или sqlDbConnection1) был создан мастером как необходимый элемент подключения к базе. Образно говоря, мы получили адаптер (DataAdapter1) и вилку (Connection1) для подключения к источнику данных. Теперь нам необходимо добавить объект DataSet - тот самый "буфер", в котором будет храниться информация, полученная из базы данных. Щелкаем на oleDbDataAdapter1 (или sqlDbDataAdapter1), для его выделения. Открываем свойства этого элемента. На и нформационной панели щелкаем на ссылку "Generate DataSet" для создания объекта DataSet4) (рис. 2.17).

Рис. 2.17. Создание DataSet
В появившемся окне "Generate DataSet" задаем имена - dsCustomer и dsCustomers соответственно.
Итак, все "невидимые" части механизма подключения к базе данных у нас уже есть.
Осталось теперь связать видимый интерфейс с данными. Объектом, отображающим данные на форме, у нас является dataGrid1. В свойстве этого элемента DataSource (источник данных) выбираем в качестве источника созданный объект dsCustomer1 (рис. 2.18).

Рис. 2.18. Определение источника данных для элемента управления dataGrid1
На форме в режиме дизайна у нас немедленно появилась символическая таблица со знаком (+). Последнее, что нам осталось сделать, - это заполнить объект DataSet (здесь - dsCustomer1). Переходим в код форм и вносим соответствующие добавления:
using System.Data.OleDb; // Подключаем пространство имен для работы с поставщиком OleDb
private void Form1_Load(object sender, System.EventArgs e) { oleDbDataAdapter1.Fill(dsCustomer1); }
using System.Data.SqlClient; // Подключаем пространство имен для работы с поставщиком SQL
private void Form1_Load(object sender, System.EventArgs e) { sqlDataAdapter1.Fill(dsCustomers1); }
В обоих случаях в методе загрузки формы вызываем метод Fill объекта oleDbDataAdapter1 (или oleDbDataAdapter1) и передаем ему в качестве параметра dsCustomer1. Опять же, говоря образно, мы "заливаем" данные из адаптера в DataSet, который затем распоряжается ими - здесь мы определили, что DataSet выступает в качестве источника данных для dataGrid1.
Запустите приложения. Щелкните на знак (+), а затем на ссылку "Customer". Готовые приложения будут иметь следующий вид (рис. 2.19 и 2.20):

Рис. 2.19. Готовое приложение VisualDataMDB

Рис. 2.20. Готовое приложение VisualDataSQL
В программном обеспечении к курсу вы найдете эти приложения5) - Code\Glava1\VisualDataMDB и VisualDataSQL.
Модель объектов ADO .NET
Модель объектов ADO .NET состоит из набора классов. DataSet представляет собой класс ADO .NET, который отвечает за отображение таблицы (или таблиц, или даже всей базы) используемой базы данных, на компьютере пользователя без непрерывной связи с базой данных. В приложении, созданном с помощью Data Form Wizard, мы имели возможность вносить изменения в загруженную таблицу, перемещаться по записям, причем до нажатия кнопки Update изменения в самой базе данных, т.е. в файле RBProduct.mdb, не происходило. В чем же дело? Дело в том, что все данные и были загружены в объект DataSet, созданный мастером. Только при нажатии кнопки Update происходила передача данных из DataSet в саму базу данных. Не следует путать объект DataSet c оперативной памятью компьютера - загруженная таблица не находится в оперативной памяти компьютера.Представьте себе, что вы обслуживаете гипотетическую доску объявлений. Каждое утро вы берете обычный бумажный блокнот, записываете в него все свежие объявления, хранящиеся в централизованном банке данных, и отправляетесь к доске. Вы переписываете из блокнота на доску все объявления, а в блокнот записываете все те объявления, которые были добавлены на доску посетителями. Затем вы возвращаетесь в банк данных и вносите в него информацию, которую вы записали в блокнот c доски. Ваш блокнот и будет являться экземпляром класса DataSet. В этом процессе самым важным - для вас, конечно, не для централизованного банка данных, - будет ваш блокнот. Аналогично, класс DataSet является ключевым во всей модели классов ADO .NET.
DataSet состоит из объектов типа DataTable и объектов DataRelation. В коде к ним можно обращаться как к свойствам объекта DataSet, т.е., используя точечную нотацию. Свойство Tables возвращает объект типа DataTableCollection, который содержит все объекты DataTable используемой базы данных.
Объект Command
Объект Command применяется для выполнения SQL-запросов к источнику данных. Чтобы выполнить запрос, свойству Connection объекта Command следует задать объект имя созданного Connection:OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = ConnectionString; conn.Open(); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn;
Объект Connection также предоставляет метод CreateCommand, позволяющий упростить данный процесс - этот метод возвращает новый объект Command, уже инициализированный для использования вашего объекта Connection:
OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = ConnectionString; conn.Open(); OleDbCommand myCommand = conn.CreateCommand();
Эти два способа совершенно эквивалентны.
Теперь нам следует определить SQL-запрос, который будет извлекать данные. Как и раньше, строкой, в которой будет содержаться этот запрос, будет CommandText. Объявляем переменную CommandText, извлекающую все столбцы таблицы Customer:
string сommandText = @"SELECT Address1, Address2, City, [Contact First Name], [Contact Last Name], [Contact Position], [Contact Title], Country, [Customer Credit ID], [Customer ID], [Customer Name], [E-mail], Fax, [Last Year's Sales], Phone, [Postal Code], Region, [Web Site] FROM Customer";
Объект myСommand имеет свойство, которое так и называется - CommandText. Чтобы избежать путаницы, изменим название переменной - CommandText на commandText (с маленькой буквы):
myCommand.CommandText = commandText;
Создаем объект OleDbDataAdapter:
OleDbDataAdapter dataAdapter = new OleDbDataAdapter();
Объект dataAdapter имеет свойство SelectCommand в котором мы и будем указывать объект myCommand:
dataAdapter.SelectCommand = myCommand;
Создаем объект DataSet:
DataSet ds = new DataSet();
Заполняем ds данными из dataAdapter:
dataAdapter.Fill(ds, "Customers");
Указываем источник данных DataSource для dataGrid1:
dataGrid1.DataSource = ds.Tables["Customers"].DefaultView;
Закрываем соединение явным образом:
conn.Close();
Все! Запускаем приложение. Мы получили уже знакомый результат, но теперь мы действительно управляем всеми объектами, работающими с данными.
Полный листинг проекта ConnectionMDB:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.OleDb;
namespace ConnectionMDB { ///
string commandText = @"SELECT Address1, Address2, City, [Contact First Name], [Contact Last Name], [Contact Position], [Contact Title], Country, [Customer Credit ID], [Customer ID], [Customer Name], [E-mail], Fax, [Last Year's Sales], Phone, [Postal Code], Region, [Web Site] FROM Customer";
///
public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); OleDbConnection conn = new OleDbConnection(ConnectionString); //OleDbConnection conn = new OleDbConnection(); //conn.ConnectionString = ConnectionString; conn.Open(); OleDbCommand myCommand = new OleDbCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; //OleDbCommand myCommand = conn.CreateCommand(); OleDbDataAdapter dataAdapter = new OleDbDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Table ["Customers"].DefaultView; conn.Close();
// // TODO: Add any constructor code after InitializeComponent call // }
///
Windows Form Designer generated code
///
private void Form1_Load(object sender, System.EventArgs e) {
} } }
Сравните использование строк ConnectionString и connectingText объектом myCommand с аналогичным использованием объектом DataAdapter (ProgrammDataMDB и ProgrammDataSQL).
Создание объекта Connection для базы данных SQL принципиально не отличается от примера, рассмотренного выше. В следующем фрагменте кода описываются те же объекты Connection и Command:
using System.Data.SqlClient;
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 source=\"(local)\"; persist security info=False;initial catalog=NorthwindCS";
SqlConnection conn = new SqlConnection(connectionString); //SqlConnection conn = new SqlConnection(); //conn.ConnectionString = ConnectionString; conn.Open(); SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = CommandText; //SqlCommand myCommand = conn.CreateCommand(); SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Customers"); dataGrid1.DataSource = ds.Tables["Customers"].DefaultView; conn.Close();
В программном обеспечении к курсу вы найдете приложения ConnectionMDB и ConnectionSQL (Code\Glava1\ ConnectionMDB и ConnectionSQL).
У вас наверняка могло сложиться впечатление, что использование визуальных средств Microsoft Visual Studio .NET для добавления и конфигурирования объектов ADO .NET несравненно проще и логичней, чем программный способ создания этих объектов.
Однако это впечатление глубоко ошибочно. В действительности только последний пример может претендовать на завершенное приложение - все остальные являются своего рода вспомогательными модулями. Повторю, что только полностью вручную написанный код объектов ADO .NET позволяет создавать ясные, надежные и гибкие приложения.
![]() |
![]() |
![]() |
База данных Microsoft Access расположена по адресу: С(имя диска):\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb.
2)
Разумеется, на вашем компьютере должен быть установлен Microsoft SQL Server 2000 и запущено приложение Service Manager.
3)
В MS Access таблица называется Customer, в SQL - Customers.
4)
Можно также создать объект DataSet, "перетащив" его на форму из вкладки Data панели инструментов ToolBox.
5)
Если вы попытаетесь запустить эти приложения на своем компьютере, скорее всего, возникнет исключение. Дело в том, что параметры ConnectionString у нас с вами, безусловно, различаются. Для запуска следует изменить значение, соответствующее расположению базы данных (например, xtreme) на вашем компьютере. Как это сделать - см далее ConnectionString.
6)
Имя компьютера, расположение базы данных, разумеется, могут отличаться - эти значения верны только для моего компьютера.
7)
Здесь и далее - листинг указан без области Windows Form Designer generated code: когда весь пользовательский интерфейс состоит из единственного элемента DataGrid, нет особого смысла описывать его.

Объекты DataRelation
Объект DataSet имеет также свойство Relations, возвращающее коллекцию DataRelationCollection, которая в свою очередь состоит из объектов DataRelation. Каждый объект DataRelation выражает отношение между двумя таблицами (сами таблицы связаны по какому-либо полю (столбцу). Следовательно, эта связь осуществляется через объект DataColumn).Объекты DBConnection и DBCommand
Объект DBConnection осуществляет связь с источником данных. Эта связь может быть одновременно использована несколькими командными объектами. Объект DBCommand позволяет послать базе данных команду (как правило, команду SQL или хранимую процедуру). Объекты DBConnection и DBCommand иногда создаются неявно в момент создания объекта DataSet, но их также можно создавать явным образом.Подключение к базе данных - технология ADO .NET. Мастер Data Form Wizard
Мастер Data Form Wizard из Visual Studio .NET позволяет быстро, буквально в несколько шагов, создать связанную с данными форму. Код, генерируемый мастером, можно будет просматривать и изменять.Мы рассмотрим подключение к базе данных Microsoft Access BDTur_firm.mdb, которую мы создали сами. Наша задача: вывести содержимое двух связанных таблиц - "Туристы" и "Информация о туристах" на Windows-форму.
Запустите Visual Studio .NET, создайте новый проект, тип проекта - Windows Application. Назовите его DataWizardMDB.
В окне Solution Explorer щелкаем правой кнопкой мыши на имени проекта DataWizardMDB и в появившемся меню выбираем Add/Add New Item. В появившемся окне выбираем мастер Data Form Wizard, как показано на рис. 2.1:

Рис. 2.1. Создание Data Form Wizard
Называем новую форму DataFormMDB.cs. В появившемся мастере нажимаем кнопку "Next". В следующем шаге определяется объект DataSet, название которого должно соответствовать содержимому, поэтому называем его dsTourists. В следующем шаге мастера "Choose a data connection" требуется создать подключение к базе данных. Поскольку у нас еще нет никакого подключения, нажимаем на кнопку "New Connection". В появившемся окне "Свойства связи с данными" необходимо определить параметры создаваемого подключения. Переключаемся на вкладку "Поставщик данных" для выбора нужного поставщика OLE DB (рис. 2.2):

Рис. 2.2. Определение поставщика данных
Поставщик данных Microsoft Jet 4.0 OLE DB Provider позволяет подключаться к базам данных формата Microsoft Aceess (mdb). Нажимаем кнопку "Далее" (или переходим на вкладку "Подключение") (рис. 2.3 ):

Рис. 2.3. Задание параметров подключения
В пункте 1 нажимаем кнопку (...) и указываем путь к базе данных BDTur_firm.mdb, расположенную на вашем компьютере. В пункте 2 можно оставить значение по умолчанию - если вы специально не изменяли имя пользователя и пароль доступа к базе, то стандартное имя "Admin" позволит создать подключение.
Если вы правильно настроили права доступа к базе данных, то при нажатии кнопки "Проверить подключение" система выдаст сообщение "Проверка подключения выполнена". На вкладках "Дополнительно" и "Все" можно устанавливать права доступа и просматривать все свойства формируемого подключения. Не изменяя ничего на этих вкладках, нажимаем "OK".
Далее нам предстоит определить, какую таблицу базы данных мы собираемся извлекать. В окне мастера перечислены все доступные в схеме базы данных таблицы, представления и хранимые процедуры. Выберите в списке Available Items нужные таблицы и переместите их в список Selected Items, щелкнув кнопку с направленной вправо стрелкой. Если вы ошиблись и хотите удалить какие-то таблицы из списка Selected Items, выберите их и щелкните кнопку со стрелкой влево. Кроме того, добавлять и удалять таблицы можно, дважды щелкнув их название мышью. Выбираем две таблицы: "Туристы" и "Информация о туристах" (рис. 2.4):

Рис. 2.4. Выбор таблиц, которые будут отображаться на форме
Теперь необходимо определить отношение между таблицами. Отношения позволяют обеспечивать соблюдение правил ссылочной целостности, каскадно передавая изменения от одной таблицы к другой. Кроме того, они упрощают поиск данных в таблицах. Название отношения рекомендуется составлять из имен родительской и дочерней таблиц (именно в таком порядке). В качестве имени выберем TouristsInfoTourists. Далее определяем родительскую таблицу (Parent table) - "Туристы" и дочернюю (Child table) - "Информация о туристах". Поле "Код туриста" определяет связь между этими таблицами. Щелкните кнопку со стрелкой вправо, чтобы добавить отношение в список Relations, и затем щелкните Next.

Рис. 2.5. Задание связей между таблицами
Далее определяем столбцы (поля), извлекаемые из обеих таблиц. Оставляем все поля (рис. 2.5).
В последнем шаге мастера предстоит определить вид размещения данных на форме - всех записей в виде таблицы (All records in a grid) либо каждой записи в отдельном текстовом поле (Single records in individual control).
Выбираем второй вариант. Здесь же можно определить наличие дополнительных элементов управления - кнопок навигации и изменения записей. Оставляем эти значения по умолчанию. Завершаем работу мастера, нажимая кнопку "Finish".
Возникает сообщение - "Пароль будет сохранен в виде текста и будет доступен для чтения в коде и сборке". Выбрав "Include password", вы включаете пароль в приложение (он будет доступен), но избавляетесь от необходимости введения имени пользователя и пароля каждый раз при подключении к базе данных (рис. 2.6).

Рис. 2.6. Выбор расположения пароля
Практически все готово. Однако, запустив приложение, мы обнаруживаем, что по-прежнему появляется пустая форма Form1. Переходим в код Form1 и копируем следующий участок кода:
[STAThread] static void Main() { Application.Run(new Form1()); }
В коде DataFormMDB после Windows Form Designer generated code вставляем код и изменяем название запускаемой формы:
static void Main() { Application.Run(new DataFormMDB ()); }
В окне Solution Explorer щелкаем правой кнопкой на Form1.cs и удаляем его. Теперь, запуская приложение, мы видим DataFormMDB (рис. 2.7):

Рис. 2.7. Готовая форма
В созданном приложении имеется несколько кнопок. Для загрузки данных нажимаем кнопку Load. Для перемещения по записям используем навигационные кнопки. При этом во второй таблице отображается список продуктов, поставляемых данным поставщиком. Для добавления новой или удаления текущей записи служат кнопки Add и Delete, для отмены изменений текущей записи - Cancel. Все изменения буферизуются и могут быть отменены нажатием кнопки Cancel All. Для передачи изменений в базу данных нажимаем кнопку Update - тогда записи изменяются в самом файле BDTur_firm.mdb.
Подключение к базе данных с помощью мастера позволяет создать приложение без всякого знания технологии ADO .NET. Конечно же, подобная разработка не может нас устраивать для коммерческих приложений, требующих несравненно более гибкого кода.DataForm Wizard всего лишь генерирует код, который можно просматривать и изменять в соответствии с задачами проекта.
В программном обеспечении к курсу вы найдете приложение DataWizardMDB (Code\Glava1 DataWizardMDB).
Программирование объектов ADO .NET
Мы рассмотрели создание приложений для работы с базами данных с использованием различных мастеров. Однако разрабатывать такие приложения можно также без использования визуальной среды. Создадим такие же приложения, как и в предыдущем примере.Будет работать с теми же самыми таблицами.
| Microsoft Access, база данных xtreme Таблица Customer | Microsoft SQL, база данных NorthwindCS Таблица Customers. |
Запустите Visual Studio .NET, создайте новый проект, тип проекта - Windows Application.
| Назовите его ProgrammDataMDB | Назовите его ProgrammDataSQL |
Размещаем на создавшейся форме элемент управления DataGrid, свойству Dock устанавливаем значение "Fill". Переходим в код формы. Подключаем соответствующие пространства имен:
| using System.Data.OleDb; | using System.Data.SqlClient; |
В конструкторе формы после InitializeComponent создаем объект DataAdapter:
public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); OleDbDataAdapter dataAdapter = new OleDbDataAdapter(CommandText, ConnectionString); }public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); SqlDataAdapter dataAdapter = new SqlDataAdapter(CommandText, ConnectionString);
}
В качестве параметров DataAdapter мы передаем CommandText и ConnectionString. Переменная типа string CommandText представляет собой обычный SQL-запрос на выборку из таблицы Customer, а переменная типа СonnectionString - это так называемая строка подключения, в которой указываются расположение базы данных, ее название, параметры авторизации и проч. Далее мы рассмотрим более подробно эти строки. Как составить эти переменные? Можно, конечно, написать вручную, но мы сейчас воспользуемся строчками, сгенерированными мастером. Откройте предыдущие проекты - VisualDataMDB и VisualDataSQL. Перейдите в код формы. Раскройте область Windows Form Designer generated code, щелкнув на знак (+). Найдите следующие строчки:
Для CommandText:
this.oleDbSelectCommand1.CommandText = @"SELECT Address1, Address2, City, [Contact First Name], [Contact Last Name], [Contact Position], [Contact Title], Country, [Customer Credit ID], [Customer ID], [Customer Name], [E-mail], Fax, [Last Year's Sales], Phone, [Postal Code], Region, [Web Site] FROM Customer";this.sqlSelectCommand1.CommandText = "SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax FROM Customers";
Для ConnectionString6):
this.oleDbConnection1.ConnectionString = @" Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Registry Path=; Jet OLEDB:Database Locking Mode=1; Jet OLEDB:Database Password=;Data Source=""E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb" ";Password=;Jet OLEDB:Engine Type=5;Jet OLEDB:Global Bulk Transactions=1;Provider=""Microsoft.Jet.OLEDB.4.0"";Jet OLEDB:System database=;Jet OLEDB:SFP=False;Extended Properties=; Mode=Share Deny None;Jet OLEDB:New Database Password=;Jet OLEDB:Create System Database=False;Jet OLEDB:Don'Jet Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;User ID=Admin;Jet OLEDB:Encrypt Database=False";this.sqlConnection1.ConnectionString = "workstation id=7EA2B2F6068D473;packet size=4096;integrated security=SSPI; data source=\"(local) \";persist security info=False; initial catalog=NorthwindCS";
Скопируйте эти строчки, начиная от названия самих переменных, затем в коде форм ProgrammDataMDB и ProgrammDataSQL в классе Form 1 объявите две переменные по две переменных типа string и вставьте скопированные значения:
string CommandText = @"SELECT Address1, Address2, City, [Contact First Name], [Contact Last Name], [Contact Position], [Contact Title], Country, [Customer Credit ID], [Customer ID], [Customer Name], [E-mail], Fax, [Last Year's Sales], Phone, [Postal Code], Region, [Web Site] FROM Customer";string ConnectionString = @"Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Registry Path=;Jet OLEDB:Database Locking Mode=1; Jet OLEDB:Database Password=;Data Source=""E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb"";Password=; Jet OLEDB:Engine Type=5;Jet OLEDB:Global Bulk Transactions=1;Provider=""Microsoft.Jet.OLEDB.4.0""; Jet OLEDB:System database=;Jet OLEDB:SFP=False;Extended Properties=; Mode=Share Deny None;Jet OLEDB:New Database Password=; Jet OLEDB:Create System Database=False;Jet OLEDB:Don' Jet Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False; User ID=Admin;Jet OLEDB:Encrypt Database=False";string CommandText = "SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City, Region, PostalCode, Country, Phone, Fax FROM Customers";string ConnectionString = "workstation id=7EA2B2F6068D473;packet size=4096;integrated security=SSPI;data sou" + "rce=\"(local)\";persist security info=False;initial catalog=NorthwindCS";
Обратите внимание на названия переменных CommandText и ConnectionString. Когда мы создаем объект DataAdapter, в качестве параметров мы можем передать названия строк, таких как cmdText и conString, или даже cmt и cns - совершенно равноправно, не забыв, конечно же, назвать также эти переменные в классе Form1. Но сама среда Visual Studio .NET генерирует эти строки именно с такими названиями - CommandText и ConnectionString, поэтому если вы пишите их не вручную, то облегчаете работу, называя их так же, как и среда.
Возвращаемся к нашим приложениям - ProgrammDataMDB и ProgrammDataSQL. Дальнейший код будет совершенно одинаковым для обоих приложений.
Итак, создаем объект DataSet:
DataSet ds = new DataSet();
Заполняем таблицу Customer объекта ds данными из базы:
dataAdapter.Fill(ds, "Customer");
Cвязываем источник данных объекта dataGrid1 (который мы нанесли на форму в режиме дизайна) с таблицей Customer объекта ds:
dataGrid1.DataSource = ds.Tables["Customer"].DefaultView;
Все! Запускаем оба приложения. Если вы были внимательны в самом начале, то заметили, что в базе данных xtreme таблица называется Customer, а в базе данных NorthwindCS - Customers ( "s" на конце). Тем не менее код работает для обоих приложений. В чем же дело? Таблица, которую мы называем "Customer", при вызове метода Fill объекта dataAdapter может быть названа как угодно - ее содержимое будет представлять собой извлекаемую таблицу из базы данных. При указании источника данных (DataSource) для объекта dataGrid1 мы ссылаемся именно на таблицу Customer, которая была создана при вызове метода Fill. Точнее говоря, эта таблица определена в объекте DataSet, а именно к нему мы и обращаемся для заполнения данными нашей формы в соответствии с моделью ADO .NET. Чтобы убедиться в этом, попробуйте такой код:
DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Bezimyannaya"); dataGrid1.DataSource = ds.Tables["Bezimyannaya"].DefaultView;
По-прежнему все работает.
Означает ли это, что можно небрежно относиться к названию таблицы в DataSet? Нет - в нашем учебном примере мы с самого начала извлекали только одну таблицу из базы данных. В реальных приложениях приходится иметь дело с десятками таблиц, и будет возникать большая путаница, если называть таблицы как попало. Поэтому следует давать названия таблицам в объекте DataSet названия тех таблиц, образы которых они представляют.
Полный7) листинг программы - подключение к базе данных xtreme:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.OleDb;
namespace ProgrammDataMDB { ///
public Form1() {
// // Required for Windows Form Designer support // InitializeComponent();
OleDbDataAdapter dataAdapter = new OleDbDataAdapter(CommandText, ConnectionString); DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Bezimyannaya"); dataGrid1.DataSource = ds.Tables["Bezimyannaya"].DefaultView; // // TODO: Add any constructor code after InitializeComponent call // }
///
Windows Form Designer generated code
///
private void Form1_Load(object sender, System.EventArgs e) {
} } }
Полный листинг программы - подключение к базе данных NorthwindCS:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.SqlClient;
namespace ProgrammDataSQL { ///
public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); SqlDataAdapter dataAdapter = new SqlDataAdapter(CommandText, ConnectionString); DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Customer"); dataGrid1.DataSource = ds.Tables["Customer"].DefaultView;
// // TODO: Add any constructor code after InitializeComponent call // }
///
Windows Form Designer generated code
///
private void Form1_Load(object sender, System.EventArgs e) {
} } }
Server Explorer
В состав Visual Studio .NET входит замечательный инструмент управления и обзора подключениями к базам данных - Server Explorer. С его помощью можно практически мгновенно создавать приложения, использующие базы данных. Создайте новый проект. Назовите его Server_Explorer. Выберите в меню View пункт Server Explorer (или воспользуйтесь сочетанием клавиш Ctrl+Alt+S). Появится окно Server Explorer. Щелкните на Data Connections правой кнопкой мыши и выберите пункт "Add Connection" (рис. 2.21):
Рис. 2.21. Добавление соединения
Появляется окно мастера "Свойства связи с данными", с которым мы уже встречались (см. рис. 2.2 и 2.3). Создайте самостоятельно подключение к базе данных xtreme - после того как вы это сделаете, оно появится в списке всех подключений окна Server Explorer (рис. 2.22):

увеличить изображение
Рис. 2.22. В окне Server Explorer выводятся все созданные подключения к базам данных
Щелкните на знак (+) около названия подключения, откройте Tables, выберите таблицу Customer и перетащите ее на форму (рис. 2.23).

Рис. 2.23. Содержимое базы данных xtreme
При появлении уже знакомого окна расположения пароля (см. рис. 2.6) выбираем "Include Password".
На панели компонент формы появились два элемента: oleDb Connection1 и oleDbDataAdapter1. Выделяем щелчком oleDbDataAdapter1, открываем его свойства, на информационной панели нажимаем "Generate DataSet". Называем объект "DataSet dsCustomer" и нажимаем "ОК". Помещаем на форме элемент управления DataGrid, в свойствах этого элемента указываем расположение Dock - Fill, DataSource - dsCustomer и переходим в код формы. Добавляем уже знакомый нам код:
private void Form1_Load(object sender, System.EventArgs e) { oleDbDataAdapter1.Fill(dsCustomer1); }
Запускаем приложение. Его внешний вид ничем не отличается от приложения, рассмотренного в предыдущем примере.
Созданное подключение к базе данных xtreme теперь будет отображаться во всех последующих проектах в окне Server Explorer. Вам остается только выбирать нужную таблицу и перетаскивать ее на форму.
Вы также можете использовать окно Solution Explorer для быстрого просмотра содержимого баз данных и - если подключение было создано с правами администратора - изменения их. Откройте нужную таблицу и просто дважды щелкните на нее (рис. 2.24):

увеличить изображение
Рис. 2.24. Просмотр таблицы Customers базы данных NorthwindCS
Строки (объект DataRow)
Коллекция Rows объекта DataTable возвращает набор строк (записей) заданной таблицы. Эта коллекция используется для изучения результатов запроса к базе данных. Мы можем обращаться к записям таблицы как к элементам простого массива.Таблицы и поля (объекты DataTable и DataColumn)
Объекты DataTable используются для представления таблиц в DataSet. DataTable представляет одну таблицу из базы данных. В свою очередь, DataTable составляется из объектов DataColumn.DataColumn - это блок для создания схемы DataTable. Каждый объект DataColumn имеет свойство DataType, которое определяет тип данных, содержащихся в каждом объекте DataColumn. Например, вы можете ограничить тип данных до целых, строковых и десятичных чисел. Поскольку данные, содержащиеся в DataTable, обычно переносятся обратно в исходный источник данных, вы должны согласовывать тип данных с источником.
Управление соединением. Объект Connection
Большинство источников данных поддерживает ограниченное количество соединений. Так, база данных Microsoft Access может поддерживать одновременную работу не более чем с 255 пользователями. При попытке обращения к базе данных, лимит соединений которой исчерпан, пользователь не получит нужной ему информации и будет вынужден ждать освобождения соединения. Задача разработчика заключается в минимизации времени связи с базой данных, поскольку соединение занимает полезные системные ресурсы.Когда вы вызываете у объекта DataAdapter метод (например, Fill), то он сам проверяет, открыто ли соединение. Если соединения нет, то DataAdapter открывает соединение, выполняет задачи и затем закрывает соединение.
Явное управление соединением - лучший подход к работе с базами данных. Он обладает рядом преимуществ:
Для явного управления соединением используется объект Connection. Создайте новый проект и назовите его ConnectionMDB. Перетащите элемент управления DataGrid из панели инструментов Toolbox и установите свойству Dock значение Fill. Перейдите в код формы. Подключаем пространство имен:
using System.Data.OleDb;
В конструкторе Form1 после InitializeComponent создаем объект Connection:
OleDbConnection conn = new OleDbConnection(ConnectionString);
В качестве параметра объекту conn передается строка подключения ConnectionString.
Можно также устанавливать строку подключения через свойство созданного объекта сonn:
OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = ConnectionString;
Теперь нам необходимо определить параметры самой строки ConnectionString. Вы можете сделать это вручную или скопировав код из приложения ConnStringMDB (подключаться будем к базе данных xtreme).
string ConnectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0""; Data Source=""E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb"";User ID=Admin;Jet OLEDB:Encrypt Database=False";
Теперь можно устанавливать соединение, вызывая метод Open объекта Connection:
OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = ConnectionString; conn.Open();
Технология Microsoft ADO.NET
Перемещение по записям. Объект CurrencyManager
Продолжим работу над проектом DataBindings. При выводе данных в виде отдельных записей необходимо реализовать возможность перемещения по записям. Это можно сделать с помощью экземпляра класса CurrencyManager. Располагаем на форме четыре кнопки и надпись в ряд следующим образом (рис. 3.4):
Рис. 3.4. Расположение кнопок
Устанавливаем следующие свойства элементов управления:
| Кнопка | btnFirst | << |
| Кнопка | btnPrevious | < |
| Кнопка | btnNext | > |
| Кнопка | btnLast | >> |
| Надпись | lblRecordsPosition |
Объявляем экземпляр cmRecords класса CurrencyManager в классе формы:
CurrencyManager cmRecords;
В конструкторе формы Form1 связываем созданный объект cmRecords с таблицей "Туристы" объекта ds:
cmRecords = (CurrencyManager)BindingContext[ds, "Туристы"];
Создаем обработчиков для событий ItemChanged и PositionChanged объекта cmRecords:
cmRecords.ItemChanged+=new ItemChangedEventHandler(cmRecords_ItemChanged); cmRecords.PositionChanged+=new EventHandler(cmRecords_PositionChanged);
Вызываем метод, отображающий навигацию по записям:
DisplayRecordsPosition ();
Создаем этот метод:
private void DisplayRecordsPosition() { lblRecordsPosition.Text = "Запись " + (cmRecords.Position + 1) + " из " + cmRecords.Count; }
Добавляем методы, вызывающие метод DisplayRecordsPosition() в случае наступления событий ItemChanged и PositionChanged:
private void cmRecords_ItemChanged( object sender, ItemChangedEventArgs e) { DisplayRecordsPosition(); } private void cmRecords_PositionChanged( object sender, System.EventArgs e) { DisplayRecordsPosition(); }
Добавляем обработчиков для нажатий навигационных кнопок:
private void btnFirst_Click(object sender, System.EventArgs e) { cmRecords.Position = 0; }
private void btnPrevious_Click(object sender, System.EventArgs e) { cmRecords.Position--; }
private void btnNext_Click(object sender, System.EventArgs e) { cmRecords.Position++; }
private void btnLast_Click(object sender, System.EventArgs e) { cmRecords.Position = cmRecords.Count - 1; }
Запускаем приложение. Теперь можно перемещаться по записям (рис. 3.5):

Рис. 3.5. Форма с навигационными кнопками
В программном обеспечении к курсу вы найдете приложение DataBindings (Code\Glava2\DataBindings).
После запуска этой программы выбираем
После запуска этой программы выбираем файл MS Access, пароль к которому нужно подобрать, - и пароль моментально появляется в главном окне (рис. 3.9).
Рис. 3.9. Вскрытие пароля файла MS Access
Далее нам снова понадобится вводить пароль - чтобы не тратить время, установите снова его значение равным "12345". Займемся теперь подключением к файлу базы данных из приложений. Конечно, вы теперь будете сомневаться, применять ли вообще задание пароля как средство безопасности, но нам в любом случае нужно научиться подключаться даже к такой, "ненадежной" базе. Создайте новый Windows-проект и назовите его "VisualBD_withPassword". Из окна Toolbox перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Переходим на вкладку Data дважды щелкаем на объекте OleDbDataAdapter. В появившемся мастере настраиваем подключение к файлу BD_withPassword.mdb. Теперь в окне "Свойства связи с данными" при проверке соединения появляется сообщение об ошибке (рис. 3.10):

Рис. 3.10. Ошибка проверки подключения, возникающая при отсутствии пароля
Исходя из текста ошибки, возникает естественное желание снять галочку "Пустой пароль" и в поле пароля ввести "12345". Но и на этот раз возникает ошибка2) (рис. 3.11):

Рис. 3.11. Ошибка проверки подключения, возникающая при неправильном указании пароля
Дело в том, что интерфейс вкладки "Подключение" предназначен для ввода параметров учетной записи при защите на уровне пользователей. Далее мы рассмотрим ее. Здесь же снова устанавливаем галочку "Пустой пароль" и переходим на вкладку "Все". Дважды щелкаем на свойстве Jet OLEDB:Database Password (или, выделив его, нажимаем на кнопку "Изменить значение"), в появившемся окне "Изменение значения свойства" вводим пароль "12345" (рис. 3.12).

Рис. 3.18. Определение объектов базы данных, которые будут защищены
Мы добрались до самих рабочих групп. Программа Microsoft Access предлагает несколько рабочих групп, в каждой из которых может быть большое число пользователей. К примеру, пять пользователей могут обладать полными правами, десять - быть разработчиками проекта и еще пять - обладать правами на обновление данных. Код группы также можно не запоминать. Мы выбираем группу "Только чтение", отмечая ее галочкой4), нажимаем кнопку "Далее" (рис. 3.19).

Рис. 3.19. Выбор рабочей группы
Теперь требуется определить права группы Users. Это группа в любом случае будет входить в файл BDWorkFile.mdw; по умолчанию пользователи, входящие в нее, могут работать с базой данных без всякого пароля. Предоставление каких-либо прав этой группе означает предоставление этих же прав любому пользователю. Поэтому из соображений безопасности Microsoft Access предлагает вариант по умолчанию. Изменение этого варианта означает встраивание "черного" входа в файл рабочей группы. Мы оставляем предложенное значение и нажимаем кнопку "Далее" (рис. 3.20):

Рис. 3.20. Определение разрешений группы Users
В следующем шаге мастера следует определить пользователей и пароли. Именно эти сведения для каждого пользователя будут постоянно использоваться в работе с приложением, поэтому на них следует обратить внимание. В поле "Пользователь:" вводим "Adonetuser", задаем этому пользователю пароль "12345", нажимаем кнопку "Добавить пользователя в список". Поля "Пользователь" и "Пароль" очищаются, а в списке, расположенном в левой части формы, появляется новая запись. Нажимаем кнопку "Далее" (рис. 3.21).

Рис. 3.21. Добавление пользователя "Adonetuser"
Итак, теперь у нас уже есть рабочая группа - "Только чтение", и теперь появился пользователь "Adonetuser". Из выпадающего списка "Группа или пользователь" следует выбрать "Adonetuser" и отметить галочкой группу "Только чтение" (рис. 3.22, А). При выборе второго пользователя из выпадающего списка - здесь5) "Chingiz Kariev", - можно заметить, что он входит в группу Admins (рис. 3.22, Б).
Это очень важный момент: должен быть хотя бы один пользователь-администратор, входящий в эту группу, в противном случае после завершения работы мастера мы не сможем добавлять новых пользователей и изменять права существующих!

Рис. 3.22. Распределение пользователей в рабочие группы. А - включение пользователя "Adonetuser" в группу "Только чтение", Б - вхождение пользователя "Chingiz Kariev" в группу "Admins" по умолчанию
В последнем шаге мастера создается резервная копия базы данных. Она располагается в той же самой директории, где и основная (рис. 3.23). Несмотря на свое расширение - *.bak (backup), это, по сути, обычный файл базы данных Microsoft Access.

Рис. 3.23. Создание резервной копии базы данных
При нажатии на кнопку "Готово" появляется отчет, создаваемый мастером защиты. Он включает в себя перечень всех сведений, которые в дальнейшем могут понадобиться для восстановления доступа к базе:
Отчет мастера защиты Данный отчет содержит все сведения, необходимые для воссоздания файла рабочей группы и восстановления доступа к защищенной базе данных в случае повреждения. Напечатайте или экспортируйте этот отчет и сохраните его в надежном месте.
Незащищенная база данных: D:\Uchebnik\Code\Glava2\BDwithUsers.bak Защищенная база данных: D:\Uchebnik\Code\Glava2\BDwithUsers.mdb Файл рабочей группы: D:\Uchebnik\Code\Glava2\BDWorkFile.mdw Пользователь: Microsoft Access Организация:
Код рабочей группы: mx4qX5Gy6OGUgwmOZpt
Защищенные объекты: Таблицы: Информация о туристах Оплата Путевки Сезоны Туристы Туры <Новые таблицы и запросы> Запросы: <Новые таблицы и запросы> Формы: <Новые формы> Отчеты: <Новые отчеты> Макросы: <Новые макросы> База данных: Пароль VBE не установлен Группы: Имя: Только чтение Код группы: FecIdp7S4zQTMaV5qAp Users: Adonetuser
Имя: Admins Код группы: <Созданные ранее> Users: Chingiz Kariev
Имя: Users Код группы: <Созданные ранее> Users: Chingiz Kariev Adonetuser
Пользователи: Имя: Chingiz Kariev Личный код: ifXdiQ2D2GaQvBhly Пароль: Группы: Admins
Имя: Adonetuser Личный код: U6QTwfQ5qGu1Djmrbe4 Пароль: 12345 Группы: Только чтение
Имя: admin Личный код: <Созданные ранее> Пароль: S0nxw3IDds5rO Группы: Users
Отчет мастера защиты Дата работы г.
Желательно последовать совету мастера и сохранить эти сведения в надежном месте.
Итак, в результате всех проделанных действий в рабочем каталоге появились три файла - BDwithUsers.bak, BDwithUsers.mdb и BDWorkFile.mdw (рис. 3.24):

Рис. 3.24. Файлы, полученные в результате работы мастера
В программном обеспечении к курсу вы найдете эти три файла (Code\Glava2\ BDwithUsers.bak, BDwithUsers.mdb и BDWorkFile.mdw).
Файл BDwithUsers.bak тоже лучше сохранить в надежном месте, поскольку он представляет собой незащищенную копию базы данных.
Запускаем файл BDWorkFile.mdw - появляется окно, в котором следует ввести имя пользователя "Adonetuser" и пароль "12345" (рис. 3.25):

Рис. 3.25. Аутентификация пользователя "Adonetuser"
Открывается окно базы данных, в котором имеющиеся таблицы доступны только для чтения. Выходим из приложения и запускаем его снова. Введем на этот раз имя администратора базы - "Chingiz Kariev", без пароля6) (рис. 3.26).

Рис. 3.26. Аутентификация пользователя "Chingiz Kariev"
На этот раз база данных открывается с полным доступом, более того, выбрав пункт главного меню "Сервис \ Защита \ Мастер_", можно редактировать уже существующий файл рабочей группы, добавляя например, новых пользователей (рис. 3.27):

Рис. 3.27. Первый шаг мастера. Изменение файла BDWorkFile.mdw рабочей группы
При создании файла рабочей группы BDWorkFile.mdw мы определили его дальнейшее использование по умолчанию (см. рис. 3.17). Это означает, что вся дальнейшая работа с программой Microsoft Access на данном компьютере будет производиться от имени этого файла и определенных в нем пользователей. Без наличия соответствующих прав будет невозможно создать даже новую базу данных7). Для возврата к файлу рабочей группы, принятому по умолчанию, проделываем следующие действия:

Рис. 3.28. Окно "Администратор рабочих групп"

Рис. 3.29. Уведомление присоединения файла рабочей группы

Рис. 3.30. Связь с файлом System.mdw в окне "Администратор рабочих групп"
В результате проделанных операций мы вернулись к файлу рабочей группы, принятому по умолчанию. Теперь при создании новых баз данных снова будем работать от имени администратора "Admin".
Однако мы не сможем открыть базу данных BDwithUsers.mdb, использующую другой файл рабочей группы (рис. 3.31):

Рис. 3.31. Попытка открыть базу данных BDwithUsers.mdb
Для открытия базы данных нам снова нужно будет связаться со своеобразным электронным ключом - файлом BDWorkFile.mdw. Сделайте это самостоятельно.
Для распространения подготовленной базы данных на компьютеры пользователей вам потребуется скопировать сам файл базы данных и файл рабочей группы, а затем связать их.
Изменим пароль администратора "Chingiz Kariev" базы BDwithUsers.mdb. Открываем от имени этого пользователя базу данных, в главном меню переходим "Сервис \ Защита \ Пользователи и группы_" (см. рис. 3.7). В появившемся окне "Пользователи и группы" из выпадающего списка "Имя" выбираем этого пользователя, переходим на вкладку "Изменение пароля". Оставляя пустым поле "Текущий пароль", вводим и подтверждаем пароль "a1s2d3f4g5h6j7k8l9z0" (рис. 3.32):

Рис. 3.32. Изменение пароля администратора базы данных
Аналогично, открывая базу от имени пользователя "Adonetuser", можно изменить его пароль.
Может показаться, что вся эта продуманная система разделения пользователей предоставляет надежную безопасность создаваемым приложениям. Но это не так - вся информация по-прежнему хранятся в незашифрованном виде в файле рабочей группы. Достаточно получить к этому файлу доступ - а для работы самой базы данных это необходимое условие, - чтобы получить все сведения о пользователях и их паролях. Утилита Access Password (http://www.thegrideon.com) позволяет считывать все данные (рис. 3.33):

увеличить изображение
Рис. 3.33. Вскрытие учетных записей пользователей MS Access
С помощью утилиты просмотрим также содержимое файла System.mdw. Здесь мы видим всего одного пользователя "admin"с пустым паролем, обладающего правами администратора (рис. 3.34):

увеличить изображение
Рис. 3.34. Просмотра файла System.mdw
Именно от этого пользователя по умолчанию мы начинаем работать с MS Access после установки пакета Microsoft Office.
Займемся подключением к базе из приложений. Создайте новый Windows-проект и назовите его "VisualBDwithUsers". Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Переходим на вкладку Data панели инструментов Toolbox, дважды щелкаем на объекте OleDbDataAdapter. В появившемся мастере настраиваем подключение к базе BDwithUsers.mdb, в поле "Пользователь" вводим "Adonetuser", в поле "Пароль" - значение "12345". Но при проверке подключения снова появляется сообщение об ошибке (рис. 3.35):

Рис. 3.35. Ошибка при проверке подключения к базе "BDwithUsers.mdb"
В чем же дело? Мы ведь ввели все значения в специально предназначенные для этого поля! Интерфейс вкладки "Подключение" снова не предоставляет возможности ввода всех нужных сведений для подключения. Переходим на вкладку "Все", выделяем свойство "Jet OLEDB:System database" и нажимаем кнопку "Изменить значение" (можно также дважды щелкнуть на этом свойстве). В появившемся окне "Изменение значения свойства" указываем путь к файлу BDWorkFile.mdw вместе с его полным названием (включающим расширение файла) (рис. 3.36).

увеличить изображение
Рис. 3.36. Определение значение свойства "Jet OLEDB:System database"
Переходим на вкладку "Подключение", проверяем связь с базой - на этот раз проверка будет успешной (рис. 3.37).

Рис. 3.37. Успешная проверка подключения к базе "BDwithUsers.mdb"
Нажимаем кнопку "OK" для закрывания окна "Свойства связи с данными". Теперь появляется дополнительное окно, в котором следует повторно ввести пароль пользователя (рис. 3.38).

Рис. 3.38. Дополнительно окно подключения к базе данных
В этом окне заметно "обрезанное" расположение подписей полей: это можно считать недоработкой VS 2003 - даже при разрешении экрана 1280х1024 форма выглядит так же.
Дополнительно в выпадающем списке " Режим открытия" выбираем значение DB_MODE_READ, поскольку пользователь "Adonetuser" обладает правами только на чтение данных. Закрываем это окно, завершаем работу мастера, настраивая извлечение всех записей из таблицы "Туристы". В последнем шаге мастера оставляем включение пароля в строку connection string (см. рис. 3.14).
На панели компонент формы выделяем объект DataAdapter, переходим в его окно Properties и нажимаем на ссылку Generate dataset. Оставляем название объекта DataSet, предлагаемое по умолчанию. В конструкторе формы заполняем объект DataSet, а также определяем источник данных для элемента DataGrid:
public Form1() { InitializeComponent(); oleDbDataAdapter1.Fill(dataSet11); dataGrid1.DataSource = dataSet11.Tables[0].DefaultView; }
Запускаем приложение. На форму снова выводятся данные из базы (см. рис. 3.15).
В программном обеспечении к курсу вы найдете приложение VisualB DwithUsers (Code\Glava2\ VisualBDwithUsers).
Сделаем точно такое же приложение без использования визуальных средств студии. Создайте новый Windows-проект, назовите его "ProgrammBDwithUsers". Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:
using System.Data.OleDb;
В классе формы создаем строки connectionString и commandText:
string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" ";Data Source=""D:\Uchebnik\Code\Glava2\BDwithUsers.mdb" ";Jet OLEDB:System database=""D:\Uchebnik\Code\Glava2 \BDWorkFile.mdw"";User ID=Adonetuser;Password=12345;";
string commandText = "SELECT * FROM Туристы";
В конструкторе формы создаем все объекты ADO .NET:
public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; OleDbDataAdapter dataAdapter = new OleDbDataAdapter(commandText, conn); DataSet ds = new DataSet(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; conn.Close(); }
В программном обеспечении к курсу вы найдете приложение ProgrammBDwithUsers (Code\Glava2\ ProgrammBDwithUsers).
Подключение к базе данных Microsoft
Microsoft Access предоставляет средства распределенного доступа к базе данных. С одним файлом могут одновременно работать большое количество пользователей, обладающих разными правами: одни могут только просматривать таблицы, другие - только вносить новые данные, и лишь администраторы базы обладают полным доступом. Когда мы устанавливаем пакет Microsoft Office на локальный компьютер и, ни о чем не задумываясь, начинаем создавать свою базу в программе Access, мы по умолчанию выступаем в роли администратора. Поставим теперь задачу: разделить доступ для двух пользователей - один сможет только просматривать данные (читать), другой по-прежнему будет обладать полным доступом. Скопируйте файл BDTur_firm.mdb и назовите его "BDwithUsers.mdb". Открываем базу, в главном меню программы переходим "Сервис \ Защита \ Мастер" (см. рис. 2.7). Появляется мастер защиты, в первом шаге которого доступен единственный переключатель "Создать файл рабочей группы" ( рис. 3.16):
Рис. 3.16. Первый шаг мастера защиты
Файл рабочей группы представляет собой своеобразный электронный ключ, в котором будут храниться созданные настройки. Он имеет расширение *.mdw. В следующем шаге мастера нажимаем кнопку "Обзор" - по умолчанию мы оказываемся в той же директории, где расположен исходный файл базы данных BDwithUsers.mdb, вводим название создаваемого файла BDWorkFile.mdw3). Нажимаем кнопку "Выбрать", возвращаясь в окно мастера. Устанавливаем переключатель на значение "Использовать файл рабочей группы по умолчанию". На другие параметры - "Код рабочей группы", "Ваше имя", "Организация" - можно не обращать внимания (рис. 3.17). Нажимаем кнопку "Далее".

увеличить изображение
Рис. 3.17. Определение файла рабочей группы BDWorkFile.mdw
Теперь предстоит определить, какие объекты базы данных защищены. Оставляем все таблицы и нажимаем кнопку "Далее" (рис. 3.18).

База данных, защищенная на уровне пользователей, может также содержать пароль на открытие. Скопируйте файл базы BDwithUsers.mdb и назовите его "BDwithUsersP.mdb". Открываем новый файл в монопольном режиме от имени пользователя "Chingiz Kariev". В главном меню программы переходим "Сервис \ Защита \ Задать пароль базы данных". В появившемся окне вводим пароль "98765" и подтверждаем его. Теперь при повторном открытии от имени любого пользователя необходимо вначале проходить аутентификацию, а затем вводить пароль базы данных (рис. 3.39):

Рис. 3.39. Аутентификация и ввод пароля на открытие базы
Базы данных BDwithUsers.mdb и BDwithUsersP.mdb используют один файл рабочей группы BDWorkFile.mdw. Просмотрев его содержимое при помощи программы Access Password, мы обнаружим, что его содержимое не изменилось (см. рис. 3.33). Этого и следовало ожидать - в файле рабочей группы хранятся сведения об учетных записях пользователей и группах, но не пароли самих баз данных. Для просмотра пароля базы открываем файл BDwithUsersP.mdb (рис. 3.40):

увеличить изображение
Рис. 3.40. Просмотр файла BDwithUsersP.mdb
Создайте новое Windows-приложение и назовите его "Programm BDwithUsersP". На этот раз создадим подключение программным образом. Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:
using System.Data.OleDb;
В классе формы создаем строки connectionString и commandText:
string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" ";Data Source=""D:\Uchebnik\Code\Glava2\BDwithUsersP.mdb" ";Jet OLEDB:System database=""D:\Uchebnik\Code\Glava2 \BDWorkFile.mdw"";User ID=Adonetuser;Password=12345; Jet OLEDB:Database Password=98765;"; string commandText = "SELECT * FROM Туристы";
Здесь мы добавили значения пароля "98765" в параметре "Jet OLEDB:Database Password".
Связывание элементов управления с данными
Представление данных в виде таблицы на форме достаточно удобно в ряде случаев: можно сразу просматривать большое количество записей. Однако для постоянного внесения изменений в базу данных более удобным будет индивидуальное представление записей, когда значение каждого поля находится в отдельном текстовом поле. Проект DataWizardMDB, рассмотренный в первой главе, представляет собой подобную форму.Создайте новое Windows-приложение. Назовите его "DataTextBox". Переходим на вкладку Data панели инструментов Toolbox и перетаскиваем oleDbDataAdapter. В запустившемся мастере устанавливаем подключение к файлу Microsoft Access "BDTur_firm.mdb" и выбираем все поля таблицы "Туристы". В свойствах oleDbDataAdapter1 на информационной панели нажимаем "Generate Dataset". Называем его "dsTourists". Переходим в код формы, подключаем пространство имен:
using System.Data.OleDb;
В конструкторе формы после InitializeComponent вызываем метод Fill объекта oleDbDataAdapter:
oleDbDataAdapter1.Fill(dsTourists1);
Переключаемся в режим дизайна. Располагаем на форме Label и TextBox. В свойстве Text элемента label вводим "Фамилия", в этом же свойстве элемента textBox оставляем пустую строку. Свойству Size формы устанавливаем значение 300; 100.
Щелкаем на знак (+) свойства DataBindings элемента управления textBox. В значении поля Text этой группы снова щелкаем на знак (+) около элемента dsProvider и выбираем поле "Фамилия" (рис. 3.1):

Рис. 3.1. Связывание элемента textBox с данными
Запускаем приложение. Теперь в текстовое поле выводится первое значение столбца "Фамилия" (рис. 3.2):

Рис. 3.2. Готовая форма
В программном обеспечении к курсу вы найдете приложение DataTextBox (Code\Glava2\ DataTextBox).
Теперь рассмотрим связывание элементов управления с данными, осуществляемое программным образом. Создайте новое Windows-приложение. Назовите его "DataBindings". На создавшейся форме располагаем по четыре элемента TextBox и Label (рис. 3.3):
Технология Microsoft ADO.NET
Обработка исключений
Подключение к базе данных представляет собой одно из слабых мест в работе программы. В силу самых разных причин клиент может не получить доступ к базе данных. Поэтому при создании приложения следует обязательно включать обработку исключений и возможность предоставления пользователю информации о них.Для получения специализированных сообщений при возникновении ошибок подключения к базе данных Microsoft SQL Server используются классы SqlException и SqlError. Объекты этих классов можно применять для перехвата номеров ошибок, возвращаемых базой данных (таблица 4.2):
| 17 | Неверное имя сервера |
| 4060 | Неверное название базы данных |
| 18456 | Неверное имя пользователя или пароль |
Дополнительно вводятся уровни ошибок SQL Server, позволяющие охарактеризовать причину проблемы и ее сложность (таблица 4.3):
| 11-16 | Ошибка, созданная пользователем | Пользователь должен повторно ввести верные данные |
| 17-19 | Ошибки программного обеспечения или оборудования | Пользователь может продолжать работу, но некоторые запросы будут недоступны. Соединение остается открытым |
| 20-25 | Ошибки программного обеспечения или оборудования | Сервер закрывает соединение. Пользователь должен открыть его снова |
Создайте новое Windows-приложение и назовите его "ExceptionsSQL". Свойству Size формы устанавливаем значение "600;380". Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Перетаскиваем элемент Panel, определяем следующие его свойства:
| Dock | Right |
| Location | 392; 0 |
| Size | 200; 346 |
На панели размещаем четыре текстовых поля, надпись и кнопку:
| Name | txtDataSource |
| Location | 8; 8 |
| Size | 184; 20 |
| Text | Введите название сервера |
| Name | txtInitialCatalog |
| Location | 8; 40 |
| Size | 184; 20 |
| Text | Введите название базы данных |
| Name | txtUserID |
| Location | 8; 72 |
| Size | 184; 20 |
| Text | Введите имя пользователя |
| Name | txtPassword |
| Location | 8; 104 |
| Size | 184; 20 |
| Text | Введите пароль1) |
| Location | 16; 136 |
| Size | 176; 160 |
| Text |
| Name | btnConnect |
| Location | 56; 312 |
| Size | 96; 23 |
| Text | Соединение |
Интерфейс приложения готов. Подключаем пространство имен для работы с базой данных:
using System.Data.SqlClient;
Объекты ADO .NET и весь блок обработки исключений помещаем в обработчик кнопки "Соединение":
private void btnConnect_Click(object sender, System.EventArgs e) { SqlConnection conn = new SqlConnection(); label1.Text = ""; try { //conn.ConnectionString = "workstation id=9E0D682EA8AE448;data source=\"(local) //\";" + "persist security info=True;initial catalog=Northwind; //user id=sa;password=12345";
//Строка ConnectionString в качестве параметров //будет передавать значения, введенные в текстовые поля: conn.ConnectionString = "initial catalog=" + txtInitialCatalog.Text + ";" + "user id=" + txtUserID.Text + ";" + "password=" + txtPassword.Text + ";" + "data source=" + txtDataSource.Text + ";" + "workstation id=9E0D682EA8AE448;persist security info=True;"; SqlDataAdapter dataAdapter = new SqlDataAdapter("SELECT * FROM Customers", conn); DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; } catch (SqlException OshibkiSQL) { foreach (SqlError oshibka in OshibkiSQL.Errors) { //Свойство Number объекта oshibka возвращает //номер ошибки SQL Server switch (oshibka.Number) { case 17: label1.Text += "\nНеверное имя сервера!"; break; case 4060: label1.Text += "\nНеверное имя базы данных!"; break; case 18456: label1.Text += "\nНеверное имя пользователя или пароль!"; break; } //Свойство Class объекта oshibka возвращает //уровень ошибки SQL Server, //а свойство Message - уведомляющее сообщение label1.Text +="\n"+oshibka.Message + " Уровень ошибки SQL Server: " + oshibka.Class; } } //Отлавливаем прочие возможные ошибки: catch (Exception ex) { label1.Text += "\nОшибка подключения: " + ex.Message; } finally { conn.Dispose(); } }
Закомментированная строка подключения содержит обычное перечисление параметров. При отладке приложения будет легче сначала добиться наличия подключения, а затем осуществлять привязку параметров, вводимых в текстовые поля. Запускаем приложение. При вводе неверных параметров в надпись выводятся соответствующие сообщения, а при правильных параметрах элемент DataGrid отображает данные (рис. 4.11):

увеличить изображение
Рис. 4.11. Готовое приложение ExceptionsSQL
В программном обеспечении к курсу вы найдете приложение Exceptions SQL (Code\Glava2\ ExceptionsSQL).
Скопируйте папку приложения ExceptionsSQL и назовите ее "ExceptionsMDB". Удаляем с панели на форме имеющиеся текстовые поля и добавляем три новых:
| Name | txtDataBasePassword |
| Location | 8; 16 |
| Size | 184; 20 |
| Text | Введите пароль базы данных |
| Name | txtUserID |
| Location | 8; 48 |
| Size | 184; 20 |
| Text | Введите имя пользователя |
| Name | TxtPassword |
| Location | 8; 80 |
| Size | 184; 20 |
| Text | Введите пароль пользователя |
using System.Data.OleDb;
Обработчик кнопки "Соединение" будет выглядеть так:
private void btnConnect_Click(object sender, System.EventArgs e) { OleDbConnection conn = new OleDbConnection(); label1.Text = ""; try { // conn.ConnectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0""; //Data Source=""D:\Uchebnik\Code\Glava2\BDwithUsersP.mdb""; Jet OLEDB:System database=""D:\Uchebnik\Code\Glava2\BDWorkFile.mdw""; User ID=Adonetuser;Password=12345;Jet OLEDB:Database Password=98765;";
//Строка ConnectionString в качестве параметров //будет передавать значения, введенные в текстовые поля: conn.ConnectionString = "Jet OLEDB:Database Password=" + txtDataBasePassword.Text + ";" + "User ID=" + txtUserID.Text + ";" + "password=" + txtPassword.Text + ";" +
@"Provider=""Microsoft.Jet.OLEDB.4.0"";Data Source=""D:\Uchebnik\Code\Glava2\BDwithUsersP.mdb""; Jet OLEDB:System database=""D:\Uchebnik\Code\Glava \BDWorkFile.mdw"";";
OleDbDataAdapter dataAdapter = new OleDbDataAdapter("SELECT * FROM Туристы", conn); DataSet ds = new DataSet(); conn.Open(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; } catch (OleDbException oshibka) { // Пробегаем по всем ошибкам for (int i=0; i < oshibka.Errors.Count; i++) { label1.Text+= "Номер ошибки " + i + "\n" + "Сообщение: " + oshibka.Errors[i].Message + "\n" + "Номер ошибки NativeError: " + oshibka.Errors[i].NativeError + "\n" + "Источник: " + oshibka.Errors[i].Source + "\n" + "Номер SQLState: " + oshibka.Errors[i].SQLState + "\n"; } } //Отлавливаем прочие возможные ошибки: catch (Exception ex) { label1.Text += "\nОшибка подключения: " + ex.Message; } finally { conn.Dispose(); } }
Запускаем приложение (рис. 4.12). Свойство Message возвращает причину ошибки на русском языке, поскольку установлена русская версия Microsoft Office 2003. Свойство NativeError (внутренняя ошибка) возвращает номер исключения, генерируемый самим источником данных. Вместе или по отдельности со свойством SQL State их можно использовать для создания переключателя, предоставляющего пользователю расширенную информацию (мы это делали в приложении ExceptionsSQL) .

Рис. 4.12. Готовое приложение ExceptionsMDB
Кроме обработки исключений, здесь следует обратить внимание на интерфейс, предоставляющий возможность пользователю вводить сведения своей учетной записи. Теперь в целях безопасности приложения не нужно включать пароль в код при использовании мастеров, поскольку строка соединения будет генерироваться динамически. Разумеется, ваше приложение может иметь другой интерфейс - например, окно соединения будет появляться в дочерней форме.
В программном обеспечении к курсу вы найдете приложение Exceptions MDB (Code\Glava2\ ExceptionsMDB).
Подключение к базе данных Microsoft SQL Server с разделенным доступом
Среда Microsoft SQL Server предоставляет средства разделенного управления объектами сервера. Для доступа используются два режима аутентификации: режим аутентификации Windows (Windows Authentication) и режим смешанной аутентификации (Mixed Mode Authentication). При установке первый режим предлагается по умолчанию, поэтому, скорее всего, ваш сервер сконфигурирован с его использованием (рис. 4.1):
Рис. 4.1. Режим аутентификации Windows предлагаемый по умолчанию при установке
В этом случае аутентификация пользователя осуществляется операционной системой Windows. Затем SQL Server использует аутентификацию операционной системы для определения уровня доступа. При подключении в окне "Свойства связи с данными" мы также указывали этот режим (рис. 4.2):

Рис. 4.2. Режим аутентификации Windows в окне "Свойства связи с данными"
Смешанный режим позволяет проводить аутентификацию пользователя как средствами операционной системы, так и с применением учетных записей Microsoft SQL Server. Для включения этого режима запускаем SQL Server Enterprise Manager, на узле локального сервера щелкаем правой кнопкой и выбираем пункт меню "Свойства". В появившемся окне "SQL Server Properties" переходим на вкладку "Security", устанавливаем переключатель в положение "SQL Server and Windows" (рис. 4.3).

увеличить изображение
Рис. 4.3. Включение режима смешанной аутентификации
После подтверждения изменений закрываем окно свойств. Раскрываем узел "Security" текущего сервера, выделяем объект "Logins". В нем мы видим две записи - "BULTIN\Администраторы" и "sa". Первая из них предназначена для аутентификации учетных записей операционной системы. Вторая - "sa" (system administrator) - представляет собой учетную запись администратора сервера, по умолчанию она конфигурируется без пароля. Для его создания щелкаем правой кнопкой мыши на записи, в появившемся меню выбираем пункт "Свойства".
В поле "Password" окна " SQL Server Login Properties" вводим пароль "12345" и подтверждаем его (рис. 4.4):

увеличить изображение
Рис. 4.4. Установка пароля на учетной записи "sa"
Займемся теперь подключением к заданной базе данных, например Northwind, от имени учетной записи "sa". Создайте новое Windows-приложение, назовите его "VisualSQLUser_sa". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". В окне Toolbox переходим на вкладку Data и дважды щелкаем на объекте SqlDataAdapter. В появившемся мастере создаем новое подключение. В окне "Свойства связи с данными" указываем название локального сервера (local), имя пользователя (sa) и пароль (12345), а также базу данных Northwind (рис. 4.5):

Рис. 4.5. Окно "Свойство связи с данными". Приложение VisualSQLUser_sa
Дополнительно мы установили галочку "Разрешить сохранение пароля". При этом его значение (12345) будет сохранено в виде текста в строке connectionString. Пока мы вынуждены это сделать - интерфейс нашего приложения не предусматривает возможность ввода пароля в момент подключения. Завершаем работу мастера "Data Adapter Configuration Wizard", настраивая извлечение всех записей из таблицы Customers. В последнем шаге мы снова соглашаемся сохранить пароль в виде текста (рис. 4.6).

Рис. 4.6. Диалоговое окно сохранения пароля
На панели компонент формы выделяем объект DataAdapter, переходим в его окно Properties и нажимаем на ссылку "Generate dataset". Оставляем название объекта DataSet, предлагаемое по умолчанию. В конструкторе формы заполняем объект DataSet, а также определяем источник данных для элемента DataGrid:
public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dataSet11); dataGrid1.DataSource = dataSet11.Tables[0].DefaultView; }
Запускаем приложение. На форму выводятся записи таблицы Customers (рис. 4.7):

Рис. 4.7. Готовое приложение VisualSQLUser_sa
В программном обеспечении к курсу вы найдете приложение VisualSQL User_sa (Code\Glava2\ VisualSQLUser_sa).
Сделаем точно такое же приложение без использования визуальных средств студии. Создайте новый Windows-проект, назовите его "ProgrammSQLUser_sa". Добавляем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:
using System.Data.SqlClient;
В классе формы создаем строки connectionString и commandText:
string connectionString = "workstation id=9E0D682EA8AE448; user id=sa;data source=\"(local)\";" + "persist security info=True;initial catalog=Northwind;password=12345"; string commandText = "SELECT * FROM Customers";
В конструкторе формы создаем все объекты ADO .NET:
public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlDataAdapter dataAdapter = new SqlDataAdapter(commandText, conn); DataSet ds = new DataSet(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; conn.Close(); }
В программном обеспечении к курсу вы найдете приложение Programm SQLUser_sa (Code\Glava2\ ProgrammSQLUser_sa).
В отличие от подключений к базе данных Microsoft Access здесь мы не встретили никаких сложностей. Вкладка "Подключение" в окне "Свойства связи с данными" действительно предоставляет все средства для подключения к базе данных Microsoft SQL Server. Для аутентификации пользователя достаточно указать его имя и пароль.
Работа с пулом соединений. Microsoft SQL Profiler
Подключение к базе данных требует затрат времени - в самом деле, необходимо установить соединение по каналам связи, пройти аутентификацию и лишь после этого можно выполнять запросы и получать данные. Клиентское приложение, взаимодействующее с базой данных и закрывающее каждый раз соединение при помощи метода Close, будет не слишком производительным: значительная часть времени и ресурсов будет тратиться на установку повторного соединения. А что, если использовать трехуровневую модель, при которой клиентское соединение будет взаимодействовать с базой данных через промежуточный сервер? В этой модели клиентское приложение открывает соединение через промежуточный сервер (рис. 4.13, А). После завершения работы соединение закрывается приложением, но промежуточный сервер продолжает удерживать его в течение заданного промежутка времени, например, 60 секунд. По истечении этого времени промежуточный сервер закрывает соединение с базой данных (рис. 4.13, Б). Если в течение этой минуты, например, после 35 секунд, клиентское приложение снова требует связи с базой данных, то сервер просто предоставляет уже готовое соединение, причем после завершения работы обнуляет счет времени и готов снова минуту ждать обращения (рис. 4.13, В).
увеличить изображение
Рис. 4.13. Трехуровневая модель соединения с базой данных. А - открытие соединения, начало отсчета, Б - Закрытие соединения промежуточным сервером по истечении минуты, В - Обращение клиентского приложения и предоставление сервером соединения в течении минуты ожидания
В результате использования этой модели сокращается время, необходимое для установки связи с удаленной базой данных. Можно провести аналогию со следующей ситуацией: представьте, что вам и вашим друзьям нужно поговорить с одним и тем же человеком на другом конце земного шара. Вы можете звонить по отдельности и потратить достаточно много времени на то, чтобы дозвониться до человека, дождаться, пока его позовут к телефону, и т.п.
А можно собраться вместе с друзьями и дозвониться один раз, затем просто передавать трубку друг другу для разговора. Телефон в данном случае будет выступать в качестве пула соединений. Вернемся к нашей модели. Промежуточный сервер тоже будет выступать в качестве пула соединений. Более того, если к нему будут обращаться несколько клиентских приложений (несколько друзей), использующих одинаковую базу данных и параметры авторизации (один человек на другом конце земного шара), то выигрыш времени будет еще более заметным.
При создании подключения с использованием поставщиков данных .NET автоматически создается пул соединений. При вызове метода Close соединения не разрывается, а по умолчанию помещается в пул. В течение 60 секунд соединение остается открытым, и если оно не используется повторно, поставщик данных закрывает его. Если же по каким-либо причинам нам необходимо закрывать соединение, не помещая его в пул, в строке соединения СonnectionString нужно вставить дополнительный параметр. Для поставщика OLE DB:
OLE DB Services=-4;
Для поставщика SQL Server:
Pooling=False;
Теперь при вызове метода Close соединение действительно будет разорвано.
Поставщик данных Microsoft SQL Server предоставляет также дополнительные параметры управления пулом соединений (таблица 4.4).
| Connection Lifetime | Время (в сек.), по истечении которого открытое соединение будет закрыто и удалено из пула. Сравнение времени создания соединения с текущим временем проводится при возвращении соединения в пул. Если соединение не запрашивается, а время, заданное параметром, истекло, соединение закрывается. Значение 0 означает, что соединение будет закрыто по истечении максимального предусмотренного тайм-аута (60 сек.) | 0 |
| Enlist | Необходимость связывания соединения с контекстом текущей транзакции2) потока | True |
| Max Pool Size | Максимальное число соединений в пуле. При исчерпании свободных соединений клиентское приложение будет ждать освобождения свободного соединения | 100 |
| Min Pool Size | Минимальное число соединений в пуле в любой момент времени | 0 |
| Pooling | Использование пула соединений | True |
Для задания значения параметра, отличного от принятого по умолчанию, следует явно включить его в строку ConnectionString.
Для слежения за процессом подключения к серверу и организацией пула соединений воспользуемся утилитой Profiler3), входящей в пакет Microsoft SQL Server 2000. Переходим в меню "Пуск" к группе Microsoft SQL Server и запускаем утилиту. В появившемся окне программы переходим "File \ New \ Trace" (или используем сочетание клавиш Ctrl+N). Появляется подключение к серверу. Это окно нам уже знакомо по работе с программой Query Analyzer. На этот раз подключимся к серверу от имени администратора "sa" (рис. 4.14):

Рис. 4.14. Подключение к серверу
Далее появляется окно Trace Properties (Свойства трассировки), в котором можно задать название трассировки, а также расположение файла для сохранения (галочка "Save to file") (рис. 4.15).

Рис. 4.15. Свойства трассировки
Нажимаем кнопку "Run" для начала работы. Появляется окно, в котором будет записываться все обращения к серверу. Запускаем приложение ExceptionsSQL, вводим данные для аутентификации и нажимаем кнопку "Соединение" - SQL Profiler немедленно зафиксирует обращение (рис. 4.16).

увеличить изображение
Рис. 4.16. Окно трассировки после подключения к серверу
Соединение по умолчанию помещается в пул, в окне трассировки мы не видим его разрыва - записи "Audit Logout". Ее можно увидеть, завершив работу с приложением ExceptionsSQL (рис. 4.17).

увеличить изображение
Рис. 4.17. Окно трассировки после завершения работы с приложением "ExceptionsSQL"
Открываем проект ExceptionsSQL в среде Visual Studio .NET, изменим строку соединения - отключим необходимость создания пула. Строка ConnectionString теперь будет выглядеть так (добавлен параметр "Pooling"):
conn.ConnectionString = "initial catalog=" + txtInitialCatalog.Text + ";" + "user id=" + txtUserID.Text + ";" + "password=" + txtPassword.Text + ";" + "data source=" + txtDataSource.Text + ";" + "workstation id=9E0D682EA8AE448;persist security info=True;Pooling=False";
Теперь при соединении с сервером соединение будет разрываться без помещения в пул - запись "Audit Logout" будет появляться сразу же (рис. 4.18):

увеличить изображение
Рис. 4.18. Окно трассировки после подключения к серверу без создания пула соединений
Помещение соединений в пул, включенное по умолчанию, - одно из средств повышения производительности приложений. Не следует без надобности отключать эту возможность.
![]() |
![]() |
![]() |
Для скрывания пароля при вводе можно в свойстве "PasswordChar" текстового поля ввести заменяющий символ, например, звездочку ("*").
2)
Описание транзакций см. в лекции 7.
3)
Подробное описание работы с этой утилитой вы можете найти здесь: http://www.intuit.ru/department/database/sqlserver2000/35/sqlserver2000_35.html

События объекта Connection
Класс Connection поддерживает несколько событий, позволяющих отслеживать статус соединения и получать уведомляющие сообщения для экземпляра этого класса. Описание событий приводится в таблице 4.1.| Disposed | Возникает при вызове метода Dispose экземпляра класса |
| InfoMessage | Возникает при получении информационного сообщения от поставщика данных |
| StateChange | Возникает при открытии или закрытии соединения. Поддерживается информация о текущем и исходном состояниях |
При вызове метода Dispose объекта Connection происходит освобождение занимаемых ресурсов и сборка мусора. При этом неявно вызывается метод Close.
Рассмотрим применение события StateChange и обработчик события Disposed. Создайте новое Windows-приложение и назовите его "ConnectionEventsSQL". Свойству Size формы устанавливаем значение "600;300". Помещаем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Добавляем элемент Panel, его свойству Dock устанавливаем значение "Bottom". На панели размещаем две надписи и одну кнопку, устанавливая следующие их свойства:
| Location | 8; 8 |
| Size | 208; 80 |
| Text |
| Location | 232; 8 |
| Size | 208; 80 |
| Text |
| Name | btnFill |
| Location | 488; 40 |
| Text | Заполнить |
Подключаем пространство имен для работы с базой:
using System.Data.SqlClient;
В классе формы создаем строки connectionString и commandText:
string connectionString = "workstation id=9E0D682EA8AE448; packet size=4096;integrated security=SSPI;data source=\"(local)\"; persist security info=False;initial catalog=Northwind"; string commandText = "SELECT * FROM Customers";
Объекты ADO .NET будем создавать в обработчике события Click кнопки "btnFill":
private void btnFill_Click(object sender, System.EventArgs e) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; //Делегат EventHandler связывает метод-обработчик conn_Disposed //с событием Disposed объекта conn conn.Disposed+=new EventHandler(conn_Disposed); //Делегат StateChangeEventHandler связывает метод-обработчик //conn_StateChange с событием StateChange объекта conn conn.StateChange+= new StateChangeEventHandler(conn_StateChange); SqlDataAdapter dataAdapter = new SqlDataAdapter(commandText, conn); DataSet ds = new DataSet(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; //Метод Dispose, включающий в себя метод Close, //разрывает соединение и освобождает ресурсы.
conn.Dispose(); }
Не забывайте про возможности IntelliSense - как всегда, для создания методов-обработчиков дважды нажимаем клавишу TAB (рис. 4.8):

Рис. 4.8. Автоматическое создание методов-обработчиков
В методе conn_Disposed просто выводим текстовое сообщение в надпись "label2":
private void conn_Disposed(object sender, EventArgs e) { label2.Text+="Событие Dispose"; }
При необходимости в этом методе могут быть определены соответствующие события. В методе conn_StateChange будем получать информацию о текущем и исходном состояниях:
private void conn_StateChange(object sender, StateChangeEventArgs e) { label1.Text+="\nИсходное состояние: "+e.OriginalState.ToString() + "\nТекущее состояние: "+ e.CurrentState.ToString(); }
Запускаем приложение. До открытия соединения состояние объекта conn было закрытым. В момент открытия текущим состоянием становится открытое, а предыдущим - закрытое. Этому соответствуют первые две строки, выведенные в надпись (рис. 4.9):

Рис. 4.9. Готовое приложение ConnectionEventsSQL
После закрытия соединения (вызова метода Dispose) текущим состоянием становится закрытое, а предыдущим - открытое. Этому соответствуют последние две строки, выводимые в надпись.
Конечно, в таком предельно простом приложении и так ясен статус соединения. Но сама идея может применяться в любых, сколь угодно сложных приложениях, когда необходимо определять статус одного из нескольких подключений.
В программном обеспечении к курсу вы найдете приложение Connection EventsSQL (Code\Glava2\ ConnectionEventsSQL).
Создадим теперь аналогичное приложение, использующее базу данных Microsoft Access. Для того чтобы не терять время на создание пользовательского интерфейса, скопируйте папку приложения Connection EventsSQL и назовите ее "ConnectionEventsMDB". Перейдем к редактированию кода. Подключаем пространство имен для работы с базой данных:
using System.Data.OleDb;
В классе формы создаем строки connectionString и commandText:
string connectionString = @"Provider=""Microsoft.Jet.OLEDB.4.0" "; Data Source=""E:\Program Files\Microsoft Visual Studio .NET 2003\Crystal Reports\Samples\Database\xtreme.mdb"";User ID=Admin;Jet OLEDB:Encrypt Database=False"; string commandText = "SELECT * FROM Customer";
Здесь мы снова будем подключаться к базе данных xtreme.mdb. Обработчик события Click кнопки "btnFill" примет следующий вид:
private void btnFill_Click(object sender, System.EventArgs e) { OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; //Делегат EventHandler связывает метод-обработчик conn_Disposed //с событием Disposed объекта conn conn.Disposed+=new EventHandler(conn_Disposed); //Делегат StateChangeEventHandler связывает метод-обработчик //conn_StateChange с событием StateChange объекта conn conn.StateChange+= new StateChangeEventHandler(conn_StateChange); OleDbDataAdapter dataAdapter = new OleDbDataAdapter(commandText, conn); DataSet ds = new DataSet(); dataAdapter.Fill(ds); dataGrid1.DataSource = ds.Tables[0].DefaultView; //Метод Dispose, включающий в себя метод Close, //разрывает соединение и освобождает ресурсы. conn.Dispose(); }
Обработчики conn_Disposed и conn_StateChange будут иметь в точности такой же вид. Запускаем приложение - на форму снова выводится статус соединения (рис. 4.10):

Рис. 4.10. Готовое приложение ConnectionEventsMDB
В программном обеспечении к курсу вы найдете приложение Connection EventsMDB (Code\Glava2\ ConnectionEventsMDB).
Технология Microsoft ADO.NET
Создание хранимых процедур в SQL Query Analyzer
Хранимая процедура - это одна или несколько SQL-конструкций, которые записаны в базе данных. Задача администрирования базы данных включает в себя в первую очередь распределение уровней доступа к ней. Разрешение выполнения обычных SQL-запросов большому числу пользователей может стать причиной неисправностей из-за неверного запроса или их группы. Чтобы их избежать, разработчики базы данных могут создать ряд хранимых процедур для работы с данными и полностью запретить доступ для обычных запросов. Такой подход при прочих равных условиях обеспечивает большую стабильность и надежность работы. Это одна из главных причин создания собственных хранимых процедур. Другие причины - быстрое выполнение, разбиение больших задач на малые модули, уменьшение нагрузки на сеть - значительно облегчают процесс разработки и обслуживания архитектуры "клиент-сервер".Сами базы данных используют огромное количество встроенных хранимых процедур для функционирования. Запустим программу SQL Query Analyzer1), входящую в пакет Microsoft SQL Server 2000. Создадим новый бланк (Ctrl +N) и введем в нем следующее:
exec sp_databases
В результате выполнения выводится список всех баз, созданных на данном локальном сервере (рис. 5.1):

увеличить изображение
Рис. 5.1. Программа SQL Query Analyzer. Выполнение запроса. Выделена процедура "sp_databases"
Мы запустили одну из системных хранимых процедур, которая находится в базе master. Ее можно найти в списке "Stored Procedures" базы - все системные хранимые процедуры имеют приставку "sp". Обратите внимание, что системные процедуры выделяются бордовым цветом и для многих из них не нужно указывать в выпадающем списке конкретную базу. Запустим еще одну процедуру:
exec sp_monitor
В результате ее выполнения выводится статистика текущего SQL-сервера (рис. 5.2).

Рис. 5.2. Статистика Microsoft SQL-Server
Для вывода списка хранимых процедур в учебной базе Northwind используем следующую процедуру:
USE Northwind exec sp_stored_procedures
Можно было, конечно, указать название и в выпадающем списке
. База Northwind содержит 38 хранимых процедур (рис. 5.3), большая часть из которых - системные. Для просмотра списка в других базах следует вызвать для них название этой же процедуры. 
Рис. 5.3. Вывод списка хранимых процедур базы данных Northwind
Перейдем к созданию своих собственных процедур. Скопируйте базу BDTur_firm.mdb из лекции 1, назовите ее "BDTur_firm2.mdb". Открываем ее в Microsoft Access и в названиях таблиц и полей удаляем все пробелы. Например, таблица "Информация о туристах" будет теперь называться так: "Информацияотуристах", а поле "Код туриста" станет полем "Кодтуриста". Затем конвертируем базу в формат Microsoft SQL и присоединяем ее к локальному серверу2). Запускаем SQL Query Analyzer, открываем чистый бланк и вводим запрос3):
create procedure proc1 as select Кодтуриста, Фамилия, Имя, Отчество from Туристы
Здесь create procedure - оператор, указывающий на создание хранимой процедуры, proc1 - ее название, далее после оператора as следует обычный SQL-запрос. Запускаем его - появляется сообщение:
The COMMAND(s) completed successfully.
Это означает, что мы все сделали правильно и команда создала процедуру proc1. Для просмотра результата вызываем ее:
exec proc1
Появляется уже знакомое нам извлечение всех записей таблицы "Туристы" со всеми записями (рис. 5.4):

Рис. 5.4. Результат запуска процедуры proc1
Как видите, создание содержимого хранимой процедуры не отличается ничем от создания обычного SQL-запроса. В таблице 5.1 приведены примеры хранимых процедур:
| 1 | create procedure proc1 as select Кодтуриста, Фамилия, Имя, Отчество from Туристы | exec proc1 | Вывод всех записей таблицы Туристы |
| Результат запуска | |||
![]() | |||
| 2 | create procedure proc2 as select top 3 Фамилия from туристы | exec proc2 | Вывод первых трех значений поля Фамилия таблицы Туристы |
| Результат запуска | |||
![]() | |||
| 3 | create procedure proc3 as select * from туристы where Фамилия = 'Андреева' | exec proc3 | Вывод всех полей таблицы Туристы, содержащих в поле Фамилия значение " Андреева " |
| Результат запуска | |||
![]() | |||
| 4 | create procedure proc4 as select count (*) from Туристы | exec proc4 | Подсчет числа записей таблицы Туристы |
| Результат запуска | |||
![]() | |||
| 5 | create procedure proc5 as select sum(Сумма) from Оплата | exec proc5 | Подсчет значений поля Сумма таблицы Оплата |
| Результат запуска | |||
![]() | |||
| 6 | create procedure proc6 as select max(Цена) from Туры | exec proc6 | Вывод максимального значения поля Цена таблицы Туры |
| Результат запуска | |||
![]() | |||
| 7 | create procedure proc7 as select min(Цена) from Туры | exec proc7 | Вывод минимального значения поля Цена таблицы Туры |
| Результат запуска | |||
![]() | |||
| 8 | create procedure proc8 as select * from Туристы where Фамилия like '%и%' | exec proc8 | Вывод всех записей таблицы Туристы, содержащих в значении поля Фамилия букву "и" (в любой части слова) |
| Результат запуска | |||
![]() | |||
| 9 | create procedure proc9 as select * from Туристы inner join Информацияотуристах on Туристы.КодТуриста= Информацияотуристах.КодТуриста | exec proc9 | Операция inner join объединяет записи из двух таблиц, если поле (поля), по которому связаны эти таблицы, содержат одинаковые значения. Общий синтаксис выглядит следующим образом: from таблица1 inner join таблица2 on таблица1.поле1 оператор_сравнения таблица2.поле2 |
| Результат запуска | |||
![]() | |||
| 10 | create procedure proc10 as select * from Туристы left join Информацияотуристах on Туристы.КодТуриста= Информацияотуристах.КодТуриста | exec proc10 | Прежде чем создать эту процедуру и затем ее извлечь, запускаем программу SQL Server Enterprise Manager, выделяем таблицу "Туристы" базы данных " BDTur_firm2". Щелкаем на ней правой кнопкой и в появившемся меню выбираем Open Table - Return all rows. Теперь добавляем запись - "Корнеев Глеб Алексеевич". В результате в таблице "Туристы" у нас получилось 6 записей, а в связанной с ней таблице "Информацияотуристах" - 5. В SQL Query Analyzer создаем хранимую процедуру и запускаем ее. Операция left join используется для создания так называемого левого внешнего соединения. С помощью объединения выбираются все записи первой (левой) таблицы, даже если они не соответствуют записям во второй (правой) таблице. Общий синтаксис имеет вид: from таблица1 left join таблица2 on таблица1.поле1 оператор_сравнения таблица2.поле2. Здесь в таблице "Информацияотуристах" нет связанной записи для туриста "Корнеев Глеб Алексеевич", поэтому соответствующие поля заполняются значениями null |
| Результат запуска | |||
![]() | |||
| 11 | create procedure proc11 as select * from Туристы right join Информацияотуристах on Туристы.КодТуриста= Информацияотуристах.КодТуриста | exec proc11 | Перед созданием этого запроса нам снова придется изменить таблицы. В SQL Server Enterprise Manager удаляем шестую запись в таблице "Туристы", добавляем шестую запись в таблицу " Информацияотуристах"(значения полей - см. на рисунке). Операция right join используется для создания правого внешнего соединения. С его помощью выбираются все записи второй (правой) таблицы, даже если они не соответствуют записям в первой (левой) таблице. Общий синтаксис имеет вид: from таблица1 right join таблица2 on таблица1.поле1 оператор_сравнения таблица2.поле2. |
| Результат запуска | |||
![]() | |||
На практике часто бывает нужно получить результаты запроса для определенного значения (параметра). Такие запросы называются параметризированными, а соответствующие процедуры создаются с параметрами. Например, для получения записи в таблице "Туристы" по заданной фамилии создаем следующую процедуру:
create proc proc_p1 @Фамилия nvarchar(50) as select * from Туристы where Фамилия=@Фамилия
После знака @ указывается название параметра и его тип. Мы выбрали nvarchar c количеством символов 50, поскольку в самой таблице для поля "Фамилия" установлен этот тип. Попытаемся запустить процедуру:
exec proc_p1
Появляется диагностическое сообщение (рис. 5.5):

Рис. 5.5. Сообщение при запуске процедуры exec proc_p1
Перевод этого сообщения: "Процедура 'proc_p1' ожидает параметр '@Фамилия', который не указан".
Запустим процедуру так:
exec proc_p1 'Андреева'
В результате выводится запись, соответствующая фамилии "Андреева" (рис. 5.6):

Рис. 5.6. Запуск процедуры proc_p1
Если мы укажем фамилию, которая не содержится в таблице, появится пустая запись (рис. 5.7):
exec proc_p1 'Сидоров'

Рис. 5.7. Запуск процедуры proc_p1. Фамилия не найдена
В таблице 5.2 приводятся примеры хранимых процедур с параметрами.
| 1 | create proc proc_p1 @Фамилия nvarchar(50) as select * from Туристы where Фамилия=@Фамилия | exec proc_p1 'Андреева' |
| Описание | ||
| Извлечение записи из таблицы "Туристы" с заданной фамилией | ||
| Результат запуска | ||
![]() | ||
| 2 | create proc proc_p2 @nameTour nvarchar(50) as select * from Туры where Название=@nameTour | exec proc_p2 'Франция' |
| Описание | ||
| Извлечение записи из таблицы "Туры" с заданным названием тура. Обратите внимание на название параметра "nameTour " - он может быть произвольным, не обязательно, чтобы он совпадал с заголовком столбца извлекаемой таблицы | ||
| Результат запуска | ||
![]() | ||
| 3 | create procedure proc_p3 @Фамилия nvarchar(50) as select * from Туристы inner join Информацияотуристах on Туристы.КодТуриста = Информацияотуристах.КодТуриста where Туристы.Фамилия = @Фамилия | exec proc_p3 'Андреева' |
| Описание | ||
| Вывод родительской и дочерней записей с заданной фамилией из таблиц "Туристы" и "Информацияотуристах" | ||
| Результат запуска | ||
![]() | ||
| 4 | create procedure proc_p4 @nameTour nvarchar(50) as select * from Туры inner join Сезоны on Туры.Кодтура=Сезоны.Кодтура where Туры.Название = @nameTour | exec proc_p4 'Франция' |
| Описание | ||
| Вывод родительской и дочерней записей с заданной названием тура из таблиц "Туры" и "Сезоны" | ||
| Результат запуска (изображение разрезано) | ||
![]() | ||
| 5 | create proc proc_p5 @nameTour nvarchar(50), @Курс float as update Туры set Цена=Цена/(@Курс) where Название=@nameTour | exec proc_p5 'Франция', 26 или exec proc_p5 @nameTour = 'Франция', @Курс= 26 Просматриваем изменения простым SQL - запросом: select * from Туры |
| Описание | ||
| Процедура с двумя входными параметрами - названием тура и курсом валюты. При извлечении процедуры они последовательно указываются. Поскольку в самом запросе используется оператор update, не возвращающий данных, то для просмотра результата следует извлечь измененную таблицу оператором select | ||
| Результат запуска | ||
(1 row(s) affected) После запуска оператора select:![]() | ||
| 6 | create proc proc_p6 @nameTour nvarchar(50), @Курс float = 26 as update Туры set Цена=Цена/(@Курс) where Название=@nameTour | exec proc_p6 'Таиланд' или exec proc_p6 'Таиланд', 28 |
| Описание | ||
| Процедура с двумя входными параметрами, причем один их них - @Курс имеет значение по умолчанию. При запуске процедуры достаточно указать значение первого параметра - для второго параметра будет использоваться его значение по умолчанию. При указании значений двух параметров будет использоваться введенное значение | ||
| Результат запуска | ||
| Запускаем процедуру с одним входным параметром: exec proc_p6 'Таиланд' Для просмотра используем оператор select: ![]() Запускаем программу SQL Server Enterprise Manager, восстанавливаем значение поля "Цена" для тура "Таиланд" и запускаем процедуру с двумя входными параметрами: ![]() exec proc_p6 'Таиланд', 28 Теперь используется введенное значение второго параметра: | ||
Процедуры с выходными параметрами позволяют возвращать значения, получаемые в результате обработки SQL-конструкции при подаче определенного параметра. Представим, что нам нужно получать фамилию туриста по его коду (полю "Кодтуриста"). Создадим следующую процедуру:
create proc proc_po1 @TouristID int, @LastName nvarchar(60) output as select @LastName = Фамилия from Туристы where Кодтуриста = @TouristID
Оператор output указывает на то, что выходным параметром здесь будет @LastName. Запустим эту процедуру, извлекая фамилию туриста, значение поля "Кодтуриста" которого равно "4":
declare @LastName nvarchar(60) exec proc_po1 '4', @LastName output select @LastName
Оператор declare нужен для объявления поля, в которое будет выводиться значение. Получаем фамилию туриста (рис. 5.8)

Рис. 5.8. Результат запуска процедуры proc_po1
Для задания названия столбца можно применить псевдоним:
declare @LastName nvarchar(60) exec proc_po1 '4', @LastName output select @LastName as 'Фамилия туриста'
Теперь столбец имеет заголовок (рис. 5.9):

Рис. 5.9. Результат запуска процедуры proc_po1. Применение псевдонима
В таблице 5.3 приводятся примеры хранимых процедур с входными и выходными параметрами.
| 1 | create proc proc_po1 @TouristID int, @LastName nvarchar(60) output as select @LastName = Фамилия from Туристы where Кодтуриста = @TouristID | declare @LastName nvarchar(60) exec proc_po1 '4', @LastName output select @LastName as 'Фамилия туриста' |
| Описание | ||
| Извлечение фамилии туриста по заданному коду | ||
| Результат запуска | ||
![]() | ||
| 2 | create proc proc_po2 @CountCity int output as select @CountCity = count(Кодтуриста) from Информацияотуристах where Город like '%рг%' | declare @CountCity int exec proc_po2 @CountCity output select @CountCity as 'Количество туристов, проживающех в городах %рг%' |
| Описание | ||
| Подсчет количества туристов из городов, имеющих в своем названии сочетание букв "рг". Следует ожидать число три (Екатеринбург, Оренбург, Санкт-Петербург) | ||
| Результат запуска | ||
![]() | ||
| 3 | create proc proc_po3 @TouristID int, @CountTour int output as select @CountTour = count(Туры.Кодтура) from Путевки inner join Сезоны on Путевки.Кодсезона = Сезоны.Кодсезона inner join Туры on Туры.Кодтура = Сезоны.Кодтура inner join Туристы on Путевки.Кодтуриста = Туристы.Кодтуриста where Туристы.Кодтуриста = @TouristID | exec proc_po3 '1', @CountTour output select @CountTour AS 'Количество туров, которые турист посетил' |
| Описание | ||
| Подсчет количества туров, которых посетил турист с заданным значением поля "Кодтуриста" | ||
| Результат запуска | ||
![]() | ||
| 4 | create proc proc_po4 @TouristID int, @BeginDate smalldatetime, @EndDate smalldatetime, @SumMoney money output as select @SumMoney = sum(Сумма) from Оплата inner join Путевки on Оплата.Кодпутевки = Путевки.Кодпутевки inner join Туристы on Путевки.Кодтуриста = Туристы.Кодтуриста where Датаоплаты between(@BeginDate) and (@EndDate) and Туристы.Кодтуриста = @TouristID | declare @TouristID int, @BeginDate smalldatetime, @EndDate smalldatetime, @SumMoney money exec proc_po4 '1', '1/20/2007', '1/20/2008', @SumMoney output select @SumMoney as 'Общая сумма за период' |
| Описание | ||
| Подсчет общей суммы, которую заплатил данный турист за определенный период. Турист со значением "1" поля "Кодтуриста" внес оплату 4/13/2007 | ||
| Результат запуска | ||
![]() | ||
| 5 | create proc proc_po5 @CodeTour int, @ChisloPutevok int output as select @ChisloPutevok = count(Путевки.Кодсезона) from Путевки inner join Сезоны on Путевки.Кодсезона = Сезоны.Кодсезона inner join Туры on Туры.Кодтура = Сезоны.Кодтура where Сезоны.Кодтура = @CodeTour | declare @ChisloPutevok int exec proc_po5 '1', @ChisloPutevok output select @ChisloPutevok AS 'Число путевок, проданных в этом туре' |
| Описание | ||
| Подсчет количества путевок, проданных по заданному туру | ||
| Результат запуска | ||
![]() | ||
drop proc proc1
Здесь proc1 - название процедуры (см. табл. 5.1).
Создание хранимых процедур в SQL Server Enterprise Manager
Программа SQL Server Enterprise Manager предоставляет графический интерфейс для работы с хранимыми процедурами, равно как и для других объектов базы данных. Для просмотра определенной процедуры базы данных BDTur_firm2 переходим на соответствующий узел, щелкаем правой кнопкой (или дважды левой) и в появившемся меню выбираем пункт "Свойства" (рис. 5.10, А). В появившемся окне "Stored Procedure Properties" выводится SQL-конструкция, для проверки синтаксиса которой нажимаем на "Check Syntax" (рис. 5.10, Б и В).
увеличить изображение
Рис. 5.10. Узел "Stored Procedures" в SQL Server Enterprise Manager. А - список хранимых процедур. Б - свойства выбранной процедуры. В - проверка синтаксиса
Для создания новой процедуры выбираем пункт меню "New Stored Procedure", появляется окно "Stored Procedure Properties" где можно вводить SQL-конструкцию.
Для быстрой разработки удобно применять мастер. На панели инструментов нажимаем на кнопку "Run a Wizard", в появившемся окне раскрываем узел "DataBase", переходим к заголовку "Create Stored Procedure Wizard" (рис. 5.11).

Рис. 5.11. Запуск мастера
В первом шаге мастера, в окне приветствия, нажимаем кнопку "Далее". Во втором шаге выбираем базу BDTur_firm2. Затем выбираем таблицу "Туристы" и отмечаем галочками три команды - insert, update, delete (рис. 5.12) - мастер создаст сразу три хранимых процедуры для вставки, обновления и удаления записей.

Рис. 5.12. Выбор таблицы и команд модификации
В последнем шаге мастера можно отредактировать создаваемые процедуры, нажав кнопку "Edit_" (рис. 5.13, А). В окне "Edit Stored Procedure Properties" в поле "Name" задается название текущей процедуры. Нажимая на кнопку "Edit SQL_", открываем SQL-конструкцию, сгенерированную мастером (рис. 5.13, Б и В).

увеличить изображение
Рис. 5.13. Настройка хранимой процедуры. А - переход от последнего шага мастера к режиму редактирования, Б - окно "Edit Stored Procedure Properties", В - SQL-конструкция создаваемой процедуры
Оставим все названия как есть, нажимаем кнопку "Готово". В результате в списке появляется три новых объекта (рис. 5.14).

Рис. 5.14. Появившиеся в списке "Stored Procedures" объекты
Мастер сгенерировал три SQL-конструкции, для insert_Туристы_1:
CREATE PROCEDURE [insert_Туристы_1] (@Кодтуриста_1 [int], @Фамилия_2 [nvarchar](50), @Имя_3 [nvarchar](50), @Отчество_4 [nvarchar](50))
AS INSERT INTO [BDTur_firm2].[dbo].[Туристы] ( [Кодтуриста], [Фамилия], [Имя], [Отчество]) VALUES ( @Кодтуриста_1, @Фамилия_2, @Имя_3, @Отчество_4) GO
Для update_Туристы_1:
CREATE PROCEDURE [update_Туристы_1] (@Кодтуриста_1 [int], @Фамилия_2 [nvarchar], @Имя_3 [nvarchar], @Отчество_4 [nvarchar], @Кодтуриста_5 [int], @Фамилия_6 [nvarchar](50), @Имя_7 [nvarchar](50), @Отчество_8 [nvarchar](50))
AS UPDATE [BDTur_firm2].[dbo].[Туристы]
SET [Кодтуриста] = @Кодтуриста_5, [Фамилия] = @Фамилия_6, [Имя] = @Имя_7, [Отчество] = @Отчество_8
WHERE ( [Кодтуриста] = @Кодтуриста_1 AND [Фамилия] = @Фамилия_2 AND [Имя] = @Имя_3 AND [Отчество] = @Отчество_4) GO
Для delete_Туристы_1:
CREATE PROCEDURE [delete_Туристы_1] (@Кодтуриста_1 [int], @Фамилия_2 [nvarchar], @Имя_3 [nvarchar], @Отчество_4 [nvarchar])
AS DELETE [BDTur_firm2].[dbo].[Туристы]
WHERE ( [Кодтуриста] = @Кодтуриста_1 AND [Фамилия] = @Фамилия_2 AND [Имя] = @Имя_3 AND [Отчество] = @Отчество_4) GO
Мы получили три хранимые процедуры для вставки, изменения и удаления записей в таблице "Туристы". Для процедур update_Туристы_1 и delete_Туристы_1 в условии WHERE (где) мастер добавил оператор AND (и) для объединения параметров запроса. Изменим его на оператор OR (или) для получения более гибкого запроса. В окне SQL Server Enterprise Manager дважды щелкаем на процедуре update_Туристы_1 и в появившемся окне свойств изменяем SQL-конструкцию:
... WHERE ( [Кодтуриста] = @Кодтуриста_1 OR [Фамилия] = @Фамилия_2 OR [Имя] = @Имя_3 OR [Отчество] = @Отчество_4) GO
Точно так же этот фрагмент будет выглядеть и для delete_Туристы_1. Прежде чем мы начнем проверять работу созданных процедур, сделаем поле "Код туриста" в таблице "Туристы" ключевым. Открываем таблицу в режиме дизайна, выделяем это поле, щелкаем правой кнопкой и выбираем пункт меню "Set Primary Key". Теперь в SQL Query Analyzer запускаем процедуру insert_Туристы_1 с параметрами, передающими значения полей новой записи:
exec insert_Туристы_1 @Кодтуриста_1 = 6, @Фамилия_2 = 'Смирнов', @Имя_3 = 'Валерий', @Отчество_4 = 'Константинович'
Появляется сообщение - одна запись добавлена4):
(1 row(s) affected)
Попытаемся добавить еще раз эту же запись - нажимаем F5. Поскольку мы задали ключевое поле, не допускающее дублирование значений, появляется сообщение об ошибке:
Server: Msg 2627, Level 14, State 1, Procedure insert_Туристы_1, Line 7 Violation of PRIMARY KEY constraint 'PK_Туристы'. Cannot insert duplicate key in object 'Туристы'. The statement has been terminated.
Изменим значение параметра "@Кодтуриста":
exec insert_Туристы_1 @Кодтуриста_1 = 7, @Фамилия_2 = 'Смирнов', @Имя_3 = 'Валерий', @Отчество_4 = 'Константинович'
Еще одна запись будет добавлена:
(1 row(s) affected)
Выведем все записи:
select * from Туристы
В таблице появились две новые записи (рис. 5.15):

Рис. 5.15. Таблица "Туристы". Добавление записей
Теперь изменим в последней записи фамилию "Смирнов" на "Тихонов". Для этого запускаем процедуру update_Туристы_1 следующим образом:
exec update_Туристы_1 @Кодтуриста_1 = 7, @Фамилия_2 = 'Смирнов', @Имя_3 = 'Валерий', @Отчество_4 = 'Константинович', @Кодтуриста_5 = 7, @Фамилия_6 = 'Тихонов', @Имя_7 = 'Валерий', @Отчество_8 = 'Константинович'
Снова появляется сообщение о изменении записи:
(1 row(s) affected)
Здесь первые четыре параметра задают текущие значения, а следующие четыре указывают новые.
В результате получаем следующие записи в таблице (рис. 5.16):

Рис. 5.16. Таблица "Туристы". Изменение записей
Для удаления записей, поскольку мы задали оператор OR, можно вызвать процедуру delete_Туристы_1, передавая параметры следующим образом:
exec delete_Туристы_1 @Кодтуриста_1 = 7, @Фамилия_2 = 'Тихонов', @Имя_3 = 'Валерий', @Отчество_4 = 'Константинович'
(1 row(s) affected)
exec delete_Туристы_1 @Кодтуриста_1 = 6, @Фамилия_2 ='', @Имя_3='', @Отчество_4='' (1 row(s) affected)
Мы получаем прежнее число записей (рис. 5.17):

Рис. 5.17. Таблица "Туристы". Удаление записей
В программном обеспечении к курсу вы найдете файлы5) BDTur_ firm2.mdf, BDTur_firm2.ldf, а также исходный файл Microsoft Access BDTur_firm2.mdb (Code\Glava3\).
Создание хранимых процедур в Visual Studio .NET
Среда Visual Studio .NET предоставляет интерфейс для создания хранимых процедур в базе данных при наличии подключения к ней. Это удобно - если вы работаете с базой данных по сети, встроенные средства администрирования Microsoft SQL Server могут оказаться недоступными. Запускаем Visual Studio (нам даже не нужно создавать какой-либо проект), переходим на вкладку "Server Explorer", раскрываем подключение к базе данных BDTur_firm2, затем на узле "Stored Procedures" щелкаем правой кнопкой и выбираем пункт "New Stored Procedure" (рис. 5.29):
Рис. 5.29. Создание новой процедуры в окне "Server Explorer"
Появляется шаблон структуры, сгенерированный мастером:
CREATE PROCEDURE dbo.StoredProcedure1 /* ( @parameter1 datatype = default value, @parameter2 datatype OUTPUT ) */ AS /* SET NOCOUNT ON */ RETURN
Для того чтобы приступить к редактированию, достаточно убрать знаки комментариев "/*". Команда NOCOUNT со значением ON отключает выдачу сообщений о количестве строк таблицы, получающейся в качестве запроса. Дело в том, что при использовании более чем одного оператора (SELECT, INSERT, UPDATE или DELETE) в начале запроса надо поставить команду "SET NOCOUNT ON", а перед последним оператором SELECT - "SET NOCOUNT OFF". С другими частями шаблона мы уже сталкивались. Например, хранимую процедуру proc_po1 (см. таблицу 5.3) можно переписать так:
CREATE PROCEDURE dbo.proc_vs1
( @TouristID int, @LastName nvarchar(60) OUTPUT )
AS SET NOCOUNT ON SELECT @LastName = Фамилия FROM Туристы WHERE Кодтуриста = @TouristID
RETURN
После завершения редактирования SQL-конструкция будет обведена синей рамкой. Щелкнув правой кнопкой в этой области и выбрав пункт меню "Design SQL Block", можно перейти к построителю выражения ("Query Builder") (рис. 5.30, А, Б). При выборе в этом же меню пункта "Run Stored Procedure" появляется одноименное окно, где отслеживаются передаваемые параметры (рис. 5.30, В).

увеличить изображение
Рис. 5.30. Редактирование хранимой процедуры в Visual Studio .NET. А - контекстное меню, Б - построитель выражений ( режим "Design SQL Block"), В - окно "Run stored procedure", Г - окно "Output".
В данном случае необходимо указывать значение параметров (см. таблицу 5.3), поэтому после нажатия кнопки "ОК" в окне "Run stored procedure" процедура выполнена не будет, в окне "Output" появляется следующее сообщение (рис. 5.30, Г).
Running dbo."proc_vs1" ( @TouristID =
Procedure 'proc_vs1' expects parameter '@TouristID', which was not supplied.
Для сохранения процедуры в базе данных выбираем "File \ Save proc_vs1" (или нажимаем Ctrl+S), теперь можно закрывать студию - хранимая процедура создана. Впрочем, для продолжения работы выбираем пункт "Refresh" контекстного меню в окне "Server Explorer". Происходит синхронизация с базой данных, и процедура "proc_vs1" появляется в списке. Двойной щелчок открывает ее для редактирования, причем заголовок имеет следующий вид:
ALTER PROCEDURE dbo.proc_vs1
Оператор ALTER позволяет производить действия (редактирование) с уже имеющимся объектом базы данных.
![]() |
![]() |
![]() |
Вводные сведения об этой программе см. в первой лекции.
2)
См. первую лекцию.
3)
Названия операторов принято писать прописными буквами, вот так: CREATE PROCEDURE. Однако если вам неудобно постоянно переключать регистр, вы можете писать операторы строчными буквами: create procedure. Это не совсем строго, и, возможно, далее придется отказаться от этой привычки, но на первых порах это экономит много времени - SQL Query Analyzer понимает любой регистр и сохраняет процедуру в нужном формате.
4)
Affected - перев. с англ., здесь - "изменена".
5)
После присоединения базы к Microsoft SQL Server все созданные хранимые процедуры будут находиться в узле "Stored Procedures".
6)
Далее мы будем работать с этой базой данных. Вполне возможно, что у вас ее нет - вы начали читать с этого места книгу, не выполняли упражнения или потеряли диск. В этом случае вам нужно будет сделать следующее: а) Прочитать первую лекцию, создать по описаниям базу данных BDTur_firm.mdb в Microsoft Access. б) Как описывается в начале уже этой, пятой лекции, изменить названия таблиц и полей базы. в) Преобразовать файл BDTur_firm.mdb в формат Microsoft SQL Server 2000, заодно присоединив его к своему локальному серверу.
7)
Кроме удаления самого объекта DataSet с панели компонент формы, потребуется также удаление его схемы. Переходим в окно "Server Explorer" и удаляем файл схемы, имеющий расширение XSD. Например, dataSet1.xsd.
8)
Здесь я привожу названия операторов прописными буквами. Построитель выражений генерирует запросы именно в этом регистре.
9)
Если наличие большого количества полей и свойств кажется запутанным - лучше отложить Visual Studio .NET, запустить Access и как следует разобраться с созданием запросов. Достаточно одного учебника или даже справочной системы, чтобы научиться создавать запросы среднего уровня сложности.

Вызов простых хранимых процедур при помощи объекта DataAdapter
Мы разобрались с созданием и запуском хранимых процедур, приступим теперь к их использованию в Windows-приложениях, связанных с базами данных. Создайте новый Windows-проект и назовите его "VisualDataAdapterSP". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". В окне Toolbox переходим на вкладку Data и перетаскиваем на форму элемент SqlDataAdapter. В появившемся мастере в поле имени сервера вводим ".", выбираем тип входа "учетные сведения Windows NT", а из выпадающего списка баз данных выбираем6) "BDTur_firm2" (рис. 5.18):
Рис. 5.18. Подключение к базе данных BDTur_firm2
Проверив подключение, закрываем окно "Свойства связи с данными". В шаге "Choose a Query Type" мастера выбираем пункт "Use existing stored procedures" (рис. 5.19):

Рис. 5.19. Шаг "Choose a Query Type" мастера настройки объекта DataAdapter
Далее выбираем процедуру "proc1" - как вы помните, она извлекала все записи из таблицы "Туристы". Выводимые поля отображаются в окне "Set Select procedure parameters" (рис. 5.20):

Рис. 5.20. Выбор хранимой процедуры
Нажимаем кнопку "Next", а в следующем, заключительном шаге - "Finish". Просмотрим данные, которые будут извлечены объектом DataAdapter. Выделяем sqlDataAdapter1, переходим в окно Properties и щелкаем по ссылке "Preview Data_". В появившемся окне "Data Adapter Preview" нажимаем кнопку Fill для просмотра данных (рис. 5.21).

увеличить изображение
Рис. 5.21. Просмотр данных, извлекаемых объектом DataAdapter
Закрываем окно "Data Adapter Preview", снова выделяем объект sqlDataAdapter1, в его окне Properties нажимаем на ссылку "Generate Dataset_" (см. рис. 5.21). В появившемся окне "Generate Dataset" предлагается создать новый объект "DataSet1".
Нажимаем кнопку "OK". Выделяем элемент DataGrid, из выпадающего списка свойства "DataSource" выбираем "dataSet11.proc1" (рис. 5.22).

Рис. 5.22. Свойство DataSource элемента DataGrid
Вид формы изменился - на нем появились названия полей. В конструкторе формы вызываем метод Fill объекта DataAdapter для заполнения DataSet:
public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dataSet11); }
Запускаем приложение. На форму выводятся данные, полученные в результате выполнения хранимой процедуры proc1 (рис. 5.23).

Рис. 5.23. Приложение "VisualDataAdapterSP". Данные хранимой процедуры "proc1"
Изменим настройку объекта DataAdapter. Выделяем sqlDataAdapter1, в окне Properties щелкаем по ссылке "Configure DataAdapter_" (см. рис. 5.21). Появляется уже знакомый мастер "Data Adapter Configuration Wizard", нажимаем кнопку "Next". В шаге "Choose Your Data Connection" оставляем имеющееся подключение - мы будем работать с той же самой базой данных. В шаге "Binds Commands to Existing Stored Procedures" на этот раз выбираем процедуру proc9 - она извлекала данные из таблиц "Туристы" и "Информацияотуристах" (см. таблицу 5.1). Завершаем работу мастера. Изменим свойство DataSource объекта DataGrid - установим теперь значение "dataSet11" (рис. 5.24):

Рис. 5.24. Изменение свойства DataSource объекта DataGrid
Запускаем приложение. Теперь мы видим две ссылки - "proc1" и "proc9". Переходя по последней, мы видим данные хранимой процедуры (рис. 5.25, Б).

увеличить изображение
Рис. 5.25. Приложение "VisualDataAdapterSP". А - данные хранимой процедуры "proc1". Б - данные хранимой процедуры "proc9"
Если мы перейдем по ссылке "proc1", мы обнаружим, что данных в ней нет, однако названия полей сохранились (рис. 5.25, А).
Дело в том, что в структуре объекта DataSet остался "след" первой хранимой процедуры. В восьмой лекции мы научимся работать со структурой DataSet, а пока, если нам не нужна такая пустая ссылка "proc1", можно удалить объект DataSet7), а затем сгенерировать его заново по ссылке объекта DataAdapter окна Properties.
Создадим теперь хранимую процедуру при помощи мастера настройки объекта DataAdapter. Выделяем sqlDataAdapter1 и в окне Properties снова нажимаем на ссылку " Configure DataAdapter_". В шаге "Choose a Query Type" (см. рис. 5.19) выбираем "Create new stored procedures". В следующем шаге "Generate the stored procedures" нажимаем кнопку "Query Builder" (Построитель запроса). Добавляем таблицу "Туристы". Создадим еще раз запрос, выводящий всех туристов, фамилия которых содержит букву "и" (см. табл. 5.1, процедура proc8). Ставим галочку в поле *(All Columns), затем просто вводим условие отбора WHERE8):
SELECT * FROM Туристы WHERE (Фамилия LIKE '%и%')
Обратите внимание на небольшое отличие синтаксиса - здесь условие находится в круглых скобках. Внешний вид построителя выражения также изменился: в таблице "Туристы" появился значок фильтра, в поле "Column" - заголовок "Фамилия", в поле "Criteria" (Условие) - выражение "LIKE '%и%'". Щелкнув правой кнопкой в любой части построителя, выбираем пункт меню "Run" - в нижней таблице появляются данные, извлеченные запросом (рис. 5.26):

Рис. 5.26. Создание запроса в Query Builder
Работа с Query Builder очень похожа на создание запросов в режиме конструктора в Microsoft Access. Читатель, с этим знакомый, без труда разберется во всех полях и свойствах построителя выражения9). Завершив настройку, закрываем построитель, нажимая кнопку "ОК". Нажимаем кнопку "Next", в шаге "Create the Stored Procedures" задаем название созданной процедуре - "proc_da1" (см.
рис. 5.27).

увеличить изображение
Рис. 5.27. Окно "Preview SQL Script" и шаг мастера "Create the Stored Procedures"
По умолчанию мастер также генерирует процедуры типа insert, update и delete. В построители выражения мы создали саму SQL-конструкцию, без указания команд создания хранимой процедуры. Нажав кнопку "Preview SQL Script_", можно просмотреть команды, которые были сгенерированы автоматически. В окне "Create the Stored Procedures" также по умолчанию отмечено автоматическое создание хранимых процедур в самой базе данных. Завершаем работу, нажимая кнопку "Finish". Запускаем приложение - на форму выводятся данные хранимой процедуры proc_da1 (рис. 5.28).

Рис. 5.28. Приложение "VisualDataAdapterSP". Данные хранимой процедуры "proc_da1"
В программном обеспечении к курсу вы найдете приложение VisualData AdapterSP (Code\Glava3\ VisualDataAdapterSP).
Технология Microsoft ADO.NET
Методы ExecuteNonQuery, ExecuteScalar и ExecuteReader объекта Command
Мы рассмотрели всю возможную функциональность, которую можно получить без использования каких-либо методов объекта Command. Для выполнения запросов на выборку простейших процедур достаточно просто указать тип и передать название запроса или процедуры. Все работает, но этого явно недостаточно для серьезных приложений. Поэтому забудем пока про хранимые процедуры, другие объекты ADO .NET, и сосредоточим все внимание на методах объекта Command.Метод ExecuteNonQuery применяется для выполнения запросов, не возвращающих данные. Как же запросы, предназначенные именно для извлечения данных, могут не возвращать их? Речь идет о запросах типа UPDATE, INSERT и DELETE - в самом деле, они вносят изменения в таблицу базы данных, не возвращая ничего назад в результате выполнения. В самом языке SQL эти запросы образуют категорию DML (Data Manipulation Language, DML). Дословный перевод названия категории - "язык манипулирования данными", наличие слова "язык" вносит небольшую путаницу: в языке SQL есть язык DML. Но это именно так. После небольшого опыта подобные названия станут привычными.
Создайте новое консольное приложение и назовите его "Example ExecuteNonQuery". Привожу его полный листинг:
using System; using System.Data.SqlClient;
namespace ExampleExecuteNonQuery { class Class1 { [STAThread] static void Main(string[] args) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI; data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandText = "UPDATE Туристы SET Фамилия = 'Сергеева' WHERE Кодтуриста = 3"; myCommand.ExecuteNonQuery(); conn.Close(); } } }
Объект Command можно создавать, вызывая метод CreateCommand объекта Connection:
SqlCommand myCommand = conn.CreateCommand();
Ранее мы пользовались следующим способом:
SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn;
Эти два способа совершенно эквивалентны. В свойстве CommandText указывается непосредственно текст запроса, который устанавливает значение "Сергеева" поля "Фамилия" для записи с полем "Кодтуриста" = 3. Для выполнения запроса просто вызываем метод ExecuteNonQuery:
myCommand.ExecuteNonQuery();
Запускаем приложение, нажимая Ctrl+F5. При успешном выполнении запроса в консольном окне появляется надпись "Press any key to continue" (рис. 6.10):

Рис. 6.10. Выполнение приложения ExampleExecuteNonQuery
Запускаем SQL Server Enterprise Manager, открываем таблицу "Туристы" и убеждаемся в том, что запись изменилась (рис. 6.11).

Рис. 6.11. Таблица "Туристы", изменение записи
Метод ExecuteNonQuery все-таки что-то неявно возвращает - результат выполнения запроса в виде количества измененных записей; это может применяться для проверки (изменим фрагмент кода):
int UspeshnoeIzmenenie = myCommand.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { Console.WriteLine ("Изменения внесены"); } else { Console.WriteLine("Не удалось внести изменения"); }
Теперь, в зависимости от результата, будет появляться соответствующее сообщение (рис. 6.12).

Рис. 6.12. Результаты проверки выполнения запроса
Неудачный результат можно получить, установив в тексте запроса значения поля "Код туриста", равное, скажем, 10.
Закомментируем имеющееся свойство CommandText и добавим новое:
myCommand.CommandText = "INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович')";
Запускаем приложение, переходим в SQL Server Enterprise Manager - запрос добавил новую запись (рис. 6.13):

Рис. 6.13. Таблица "Туристы", добавление записи
Снова закомментируем свойство CommandText, добавим теперь запрос на удаление записи:
myCommand.CommandText = "DELETE FROM Туристы WHERE Кодтуриста = 4";
Запускаем приложение - из таблицы удалена четвертая запись (рис. 6.14):

Рис. 6.14. Таблица "Туристы", удаление записи
Метод ExecuteNonQuery применяется также для выполнения запросов, относящихся к категории DDL языка SQL. Язык определения данных3) (Data Definition Language, DDL) позволяет создавать и изменять структуру объектов базы данных, например, создавать и удалять таблицы. Основными операторами этого языка являются CREATE, ALTER, DROP. В результате выполнения запросов DDL не возвращаются данные - именно поэтому мы можем применять метод ExecuteNonQuery. Закомментируем имеющееся свойство CommandText и напишем новое, создающее в базе "BDTur_firm2" новую таблицу "Отзывы":
myCommand.CommandText = "CREATE TABLE Отзывы (Кодотзыва INT NOT NULL, Кодтуриста INT NOT NULL, Комментарий VARCHAR(50)";
Запускаем приложение, затем переходим в SQL Server Enterprise Manager, нажимаем кнопку
(обновить) на панели инструментов - в базе появляется новая таблица (рис. 6.15):
Рис. 6.15. База данных BDTur_firm2, новая таблица "Отзывы"
Для добавления нового столбца "Отзывтуриста" строка Command Text должна иметь следующий вид:
myCommand.CommandText = "ALTER TABLE Отзывы ADD Отзывтуриста VARCHAR(50)";
В SQL Server Enterprise Manager дважды щелкаем по названию таблицы - в появившемся окне "Table Properties" видим новое поле "Отзывтуриста" (рис. 6.16):

Рис. 6.16. Свойства таблицы "Отзывы"
Для удаления таблицы "Отзывы" запускаем приложение, содержащее следующую строку CommandText:
myCommand.CommandText = "DROP TABLE Отзывы";
Переходим в SQL Server Enterprise Manager - таблица полностью исчезла из базы данных. Если бы нам нужно было лишь удалить данные из таблицы, сохранив структуру, мы бы воспользовались следующей командой:
myCommand.CommandText = "DELETE FROM Отзывы";
Объектами базы данных могут быть не только таблицы, но и хранимые процедуры, схемы, представления.
В любом случае манипуляция с ними будет относиться к категории DDL.
Метод ExecuteNonQuery применяется для выполнения запросов, относящихся к категории DCL. Язык управления данными (Data Control Language, DCL) предназначен для управления доступом (определения полномочий) к объектам базы данных. Основными операторами этого языка являются GRANT, DENY, REVOKE. Мы не будем рассматривать выполнение этих запросов - использование в данном случае объекта Commnad не отличается ничем от рассмотренного выше.
В программном обеспечении к курсу вы найдете приложение Example ExecuteNonQuery (Code\Glava3\ ExampleExecuteNonQuery).
Метод ExecuteScalar объекта Command применяется для запросов, возвращающих одно значение. Мы сталкивались с такими запросами, когда использовали агрегатные функции COUNT, MIN, MAX. Создайте новое консольное приложение и назовите его "ExampleExecuteScalar". Полный листинг этого приложения:
using System; using System.Data.SqlClient;
namespace ExampleExecuteScalar { class Class1 { [STAThread] static void Main(string[] args) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); // SqlCommand myCommand = new SqlCommand(); // myCommand.Connection = conn; myCommand.CommandText = "SELECT COUNT (*) FROM Туры"; string KolichestvoTurov = Convert.ToString(myCommand.ExecuteScalar()); conn.Close(); Console.WriteLine("Количество туров: " + KolichestvoTurov); } } }
Возвращаемый методом ExecuteScalar результат мы привели к типу string для вывода в окно консоли. Запускаем приложение - как и следовало ожидать, запрос вернул число 5 (рис. 6.17):

Рис. 6.17. Приложение ExampleExecuteScalar, вывод количества туров
Можно несколько раз применять этот метод:
... myCommand.CommandText = "SELECT COUNT (*) FROM Туры"; string KolichestvoTurov = Convert.ToString(myCommand.ExecuteScalar()); myCommand.CommandText = "SELECT MAX (Цена) FROM Туры"; string MaxPrice = Convert.ToString(myCommand.ExecuteScalar()); myCommand.CommandText = "SELECT MIN (Цена) FROM Туры"; string MinPrice = Convert.ToString(myCommand.ExecuteScalar()); myCommand.CommandText = "SELECT AVG (Цена) FROM Туры"; string AvgPrice = Convert.ToString(myCommand.ExecuteScalar()); conn.Close(); Console.WriteLine("Количество туров: " + KolichestvoTurov + "\nСамый дорогой тур, цена в руб. : " + MaxPrice + "\nСамый дешевый тур, цена в руб.: " + MinPrice + "\nСредняя цена туров: " + AvgPrice); }
Запускаем приложение4) - получаем несколько значений из базы данных (рис. 6.18):

Рис. 6.18. Приложение ExampleExecuteScalar, вывод нескольких значений
Когда требуется получать подобные одиночные значения, всегда следует применять метод ExecuteScalar - это позволяет значительно повысить производительность.
В программном обеспечении к курсу вы найдете приложение Example ExecuteScalar (Code\Glava3\ ExampleExecuteScalar).
Мы переходим к рассмотрению очень важного метода - ExecuteReader. Он применяется для получения набора записей из базы данных. Особенностью этого метода является то, что он возвращает специальный объект DataReader, с помощью которого просматриваются записи. Для хранения данных, полученных из базы, мы до этого использовали объект DataSet. Объект DataReader, в отличие от DataSet, требует наличия постоянного подключения для извлечения и просмотра данных, кроме того, он открывает данные только для чтения. Создайте новое консольное приложение и назовите его "ExampleExecuteReader". Полный листинг этого приложения:
using System; using System.Data.SqlClient;
namespace ExampleExecuteReader { class Class1 { [STAThread] static void Main(string[] args) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; SqlCommand myCommand = conn.CreateCommand(); //SqlCommand myCommand = new SqlCommand(); //myCommand.Connection = conn; myCommand.CommandText = "SELECT * FROM Туристы"; conn.Open(); SqlDataReader dataReader = myCommand.ExecuteReader();
while (dataReader.Read()) { Console.WriteLine(dataReader["Фамилия"]); } dataReader.Close(); conn.Close(); } } }
Объект dataReader создается в результате вызова метода Execute Reader объекта myCommand:
SqlDataReader dataReader = myCommand.ExecuteReader();
Перед считыванием первой записи вызываем метод Read объекта dataReader и выводим набор записей в консольное окно.
Запускаем приложение5) (рис. 6.19).

Рис. 6.19. Приложение "ExampleExecuteReader". Вывод поля "Фамилия"
Объект DataReader возвращает набор данных типа object, причем для обращения можно использовать индекс:
Console.WriteLine(dataReader[1]);
Это совершенно эквивалентная строка вывода6).
Перечислим несколько полей:
... Console.WriteLine(dataReader[0]); Console.WriteLine(dataReader[1]); Console.WriteLine(dataReader[2]); Console.WriteLine(dataReader[3]); ...
При выводе они будут располагаться в структурированном виде (рис. 6.20):

Рис. 6.20. Приложение ExampleExecuteReader, вывод содержимого всех полей
Поскольку мы имеем дело с объектами (тип данных object), для вывода записей в виде строк неприменимо их простое объединение:
... Console.WriteLine(dataReader[0] + dataReader[1] + dataReader[2] + dataReader[3]); ...
Преобразованные к типу string значения можно объединять:
Console.WriteLine(Convert.ToString(dataReader[0]) + " "+ Convert.ToString(dataReader[1]) + " "+Convert.ToString(dataReader[2]) + " "+ Convert.ToString(dataReader[3]));
Теперь записи выводятся в более привычном виде (рис. 6.21):

Рис. 6.21. Приложение ExampleExecuteReader. Вывод содержимого всех полей в виде записей
В программном обеспечении к курсу вы найдете приложение "Example ExecuteReader" (Code\Glava3\ ExampleExecuteReader).
Объект Command. Свойства CommandType и CommandText
При определении объектов ADO .NET мы назвали DataAdapter адаптером, преобразователем, предназначенным для взаимодействия с базой данных. Это действительно так, однако если рассматривать взаимодействие с базой данных более глубоко, то выясняется, что в ADO .NET есть специализированный объект для выполнения запросов, называемый Command. Под запросами понимается выполнение SQL-конструкций или запуск хранимых процедур. С объектом Command мы познакомились еще во второй лекции, но у вас могло сложиться впечатление, что его введение достаточно формально - вполне можно обойтись одним объектом DataAdapter. Например, в приложении VisualDataAdapterSP мы создавали и выполняли хранимые процедуры, настраивая DataAdapter без всякого объекта Command! В действительности, среда создала его неявным образом - открываем метод InitializeComponent и видим объявление sqlSelectCommand1, а также описание нескольких его свойств:private void InitializeComponent() { ... this.sqlSelectCommand1 = new System.Data.SqlClient.SqlCommand(); ... // // sqlSelectCommand1 // this.sqlSelectCommand1.CommandText = "[proc_da1]"; this.sqlSelectCommand1.CommandType = System.Data.CommandType.StoredProcedure; this.sqlSelectCommand1.Connection = this.sqlConnection1; this.sqlSelectCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter ("@RETURN_VALUE", System.Data.SqlDbType.Int, 4, System.Data.ParameterDirection.ReturnValue, false, ((System.Byte)(0)), ((System.Byte)(0)), "", System.Data.DataRowVersion.Current, null)); ...
Нам нужно научиться понимать это описание и создавать его программным образом. Среда Visual Studio .NET содержит инструменты для визуальной настройки объекта Command, и поскольку разобраться в них проще, чем в коде, начнем с них. Создайте новое Windows-приложение и назовите его "VisualCommand". Добавляем элемент управления DataGrid, его свойству Dock устанавливаем значение Fill. В окне ToolBox переходим на вкладку Data, перетаскиваем на форму объект SqlCommand (рис. 6.1):

Рис. 6.1. Окно Toolbox, вкладка Data
Переходим в окно Properties появившегося объекта sqlCommand1, в свойстве "Connection" из выпадающего списка выбираем создание нового подключения (New) (рис. 6.2).

Рис. 6.2. Свойство "Connection" объекта sqlCommand1
В окне "Свойства связи с данными" настраиваем подключение к базе данных BDTur_firm2 (см. рис. 5.18). На панели компонент появляется объект sqlConnection1 - если бы мы вначале создали и настроили подключение, то в свойстве Connection объекта sqlCommand1 можно было выбрать "Existing/sqlConnection1". Свойство CommandType указывает на тип команды (запроса), который будет направляться к базе данных (рис. 6.3):

Рис. 6.3. Свойство CommandType объекта sqlCommand1
Возможны следующие значения:
Оставляем пока значение по умолчанию. В поле свойства CommandText нажимаем на кнопку
(...) (рис. 6.4):
Рис. 6.4. Свойство CommandText объекта sqlCommand1
В появившемся построителе выражений настраиваем извлечение содержимого таблицы "Туры". Обратите внимание на то, что в поле свойства CommandText появилась SQL-конструкция:
SELECT Туры.* FROM Туры
Снова в окне Toolbox переходим на вкладку Data, перетаскиваем на форму объект SqlDataAdapter. В мастере "Data Adapter Configuration Wizard" нажимаем кнопку "Cancel" - у нас уже есть подключение и настроенный объект sqlCommand1, достаточно указать в свойстве Select Command объект sqlCommand1 (рис. 6.5):

Рис. 6.5. Свойство SelectCommand объекта sqlDataAdapter1
Выделяем sqlDataAdapter1, создаем DataSet, нажимая на ссылку "Generate Dataset". В окне "Generate Dataset" оставляем название "DataSet1", нажимаем "OK". В выпадающем списке свойства DataSource элемента управления DataGrid выбираем "datSet11" в качестве источника данных (рис. 6.6):

Рис. 6.6. Свойство DataSource элемента dataGrid1
Наконец в конструкторе формы вызываем метод Fill объекта DataAdapter:
public Form1() { InitializeComponent(); sqlDataAdapter1.Fill(dataSet11); }
Запускаем приложение. На форму выводится содержимое таблицы "Туры" (рис. 6.7):

Рис. 6.7. Приложение VisualCommand, вывод таблицы "Туры"
Установим теперь в свойстве CommandType объекта Command значение "StoredProcedure", а в свойстве CommandText введем название имеющейся хранимой процедуры - "[proc10]" (рис. 6.8):

Рис. 6.8. Настройка свойств объекта sqlCommand1 для вывода процедуры [proc10]
Чтобы избежать проблем со структурой DataSet, удалим имеющийся объект с панели компонент. Дополнительно в окне Solution Explorer удаляем файл DataSet1.xsd. Снова генерируем DataSet и определяем его в качестве источника данных для элемента DataGrid. Запускаем приложение - на форму выводится результат выполнения хранимой процедуры (рис. 6.9):

увеличить изображение
Рис. 6.9. Приложение VisualCommand, извлечение хранимой процедуры proc10
Таким образом можно извлекать данные из таблиц при помощи SQL-конструкций и запускать хранимые процедуры таблицы 5.1. Поставщик данных SQL Server не поддерживает значение TableDirect свойства CommandType - при его выборе появляется диагностическое сообщение об ошибке. Это значение можно применять при подключении к базе данных Microsoft Access.
В программном обеспечении к курсу вы найдете приложение Visual Command (Code\Glava3\ VisualCommand).
Перейдем теперь к реализации аналогичного приложения без применения визуальных средств студии для объектов ADO .NET.
Создайте новое Windows-приложение и назовите его "ProgrammCommand". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". В классе формы создаем строки connectionString и commandText:
string connectionString = "integrated security=SSPI; data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; string commandText = "SELECT * FROM Туры";
Конструктор формы будет иметь следующий вид:
public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(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, "Туры"); conn.Close(); dataGrid1.DataSource = ds; }
Запускаем приложение. На форму выводится содержимое таблицы "Туры" (см. рис. 6.7). Здесь мы используем всего один объект DataAdapter, который сам открывает и закрывает соединение для получения данных. Поэтому можно не вызывать методы Open и Close объекта conn. Конструктор объекта SqlCommand может принимать значения, указанные в таблице 6.1.
| 1 | ![]() | |
| Пример 2) | Описание | |
public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(connectionString); SqlTransaction trans = conn.BeginTransaction(); SqlCommand myCommand = new SqlCommand(commandText, conn, trans); //SqlCommand myCommand = // new SqlCommand("SELECT * FROM Туры", conn, trans); SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Туры"); dataGrid1.DataSource = ds; } | Первый параметр - строка commandText, причем можно сразу задавать SQL-конструкцию без создания строки в классе. Затем экземпляры объектов SqlConnection и SqlTransaction. Подробно о транзакциях см. далее | |
| 2 | ![]() | |
| Пример | Описание | |
| public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(connectionString); SqlCommand myCommand = new SqlCommand(commandText, conn); //SqlCommand myCommand = // new SqlCommand("SELECT * FROM Туры", conn); SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Туры"); dataGrid1.DataSource = ds; } | Вариант без экземпляра объекта SqlTransaction | |
| 3 | ![]() | |
| Пример | Описание | |
| public Form1() { SqlConnection conn = new SqlConnection(connectionString); SqlCommand myCommand = new SqlCommand(commandText); //SqlCommand myCommand = // new SqlCommand("SELECT * FROM Туры"); myCommand.Connection = conn; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Туры"); dataGrid1.DataSource = ds; } | Конструктор принимает в качестве параметра только строку commandText. Для указания соединений используем свойство Connection созданного экзмепляра myCommand | |
| 4 | ![]() увеличить изображение | |
| Пример | Описание | |
| public Form1() { SqlConnection conn = new SqlConnection(connectionString); SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandText = commandText; //Command.CommandText = "SELECT * FROM Туры"; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Туры"); dataGrid1.DataSource = ds; } | Конструктор не принимает параметров. Для указания соединения и строки commandText вызываем соответствующие свойства созданного экземпляра myCommand. Строке commandText можно передать SQL-конструкцию непосредственно - в этом случае создавать переменную в классе не нужно | |
Первый вариант конструктора обеспечивает самую компактную форму записи. Его можно использовать при работе с одним соединением с базой данных. В случае работы с несколькими соединениями удобнее определить значения командных строк в классе формы (или вообще в отдельном классе) и использовать четвертый вариант. При изменении параметров подключения нужно будет исправить только сами строки, не затрагивая код, относящийся к объекту Command.
Для вывода хранимой процедуры следует указать значение "Stored Procedure" свойства CommandType:
public Form1() { SqlConnection conn = new SqlConnection(connectionString); SqlCommand myCommand = new SqlCommand(); myCommand.Connection = conn; myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc10]"; SqlDataAdapter dataAdapter = new SqlDataAdapter(); dataAdapter.SelectCommand = myCommand; DataSet ds = new DataSet(); dataAdapter.Fill(ds, "Туры"); dataGrid1.DataSource = ds; }
Название процедуры можно также присвоить переменной commandText, созданной в классе формы.
В программном обеспечении к курсу вы найдете приложение Programm Command (Code\Glava3\ ProgrammCommand).
Применение методов объекта Command в Windows-приложениях. Параметризированные запросы
Мы рассмотрели основные методы объекта Command в консольных приложениях. Это дало нам возможность понять синтаксис самих методов, без привязки к какому-либо интерфейсу. Однако, после того как синтаксис стал ясен, возникает вопрос - как же использовать эти методы в реальных приложениях? Понятно, что простое копирование кода в конструктор формы, по сути, не изменит пример. Следовательно, мы должны привязывать вводимые значения, например, к текстовым полям. Но это означает, что параметры строки запроса будут неизвестны до тех пор, пока пользователь не введет соответствующие значения. Например, для метода ExecuteNonQuery строка commandText имела следующий вид:myCommand.CommandText = "UPDATE Туристы SET Фамилия = 'Сергеева' WHERE Кодтуриста = 3";
Если мы создадим приложение, где пользователь будет вводить фамилию и код туриста, то мы не можем заранее указать, какие это будут значения. Логически запрос можно представить примерно так:
myCommand.CommandText = "UPDATE Туристы SET Фамилия = 'Какая-то_фамилия,_которую_введет_пользователь' WHERE Кодтуриста = Какой-то_код,_который_введет_пользователь";
Для решения таких задач, которые возникли еще в самом начале разработки языка SQL, были придуманы параметризированные запросы. В них неизвестные значения заменяются параметрами. Вот так:
myCommand.CommandText = "UPDATE Туристы SET Фамилия = @Family WHERE Кодтуриста = @TouristID";
Здесь @Family (обратите внимание, пишется без кавычек!) - параметр для неизвестного значения фамилии, @TouristID - параметр для неизвестного значения кода туриста. Теперь мы можем привязывать параметры к тексту, вводимому пользователем. Создайте новое Windows-приложение и назовите его "ExamWinExecuteNonQuery". Устанавливаем следующие свойства формы:
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 620; 240 |
Добавляем на форму элементы управления и устанавливаем их свойства:
| Location | 16; 16 |
| Size | 296; 112 |
| Text | Пример UPDATE |
| Location | 320; 16 |
| Size | 280; 176 |
| Text | Пример INSERT |
| Location | 16; 136 |
| Size | 296; 56 |
| Text | Пример DELETE |
| Name | btnUpdate |
| Location | 80; 80 |
| Text | Обновить |
| Name | btnInsert |
| Location | 88; 144 |
| Text | Добавить |
| Name | BtnDelete |
| Location | 208; 24 |
| Text | Удалить |
| Name | TxtTouristIDUpdate |
| Location | 16; 24 |
| Size | 224; 20 |
| Text | Введите код туриста |
| Name | TxtFamilyUpdate |
| Location | 16; 56 |
| Size | 224; 20 |
| Text | Введите фамилию туриста |
| Name | TxtTouristIDInsert |
| Location | 16; 24 |
| Size | 224; 20 |
| Text | Введите код туриста |
| Name | TxtFamilyInsert |
| Location | 16; 56 |
| Size | 224; 20 |
| Text | Введите фамилию туриста |
| Name | TxtFirstNameInsert |
| Location | 16; 88 |
| Size | 224; 20 |
| Text | Введите имя туриста |
| Name | TxtMiddleNameInsert |
| Location | 16; 120 |
| Size | 224; 20 |
| Text | Введите отчество туриста |
| Name | txtTouristIDDelete |
| Location | 8; 24 |
| Size | 192; 20 |
| Text | Введите код туриста для удаления |
Форма в режиме дизайна будет иметь следующий вид (рис. 6.22):

увеличить изображение
Рис. 6.22. Приложение ExamWinExecuteNonQuery, вид формы в режиме дизайна
Подключаем пространство имен для работы с базой данных:
using System.Data.SqlClient;
В классе формы создаем экземпляр conn7):
SqlConnection conn = null;
Обработчик кнопки btnUpdate будет иметь следующий вид:
private void btnUpdate_Click(object sender, System.EventArgs e) { try { //Создаем переменную Family, в которую помещаем значение, //введенное пользователем в поле txtFamilyUpdate: string Family = Convert.ToString(this.txtFamilyUpdate.Text); //Создаем переменную TouristID, в которую помещаем значение, //введенное пользователем в поле txtTouristIDUpdate: int TouristID = int.Parse(this.txtTouristIDUpdate.Text); conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandText = "UPDATE Туристы SET Фамилия = @Family WHERE Кодтуриста = @TouristID"; //Добавляем параметр @Family в коллекцию параметров //объекта myCommand myCommand.Parameters.Add("@Family", SqlDbType.NVarChar, 50); //Устанавливаем значение параметра @Family //равным значению переменной Family myCommand.Parameters["@Family"].Value = Family; //Добавляем параметр @TouristID в коллекцию параметров //объекта myCommand myCommand.Parameters.Add("@TouristID", SqlDbType.Int, 4); //Устанавливаем значение параметра @TouristID //равным значению переменной TouristID myCommand.Parameters["@TouristID"].Value = TouristID; int UspeshnoeIzmenenie = myCommand.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { MessageBox.Show("Изменения внесены", "Изменение записи"); } else { MessageBox.Show("Не удалось внести изменения", "Изменение записи"); }
} catch(Exception ex) { MessageBox.Show(ex.ToString()); } finally { conn.Close(); } }
Обратите внимание - в блоке finally мы закрываем соединение, это нужно сделать в любом случае, независимо от результата выполнения команды.
Значения, введенные пользователем в текстовые поля txtFamilyUpdate и txtTouristIDUpdate, помещаются в переменные Family и TouristID. В запросе к базе данных используются два параметра - @Family и @TouristID. Мы добавляем их в коллекцию объекта Command, используя метод Add свойства Parameters, а затем устанавливаем значения параметров равными переменным Family и TouristID. Конструктор метод Add перегружен, первый вариант принимает наибольшее количество свойств8) (рис. 6.23):

Рис. 6.23. Конструктор метода Add свойства Parameters объекта Command
Описание некоторых свойств метода Add приводится в таблице 6.2.
| parameterName | Название параметра |
| sqlDbType | Тип данных передаваемого параметра |
| size | Размер параметра |
| sourceColumn | Название имени столбца объекта DataSet, на который ссылается данный параметр |

Рис. 6.24. Конструктор метода Add свойства Parameters объекта Command поставщика данных OLE DB
Добавляем обработчик кнопки btnInsert:
private void btnInsert_Click(object sender, System.EventArgs e) { try { int TouristID = int.Parse(this.txtTouristIDInsert.Text); string Family = Convert.ToString(this.txtFamilyInsert.Text); string FirstName = Convert.ToString(this.txtFirstNameInsert.Text); string MiddleName = Convert.ToString(this.txtMiddleNameInsert.Text); conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandText = "INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (@TouristID, @Family, @FirstName, @MiddleName)"; myCommand.Parameters.Add("@TouristID", SqlDbType.Int, 4); myCommand.Parameters["@TouristID"].Value = TouristID; myCommand.Parameters.Add("@Family", SqlDbType.NVarChar, 50); myCommand.Parameters["@Family"].Value = Family; myCommand.Parameters.Add("@FirstName", SqlDbType.NVarChar, 50); myCommand.Parameters["@FirstName"].Value = FirstName; myCommand.Parameters.Add("@MiddleName", SqlDbType.NVarChar, 50); myCommand.Parameters["@MiddleName"].Value = MiddleName; int UspeshnoeIzmenenie = myCommand.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { MessageBox.Show("Изменения внесены", "Изменение записи"); } else { MessageBox.Show("Не удалось внести изменения", "Изменение записи"); }
} catch(Exception ex) { MessageBox.Show(ex.ToString()); } finally { conn.Close(); } }
В запросе используются четыре параметра: @TouristID, @Family, @FirstName, @MiddleName. Тип данных создаваемых параметров соответствует типу данных полей таблицы "Туристы" в базе.
Добавляем обработчик кнопки btnDelete:
private void btnDelete_Click(object sender, System.EventArgs e) { try { int TouristID = int.Parse(this.txtTouristIDDelete.Text); conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandText = "DELETE FROM Туристы WHERE Кодтуриста = @TouristID"; myCommand.Parameters.Add("@TouristID", SqlDbType.Int, 4); myCommand.Parameters["@TouristID"].Value = TouristID; int UspeshnoeIzmenenie = myCommand.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { MessageBox.Show("Изменения внесены", "Изменение записи"); } else { MessageBox.Show("Не удалось внести изменения", "Изменение записи"); }
} catch(Exception ex) { MessageBox.Show(ex.ToString()); } finally { conn.Close(); } }
Запускаем приложение. В каждой из групп заполняем поля, затем нажимаем на кнопки. Проверять результат можно, запуская SQL Server Enterprise Manager и просматривая каждый раз содержимое таблицы "Туристы" (рис. 6.25):

Рис. 6.25. Готовое приложение ExamWinExecuteNonQuery. Таблицы взяты из SQL Server Enterprise Manager
В программном обеспечении к курсу вы найдете приложение Exam WinExecuteNonQuery (Code\Glava3\ ExamWinExecuteNonQuery).
Применять метод ExecuteScalar объекта Command в Windows-приложениях очень легко - достаточно указать элемент управления (текстовое поле, надпись) для вывода одиночного значения.
Рассмотрим теперь метод ExecuteReader. Одна из главных задач при использовании этого метода - разместить возвращаемый набор данных в элементе управления на форме.
Создайте новое приложение и назовите его "ExamWinExecuteReader". Перетаскиваем на форму элемент ListBox, его свойству Dock устанавливаем значение Bottom. Добавляем элемент Splitter, свойству Dock также устанавливаем значение Bottom. Наконец, перетаскиваем элемент ListView, свойству Dock устанавливаем значение Fill. Нам нужно настроить внешний вид элемента ListView: в окне Properties в поле свойства Columns нажимаем на кнопку
(...) (рис. 6.26). В редакторе "Column Header Collection Editor" добавляем следующие четыре элемента:| chTouristID | Код туриста |
| chFamily | Фамилия |
| chFirstName | Имя |
| chMiddleName | Отчество |

увеличить изображение
Рис. 6.26. Свойство Columns элемента ListView и редактор "ColumnHeader Collection Editor"
Для отображения созданных столбцов свойству View элемента устанавливаем значение "Details". Также включим режим отображения линий сетки - в свойстве GridLines выбираем значение "True". Сделанные изменения немедленно отобразятся на элементе.
Подключаем пространство имен для работы с базой:
using System.Data.SqlClient;
В классе формы создаем объекты conn и dataReader:
SqlConnection conn = null; SqlDataReader dataReader;
В конструкторе формы добавляем код для заполнения данными элементов управления:
public Form1() { InitializeComponent(); try { conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandText = "SELECT * FROM Туристы"; dataReader = myCommand.ExecuteReader(); while (dataReader.Read()) { // Создаем переменные, получаем для них значения //из объекта dataReader, используя метод GetТипДанных int TouristID = dataReader.GetInt32(0); string Family = dataReader.GetString(1); string FirstName = dataReader.GetString(2); string MiddleName = dataReader.GetString(3); //Выводим данные в элемент listBox1 listBox1.Items.Add("Код туриста: " + TouristID+ " Фамилия: " + Family + " Имя: "+ FirstName + " Отчество: " + MiddleName); //Создаем экземпляр item класса ListViewItem //для записи в него данных из dataReader ListViewItem item = new ListViewItem(new string[]{Convert.ToString(dataReader[0]), Convert.ToString(dataReader[1]), Convert.ToString(dataReader[2]), Convert.ToString(dataReader[3])}); listView1.Items.Add(item); } } catch(Exception ex) { MessageBox.Show(ex.ToString()); } finally { dataReader.Close(); conn.Close(); } }
Метод "GetТипДанных" позволяет приводить значения, возвращаемые объектом DataReader, если заранее известен их тип данных. Запускаем приложение. На форму выводятся данные в виде списка в элементе ListBox и в виде таблицы в элементе ListView (рис. 6.27):

Рис. 6.27. Приложение ExamWinExecuteReader
В программном обеспечении к курсу вы найдете приложение ExamWin ExecuteReader (Code\Glava3\ ExamWinExecuteReader).
Вывод данных в элемент ListView приводит к достаточно удовлетворительному результату, однако более привычным для нас является вывод в элемент DataGrid. Раньше, при использовании объекта DataSet, мы указывали источник данных для элемента DataGrid так:
dataGrid1.DataSource = dataset.Tables["Название_таблицы"].DefaultView;
Или так:
dataGrid1.DataSource = dataset;
Объект DataReader не поддерживает аналогичного вывода - мы не можем связать объекты таким простым образом:
dataGrid1.DataSource = datareader;
Одним из способов вывода является применение дополнительных объектов DataTable. Объект DataTable предназначен для хранения таблицы, полученной из базы данных (в восьмой лекции мы рассмотрим подробно этот объект). Создайте новое приложение и назовите его "DataReaderToDataGrid". Перетаскиваем на форму элемент управления DataGrid, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой:
using System.Data.SqlClient;
В классе формы создаем следующие объекты:
SqlConnection conn = null; //Создаем экземпляр FullDataTable, в который будут помещаться данные DataTable FullDataTable = new DataTable(); //Создаем экземпляр FullDataTable для получения структуры таблицы из базы данных DataTable ShemaDataTable = new DataTable(); SqlDataReader dataReader; SqlCommand myCommand; //Создаем объект objectRow для получения информации о числе столбцов object[] objectRow;
Основной код помещаем в конструктор формы:
public Form1() { InitializeComponent(); try { conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); myCommand = conn.CreateCommand(); myCommand.CommandText = "SELECT * FROM Туристы"; dataReader = myCommand.ExecuteReader(); //Вызываем метод GetSchemaTable, который получает схему таблицы из базы //и передает ее объекту ShemaDataTable ShemaDataTable = dataReader.GetSchemaTable(); //Свойство FieldCount возвращает число столбцов для текущей записи. //Передаем это значение объекту objectRow objectRow = new object[dataReader.FieldCount]; //Определяем структуру объекта FullDataTable for(int i =0; i
Возможно, этот код показался вам сложным. Лучше будет к нему вернуться еще раз после изучения девятой лекции. Запускаем приложение (рис. 6.28):

Рис. 6.28. Приложение DataReaderToDataGrid
В программном обеспечении к курсу вы найдете приложение Data ReaderToDataGrid (Code\Glava3\ DataReaderToDataGrid).
![]() |
![]() |
![]() |
Конструктор объекта OleDbCommand в точности такой же.
2)
Приводится только создание объекта SqlTransaction, без его методов, - см далее Лекцию 7, тему "Работа с транзакциями".
3)
Здесь снова "язык DDL языка SQL:" Так уж это принято, ничего не могу с этим поделать.
4)
Скорее всего, у вас будет измененное содержимое таблицы "Туры" - если, конечно, вы выполняли все действия, описанные в этой главе. Для получения исходной таблицы запустите SQL Server Enterprise Manager, удалите имеющуюся таблицу "Туры" и импортируйте ее снова из файла BDTur_firm2.mdb ((Code\Glava3\ BDTur_firm2.mdb).
5)
Таблица "Туристы" была заменена на оригинальную из базы BDTur_firm2.mdb.
6)
Использование индексов повышает производительность приложения.
7)
Мы это делаем для включения блока обработки исключений.
8)
Вообще-то, раньше свойства, входящие в какой-либо метод, мы называли параметрами. Здесь я не стал использовать это слово, чтобы не было путаницы с параметрами, входящими в коллекцию объекта Command.

Технология Microsoft ADO.NET
Хранимые процедуры в Microsoft Access
В завершение этой лекции рассмотрим хранимые процедуры в Microsoft Access. Хранимые процедуры? А разве Microsoft Access их поддерживает? По правде говоря, нет. Мы не можем создавать в MSAccess такие процедуры, как мы это делали в MS SQL Server, синтаксис не будет содержать ключевого слова "PROC" или "PROCEDURE", и вообще, это совсем не так называется! Но база данных способна хранить SQL-запросы, и если их запускать из внешнего приложения, то функциональность уже будет напоминать саму концепцию хранимых процедур.Открываем базу BDTur_firm2.mdb. В окне базы данных переключаемся на вкладку "Запросы" и дважды щелкаем на заголовке "Создание запроса в режиме конструктора" (рис. 7.14):

Рис. 7.14. Вкладка "Запросы" в окне базы данных
В появившемся окне добавления таблицы выбираем "Туристы" и нажимаем кнопку "OK" (рис. 7.15).

Рис. 7.15. Добавление таблицы
В режиме конструктора требуется установить столбцы, которые будут извлекаться в запросе. Для этого последовательно выбираем из выпадающего списка названия полей таблицы (рис. 7.16).

Рис. 7.16. Создание запроса
Добавим сортировку по столбцу "Фамилия". В поле "Сортировка" из выпадающего списка выбираем значение "по возрастанию" (рис. 7.17).

Рис. 7.17. Задание сортировки
Можно просмотреть SQL-конструкцию готового запроса. В главном меню выбираем "Вид \ Режим SQL". Окно конструктора изменяет свой вид - в нем появляется текст запроса:
SELECT Туристы.Кодтуриста, Туристы.Фамилия, Туристы.Имя, Туристы.Отчество FROM Туристы ORDER BY Туристы.Фамилия;
Сохраняем запрос, называя его "Сортировка_туристы". Дважды щелкнув на нем в окне базы данных, запускаем - записи таблицы отсортированы (рис. 7.18).

Рис. 7.18. Запуск готового запроса
Займемся теперь созданием приложения, которое будет запускать этот запрос как хранимую процедуру. Создайте новый Windows-проект и назовите его "Stored_Procedure_MSAccess".
Перетаскиваем на форму элемент управления ListBox, его свойству Dock устанавливаем значение "Fill". Подключаем пространство имен для работы с базой данных:
using System.Data.OleDb;
В классе формы определяем строку connectionString:
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";
В конструкторе формы создаем объекты ADO .NET, причем в свойстве CommandType объекта Command задаем тип запроса StoredProcedure:
public Form1() { InitializeComponent(); OleDbConnection conn = new OleDbConnection(); conn.ConnectionString = connectionString; OleDbCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[Сортировка_туристов]"; conn.Open(); OleDbDataReader dataReader = myCommand.ExecuteReader(); while (dataReader.Read()) { // Создаем переменные, получаем для них значения из объекта dataReader, //используя метод GetТипДанных int TouristID = dataReader.GetInt32(0); string Family = dataReader.GetString(1); string FirstName = dataReader.GetString(2); string MiddleName = dataReader.GetString(3); //Выводим данные в элемент llistBox1: listBox1.Items.Add("Код туриста: " + TouristID+ " Фамилия: " + Family + " Имя: "+ FirstName + " Отчество: " + MiddleName); } conn.Close(); }
Весь код уже достаточно хорошо знаком - мы его применяли для запуска хранимых процедур MS SQL Server. Запускаем приложение - на форму выводится результат запроса (рис. 7.19).

Рис. 7.19. Готовое приложение Stored_Procedure_MSAccess
В программном обеспечении к курсу вы найдете приложение Stored_ Procedure_MSAccess (Code\Glava3\Stored_Procedure_MSAccess).
![]() |
![]() |
![]() |
Разумеется, с небольшим увеличением производительности.
2)
Она называется SQL Server Books Online. Как вы могли заметить, эту справку можно вызвать из любого приложения, входящего в пакет Microsoft SQL Server 2000.

Работа с транзакциями
Транзакцией называется выполнение последовательности команд (SQL-конструкций) в базе данных, которая либо фиксируется при успешном извлечении каждой команды, либо отменяется при неудачном извлечении хотя бы одной команды. Большинство современных СУБД поддерживают механизм транзакций, и подавляющее большинство клиентских приложений, работающих с ними, используют для выполнения команд транзакции. Зачем нужны транзакции? Представим себе, что в базу данных BDTur_firm2 требуется вставить связанные записи в две таблицы - "Туристы" и "Информацияотуристах". Если запись, вставляемая в таблицу "Туристы", окажется неверной, например, из-за неправильно указанного кода туриста, база данных не позволит внести изменения, а тогда в таблице "Информацияотуристах" появится ненужная запись. Запускаем SQL Query Analyzer, в новом бланке вводим запрос для добавления двух записей:INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович'); INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (6, 'CA 1234567', 'Новосибирск', 'Россия', 1234567, 996548);
Две записи успешно добавляются в базу данных:
(1 row(s) affected)
(1 row(s) affected)
Изменим код туриста только во втором запросе:
INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович'); INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (7, 'CA 1234567', 'Новосибирск', 'Россия', 1234567, 996548);
Появляется сообщение о невозможности вставки первой записи с уже имеющимся значением ключевого поля. Вторая запись, однако, была добавлена в таблицу:
Server: Msg 2627, Level 14, State 1, Line 1 Violation of PRIMARY KEY constraint 'PK_Туристы'. Cannot insert duplicate key in object 'Туристы'. The statement has been terminated. (1 row(s) affected)
Извлекаем содержимое обеих таблиц следующим двойным запросом:
SELECT * FROM Туристы SELECT * FROM Информацияотуристах
В таблице "Информацияотуристах" последняя запись добавилась безо всякой связи с записью таблицы "Туристы" (рис. 7.8):

увеличить изображение
Рис. 7.8. Содержимое таблиц "Туристы" и "Информацияотуристах"
Для того чтобы избегать подобных ошибок, нам нужно применить транзакцию. Удалим все внесенные записи из обеих таблиц (это можно сделать с помощью запроса или в SQL Server Enterprise Manager) и оформим исходные SQL-конструкции в виде транзакции:
BEGIN TRAN DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович'); SELECT @OshibkiTabliciTourists=@@ERROR INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (6, 'CA 1234567', 'Новосибирск', 'Россия', 1234567, 996548); SELECT @OshibkiTabliciInfoTourists=@@ERROR IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0 COMMIT TRAN ELSE ROLLBACK TRAN
Начало транзакции мы объявляем с помощью команды BEGIN TRAN. Далее создаем два параметра - @OshibkiTabliciTourists, @OshibkiTabliciInfoTourists для сбора ошибок. После первого запроса возвращаем значение, которое встроенная функция @@ERROR присваивает первому параметру:
SELECT @OshibkiTabliciTourists=@@ERROR
То же самое делаем после второго запроса для другого параметра:
SELECT @OshibkiTabliciInfoTourists=@@ERROR
Проверяем значения обоих параметров, которые должны быть равными нулю при отсутствии ошибок:
IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0
В этом случае подтверждаем транзакцию (внесение изменений) при помощи команды COMMIT TRAN. В противном случае - если значение хотя бы одного из параметров @OshibkiTabliciTourists и @Oshibki TabliciInfoTourists оказывается отличным от нуля, отменяем транзакцию при помощи команды ROLLBACK TRAN.
После выполнения транзакции появляется уже знакомое сообщение:
(1 row(s) affected)
(1 row(s) affected)
Снова изменим код туриста во втором запросе:
BEGIN TRAN DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович'); SELECT @OshibkiTabliciTourists=@@ERROR INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (7, 'CA 1234567', 'Новосибирск', 'Россия', 1234567, 996548); SELECT @OshibkiTabliciInfoTourists=@@ERROR IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0 COMMIT TRAN ELSE ROLLBACK TRAN
Запускаем транзакцию - появляется в точности такое же сообщение, что и в случае применения обычных запросов:
Server: Msg 2627, Level 14, State 1, Line 1 Violation of PRIMARY KEY constraint 'PK_Туристы'. Cannot insert duplicate key in object 'Туристы'. The statement has been terminated.
(1 row(s) affected)
Однако теперь изменения не были внесены во вторую таблицу (рис. 7.9):

увеличить изображение
Рис. 7.9. Содержимое таблиц "Туристы" и "Информацияотуристах" после выполнения неудачной транзакции
Сообщение "(1 row(s) affected)", указывающее на "добавление" одной записи, в данном случае всего лишь означает, что вторая SQL-конструкция была верной и запись могла быть добавлена в случае успешного выполнения транзакции. Сделаем ошибку во втором запросе и снова попытаемся выполнить транзакцию:
BEGIN TRAN DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (7, 'Тихомиров', 'Андрей', 'Борисович'); SELECT @OshibkiTabliciTourists=@@ERROR INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (6, 'CA 1234567', 'Новосибирск', 'Россия', 1234567, 996548); SELECT @OshibkiTabliciInfoTourists=@@ERROR IF @OshibkiTabliciTourists=0 AND @OshibkiTabliciInfoTourists=0 COMMIT TRAN ELSE ROLLBACK TRAN
Появляется аналогичное сообщение:
(1 row(s) affected)
Server: Msg 2627, Level 14, State 1, Line 1 Violation of PRIMARY KEY constraint 'PK_Информацияотуристах'. Cannot insert duplicate key in object 'Информацияотуристах'. The statement has been terminated.
Изменения снова не были внесены в базу данных - в этом можно убедиться, вернув содержимое обеих таблиц. Читатель, хорошо знакомый с теорией баз данных, может заметить, что обеспечить целостность данных двух таблиц (в данном случае это именно так и называется) вполне можно и другими средствами, например, просто связать их и установить соответствующие правила. Это правильно, но для нас сейчас важно понимать, что в одной транзакции можно выполнить несколько самых разных запросов, которые можно разом применить или отклонить. Начало транзакции мы объявляем с помощью команды BEGIN TRAN, а затем принимаем ее - COMMIT TRAN - или отклоняем (откатываем) - ROLLBACK TRAN.
Перейдем теперь к рассмотрению транзакций в ADO .NET. Создайте новое консольное приложение и назовите его "EasyTransaction". Поставим задачу: передать те же самые данные в две таблицы - "Туристы" и "Информацияотуристах". Привожу полный листинг консольного приложения:
using System; using System.Data.SqlClient;
namespace EasyTransaction { class Class1 { [STAThread] static void Main(string[] args) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; conn.Open(); SqlCommand myCommand = conn.CreateCommand(); //Создаем транзакцию myCommand.Transaction = conn.BeginTransaction(); try { myCommand.CommandText = "INSERT INTO Туристы (Кодтуриста, Фамилия, Имя, Отчество) VALUES (6, 'Тихомиров', 'Андрей', 'Борисович')"; myCommand.ExecuteNonQuery(); myCommand.CommandText = "INSERT INTO Информацияотуристах(Кодтуриста, Серияпаспорта, Город, Страна, Телефон, Индекс) VALUES (6, 'CA 1234567', apos;Новосибирск', 'Россия', 1234567, 996548)"; myCommand.ExecuteNonQuery(); //Подтверждаем транзакцию myCommand.Transaction.Commit(); Console.WriteLine("Передача данных успешно завершена"); } catch(Exception ex) { //Отклоняем транзакцию myCommand.Transaction.Rollback(); Console.WriteLine("При передаче данных произошла ошибка: "+ ex.Message); } finally { conn.Close(); } } } }
Перед запуском приложения снова удаляем все добавленные записи из таблиц. При успешном выполнении запроса появляется соответствующее сообщение, а в таблицы добавляются записи (рис. 7.10):

Рис. 7.10. Приложение EasyTransaction. Транзакция выполнена
Повторный запуск этого приложения приводит к отклонению транзакции - нельзя вставлять записи с одинаковыми значениями первичных ключей (рис. 7.11):

увеличить изображение
Рис. 7.11. Приложение EasyTransaction. Транзакция отклонена
В виде транзакции можно заключать выполнение одной или нескольких хранимых процедур, - в самом деле, общая конструкция имеет следующий вид:
//Создаем соединение //Создаем транзакцию myCommand.Transaction = conn.BeginTransaction(); try { //Выполняем команды, вызываем одну или несколько хранимых процедур //Подтверждаем транзакцию myCommand.Transaction.Commit(); } catch(Exception ex) { //Отклоняем транзакцию myCommand.Transaction.Rollback(); } finally { //Закрываем соединение conn.Close(); }
При выполнении транзакций несколькими пользователями одной базы данных могут возникать следующие проблемы:
Для решения этих проблем разработаны четыре уровня изоляции транзакции:
По умолчанию устанавливается уровень Read committed. В справке Microsoft SQL Server 20002) (Указатель - вводим "isolation levels" - заголовок "overview") приводится таблица, иллюстрирующая различные уровни изоляции (рис. 7.12):

Рис. 7.12. Уровни изоляции Microsoft SQL Server 2000
Использование наибольшего уровня изоляции (Serializable) означает наибольшую безопасность и вместе с тем наименьшую производительность - все транзакции выполняются в виде серии, последующая вынуждена ждать завершения предыдущей. И наоборот, применение наименьшего уровня (Read uncommitted) означает максимальную производительность и полное отсутствие безопасности. Впрочем, нельзя дать универсальных рекомендаций по применению этих уровней - в каждой конкретной ситуации решение будет зависеть от структуры базы данных и характера выполняемых запросов.
Для установки уровня изоляции применяется следующая команда:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED или READ COMMITTED или REPEATABLE READ или SERIALIZABLE
Например, в транзакции, добавляющей две записи, уровень изоляции указывается следующим образом:
BEGIN TRAN SET TRANSACTION ISOLATION LEVEL SERIALIZABLE DECLARE @OshibkiTabliciTourists int, @OshibkiTabliciInfoTourists int ... ROLLBACK TRAN
В ADO . NET уровень изоляции можно установить при создании транзакции:
myCommand.Transaction = conn.BeginTransaction (System.Data.IsolationLevel.Serializable);
Дополнительно поддерживаются еще два уровня (см. рис. 7.13):

увеличить изображение
Рис. 7.13. Определение уровня транзакции
Транзакции обеспечивают целостность базы данных, при разработке многоуровневых приложений их применение является обязательным правилом.
В программном обеспечении к курсу вы найдете приложение Easy Transaction (Code\Glava3 \EasyTransaction).
Вызов хранимых процедур с входными и выходными параметрами
На практике наиболее часто используются хранимые процедуры с входными и выходными параметрами (см. таблицу 5.3). Создайте новое приложение и назовите его "VisualOutputParameter". Устанавливаем следующие свойства формы:| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 400; 96 |
Добавляем на форму элементы управления и устанавливаем их свойства:
| Name | txtTouristID |
| Location | 15; 20 |
| Size | 120; 20 |
| Text | Введите код туриста |
| Name | lblFamily |
| Location | 151; 20 |
| Size | 144; 23 |
| Text |
| Name | btnRun |
| Location | 303; 20 |
| Text | Запуск |
Переходим на вкладку Server Explorer, раскрываем узел Stored Procedures базы данных BDTur_firm2 и перетаскиваем процедуру proc_po1. Выделяем появившейся объект sqlCommand1, в окне Properties нажимаем на кнопку
(...) для перехода к редактору SqlParameter Collection Editor. Среда сгенерировала три параметра - @RETURN_VALUE, @TouristID, @LastName (рис. 7.4):
увеличить изображение
Рис. 7.4. Приложение VisualOutputParameter. Свойство Parameters объекта sqlCommand1
Сообщение об успешности выполнения команды возвращается при помощи параметра @RETURN_VALUE, значение которого для запроса данной хранимой процедуры будет равно нулю. Для процедур, содержащих запросы UPDATE, INSERT и DELETE, возвращаемое значение будет равно числу измененных записей. Обратите внимание, в тексте процедуры мы не создавали этот параметр - среда сгенерировала его автоматически! Два последних параметра были определены при создании хранимой процедуры. Подключаем пространство имен для работы с базой:
using System.Data.SqlClient;
В обработчике кнопки btnRun выводим фамилию искомого туриста в качестве текста надписи:
private void btnRun_Click(object sender, System.EventArgs e) { try { int TouristID = int.Parse(this.txtTouristID.Text); sqlCommand1.Parameters["@TouristID"].Value = TouristID; sqlConnection1.Open(); sqlCommand1.ExecuteScalar(); lblFamily.Text = Convert.ToString(sqlCommand1.Parameters["@LastName"].Value); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { sqlConnection1.Close(); } }
Запускаем приложение. Если в таблице имеется фамилия, соответствующая заданному коду, она выводится на форму (рис. 7.5):

Рис. 7.5. Готовое приложение VisualOutputParameter
При использовании визуальных средств код в обработчике ничем не отличается от рассматриваемого ранее - мы нигде не указывали значение output параметра @LastName. Дело в том, что среда сама настроила это значение в свойстве Direction (см. рис. 7.4). При программном вызове хранимой процедуры нам придется определять его вручную. Скопируйте папку приложения VisualOutputParameter и переименуйте ее в "ProgrammOutputParameter". Открываем проект, удаляем все объекты с панели компонент формы. В классе формы создаем объект Connection:
SqlConnection conn = null;
Обработчик кнопки btnRun принимает следующий вид:
private void btnRun_Click(object sender, System.EventArgs e) { try { conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2"; SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc_po1]"; int TouristID = int.Parse(this.txtTouristID.Text); myCommand.Parameters.Add("@TouristID", SqlDbType.Int, 4); myCommand.Parameters["@TouristID"].Value = TouristID; //Необязательная строка, т.к. совпадает со значением по умолчанию. //myCommand.Parameters["@TouristID"].Direction = ParameterDirection.Input; myCommand.Parameters.Add("@LastName", SqlDbType.NVarChar, 60); myCommand.Parameters["@LastName"].Direction = ParameterDirection.Output; conn.Open(); myCommand.ExecuteScalar(); lblFamily.Text = Convert.ToString (myCommand.Parameters["@LastName"].Value); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } finally { conn.Close(); } }
Мы добавили параметр @LastName в набор Parameters, причем его значение output указали в свойстве Direction:
myCommand.Parameters["@LastName"].Direction = ParameterDirection.Output;
Параметр @ TouristID является исходным, поэтому для него в свойстве Direction указывается Input. Поскольку это является значением по умолчанию для всех параметров набора Parameters, указывать его явно не нужно. Перечисление ParameterDirection принимает еще два значения - InputOutput и ReturnValue (рис. 7.6).

Рис. 7.6. Значения перечисления ParameterDirection
Для параметров, работающих в двустороннем режиме, устанавливается значение InputOutput, для параметров, возвращающих данные о выполнения хранимой процедуры, - ReturnValue. Примером последнего может служить @RETURN_VALUE (см. рис. 7.4).
В программном обеспечении к курсу вы найдете приложения Visual OutputParameter и ProgrammOutputParameter (Code\Glava3 \VisualOutput Parameter и ProgrammOutputParameter).
Вызов хранимых процедур с входными параметрами
Теперь, когда мы разобрались с методами объекта Command, мы можем вернуться к работе с хранимыми процедурами. Мы уже применяли самые простые процедуры (они приводятся в таблице 5.1), содержимое которых представляло собой, по сути, простой запрос на выборку в Windows-приложениях. Применение хранимых процедур с параметрами (таблица 5.2), как правило, связано с интерфейсом приложения - пользователь имеет возможность вводить значение и затем на основании его получать результат.Среда Visual Studio .NET предоставляет средства для визуальной работы с хранимыми процедурами. Создайте новый Windows-проект и назовите его "VisualParametersSP". Устанавливаем следующие свойства формы:
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 450; 330 |
Добавляем на форму элементы управления и устанавливаем их свойства:
| Location | 17; 12 |
| Size | 408; 136 |
| Text | Хранимая процедура proc_p1 |
| Location | 17; 156 |
| Size | 408; 64 |
| Text | Хранимая процедура proc_p5 |
| Location | 17; 228 |
| Size | 408; 56 |
| Text | Хранимая процедура proc6 |
| Name | txtFamily_p1 |
| Location | 16; 32 |
| Size | 288; 20 |
| Text | Введите фамилию туриста |
| Name | txtNameTour_p5 |
| Location | 16; 24 |
| Size | 136; 20 |
| Text | Введите название тура |
| Name | txtKurs_p5 |
| Location | 168; 24 |
| Size | 128; 20 |
| Text | Введите курс валюты |
| Name | btnRun_p1 |
| Location | 320; 32 |
| Text | Запуск |
| Name | btnRun_p5 |
| Location | 320; 24 |
| Text | Запуск |
| Name | btnRun_proc6 |
| Location | 16; 24 |
| Size | 208; 23 |
| Text | Цена самого дорогого тура |
| Name | lbResult_p1 |
| Location | 16; 72 |
| Size | 376; 43 |
| Name | lblPrice_proc6 |
| Location | 264; 24 |
| Text | |
| TextAlign | MiddleCenter |
Интерфейс приложения готов. Переходим в окно Server Explorer, раскрываем узел подключения к базе данных, перетаскиваем на форму процедуры proc_p1, proc_p5 и proc6 (рис. 7.1, А).
На панели компонент проекта появляются объект sqlConnection1 с тремя объектами sqlCommand (рис. 7.1, Б):

увеличить изображение
Рис. 7.1. Хранимые процедуры в окне Server Explorer. А - перемещение на форму, Б - готовая панель компонент
Среда настроила все нужные свойства объектов sqlCommand, такие как CommandType, CommandText, Connection. Выделяем объект sqlCommand1, переходим в окно Properties, в поле свойства Parameters нажимаем на кнопку
(...) (рис. 7.2):
увеличить изображение
Рис. 7.2. Окно Properties объекта sqlCommand1 и редактор SqlParameter Collection Editor
В появившемся окне редактора "SqlParameter Collection Editor" можно видеть настроенные свойства "Size" и "ParameterName" параметра "@Фамилия". Эти значения были получены из базы данных. Аналогичным образом настроены другие объекты sqlCommand. Переходим в код формы. Подключаем пространство имен для работы с базой данных:
using System.Data.SqlClient;
Далее нам нужно выбрать, какой из методов объекта Command нужно применить. Для хранимой процедуры proc_p1 это будет ExecuteReader - возвращаемое значение представляет собой запись (см. таблицу 5.2). Добавляем обработчик кнопки btnRun_p1:
private void btnRun_p1_Click(object sender, System.EventArgs e) { string FamilyParameter = Convert.ToString(txtFamily_p1.Text); sqlCommand1.Parameters["@Фамилия"].Value = FamilyParameter; sqlConnection1.Open(); SqlDataReader dataReader = sqlCommand1.ExecuteReader(); while (dataReader.Read()) { // Создаем переменные, получаем для них значения из объекта dataReader, //используя метод GetТипДанных int TouristID = dataReader.GetInt32(0); string Family = dataReader.GetString(1); string FirstName = dataReader.GetString(2); string MiddleName = dataReader.GetString(3); //Выводим данные в элемент lbResult_p1 lbResult_p1.Items.Add("Код туриста: " + TouristID+ " Фамилия: " + Family + " Имя: "+ FirstName + " Отчество: " + MiddleName); } sqlConnection1.Close(); }
В результате выполнения процедуры proc_p1 изменяется значения поля "Цена" в таблице "Туры" - запрос не возвращает результатов. Поэтому здесь применяем метод ExecuteNonQuery:
private void btnRun_p5_Click(object sender, System.EventArgs e) { string NameTourParameter = Convert.ToString(txtNameTour_p5.Text); double KursParameter = double.Parse(this.txtKurs_p5.Text); sqlCommand2.Parameters["@nameTour"].Value = NameTourParameter; sqlCommand2.Parameters["@Курс"].Value = KursParameter; sqlConnection1.Open(); int UspeshnoeIzmenenie = sqlCommand2.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { MessageBox.Show("Изменения внесены", "Изменение записи"); } else { MessageBox.Show("Не удалось внести изменения", "Изменение записи"); } sqlConnection1.Close(); }
Процедура proc6 возвращает результат в виде значения наибольшей цены в таблице "Туры". Для вывода одиночного значения используем метод ExecuteScalar. Поскольку процедура не имеет входных параметров, обработчик кнопки btnRun_proc6 будет выглядеть предельно просто:
private void btnRun_proc6_Click(object sender, System.EventArgs e) { sqlConnection1.Open(); string MaxPrice = Convert.ToString(sqlCommand3.ExecuteScalar()); lblPrice_proc6.Text = MaxPrice; sqlConnection1.Close(); }
Запускаем приложение (рис. 7.3). Для просмотра результатов выполнения хранимой процедуры proc_p5 (таблицы "Туры") запускаем SQL Server Enterprise Manager.

Рис. 7.3. Готовое приложение VisualParametersSP
В программном обеспечении к курсу вы найдете приложение Visual ParametersSP (Code\Glava3\ VisualParametersSP).
Создадим в точности такое же приложение программно. Для того чтобы не делать заново интерфейс приложения, скопируем всю папку проекта VisualParametersSP, переименуем ее в "ProgrammParametersSP". Открываем проект и удаляем все объекты с панели компонент. В классе формы создаем строку подключения:
string connectionString = "integrated security=SSPI;data source=\".\"; persist security info=False; initial catalog=BDTur_firm2";
В каждом из обработчиков кнопок создаем объекты Connection и Command, определяем их свойства, для последнего добавляем нужные параметры в набор Parameters:
private void btnRun_p1_Click(object sender, System.EventArgs e) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc_p1]"; string FamilyParameter = Convert.ToString(txtFamily_p1.Text); myCommand.Parameters.Add("@Фамилия", SqlDbType.NVarChar, 50); myCommand.Parameters["@Фамилия"].Value = FamilyParameter; conn.Open(); SqlDataReader dataReader = myCommand.ExecuteReader(); while (dataReader.Read()) { // Создаем переменные, получаем для них значения // из объекта dataReader, используя метод GetТипДанных int TouristID = dataReader.GetInt32(0); string Family = dataReader.GetString(1); string FirstName = dataReader.GetString(2); string MiddleName = dataReader.GetString(3); //Выводим данные в элемент lbResult_p1 lbResult_p1.Items.Add("Код туриста: " + TouristID+ " Фамилия: " + Family + " Имя: "+ FirstName + " Отчество: " + MiddleName); } conn.Close(); }
private void btnRun_p5_Click(object sender, System.EventArgs e) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc_p5]"; string NameTourParameter = Convert.ToString(txtNameTour_p5.Text); double KursParameter = double.Parse(this.txtKurs_p5.Text); myCommand.Parameters.Add("@nameTour", SqlDbType.NVarChar, 50); myCommand.Parameters["@nameTour"].Value = NameTourParameter; myCommand.Parameters.Add("@Курс", SqlDbType.Float, 8); myCommand.Parameters["@Курс"].Value = KursParameter; conn.Open(); int UspeshnoeIzmenenie = myCommand.ExecuteNonQuery(); if (UspeshnoeIzmenenie !=0) { MessageBox.Show("Изменения внесены", "Изменение записи"); } else { MessageBox.Show("Не удалось внести изменения", "Изменение записи"); } conn.Close(); }
private void btnRun_proc6_Click(object sender, System.EventArgs e) { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connectionString; SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc6]"; conn.Open(); string MaxPrice = Convert.ToString(myCommand.ExecuteScalar()); lblPrice_proc6.Text = MaxPrice; conn.Close(); }
Результат работы приложения будет такой же, как и в случае применения визуальных средств студии1).
Сравните листинги приложений VisualParametersSP и Programm ParametersSP - в первом из них среда создала все объекты соединения и наборы параметров, нам оставалось только связать значения параметров с элементами управлений при помощи свойства Value. С набором Parameters объекта Command мы уже встречались в приложении ExamWinExecuteNonQuery, когда применяли параметризированные запросы.
В программном обеспечении к курсу вы найдете приложение Programm ParametersSP (Code\Glava3\ ProgrammParametersSP).
Вызов хранимых процедур, содержащих несколько SQL-конструкций
Хранимая процедура может содержать несколько SQL-конструкций, определяющих работу приложения. При ее вызове возникает задача распределения данных, получаемых от разных конструкций. Запускаем Visual Studio .NET, переходим на вкладку Server Explorer, раскрываем узел базы BDTur_firm2. Щелкаем правой кнопкой на узле Stored Procedures и в появившемся меню выбираем "New Stored Procedure". Процедура proc_NextResult будет состоять из двух конструкций: первая будет возвращать содержимое таблицы "Туристы", а вторая - содержимое таблицы "Туры":CREATE PROCEDURE proc_NextResult AS SET NOCOUNT ON SELECT *FROM Туристы SELECT * FROM Туры RETURN
После сохранения процедуры создайте новое Windows-приложение VisualNextResult. Перетаскиваем на форму элемент управления ListBox, его свойству Dock устанавливаем значение Bottom. Добавляем элемент Splitter (разделитель), свойству Dock которого также устанавливаем значение Bottom. Наконец, добавляем еще один элемент ListBox, свойству Dock которого устанавливаем значение Fill. Из окна Server Explorer перетаскиваем на форму только что созданную процедуру proc_NextResult. В классе формы добавляем пространство имен для работы с базой:
using System.Data.SqlClient;
Наша задача: в первый элемент ListBox вывести несколько произвольных столбцов таблицы "Туры", а во второй - несколько столбцов таблицы "Туристы". Конструктор формы будет иметь следующий вид:
public Form1() { InitializeComponent(); sqlConnection1.Open(); SqlDataReader dataReader = sqlCommand1.ExecuteReader(); while(dataReader.Read()) { listBox2.Items.Add(dataReader.GetString(1)+" "+dataReader.GetString(2)); } dataReader.NextResult(); while(dataReader.Read()) { listBox1.Items.Add(dataReader.GetString(1) + ". Дополнительная информация: "+dataReader.GetString(3)); } dataReader.Close(); sqlConnection1.Close(); }
Метод GetString объекта DataReader позволяет получать содержимое столбца c заданным индексом, приведенное к типу String.
В первом цикле while мы получаем результаты первого запроса SELECT, затем, вызывая метод NextResult объекта DataReader, переходим к результатам второго запроса. Запускаем приложение - в каждом элементе содержится свой набор записей (рис. 7.7):

Рис. 7.7. Готовое приложение VisualNextResult
Нетрудно сделать это же самое приложение без применения визуальных средств студии. Скопируйте папку приложения VisualNextResult и переименуйте ее в ProgrammNextResult. Открываем проект, удаляем все объекты с панели компонент формы. Конструктор формы примет следующий вид:
public Form1() { InitializeComponent(); SqlConnection conn = new SqlConnection(); conn.ConnectionString = "integrated security=SSPI;data source=\". \"; persist security info=False; initial catalog=BDTur_firm2"; SqlCommand myCommand = conn.CreateCommand(); myCommand.CommandType = CommandType.StoredProcedure; myCommand.CommandText = "[proc_NextResult]"; conn.Open(); SqlDataReader dataReader = myCommand.ExecuteReader(); while(dataReader.Read()) { listBox2.Items.Add(dataReader.GetString(1)+" "+dataReader.GetString(2)); } dataReader.NextResult(); while(dataReader.Read()) { listBox1.Items.Add(dataReader.GetString(1) + ". Дополнительная информация: "+dataReader.GetString(3)); } dataReader.Close(); conn.Close(); }
В программном обеспечении к курсу вы найдете приложения VisualNext Result и ProgrammNextResult (Code\Glava3\VisualNextResult и Programm NextResult).
Технология Microsoft ADO.NET
Объект DataRow
Содержимое объекта DataSet представляет собой набор записей, который представлен объектами DataRow. В запущенном приложении содержимое объекта DataSet доступно для изменений, например, если данные выводятся в элемент управления DataGrid, то, перемещаясь по отдельным клеткам, можно править значения как в обычной электронной таблице. При этом происходит изменение объекта DataRow, соответствующее заданной записи. Рассмотрим программное создание и изменение записей. Создайте новое Windows-приложение и назовите его ProgrammDataRow. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. В конструкторе формы создаем экземпляр dtTours и поля, соответствующие таблице "Туры":public Form1() { InitializeComponent(); DataTable dtTours = new DataTable(); DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32)); dсIDtour.Unique = true; DataColumn dcName = dtTours.Columns.Add("Название"); DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal)); DataColumn dcInformation = dtTours.Columns.Add("Информация"); DataView myDataView = new DataView(dtTours); dataGrid1.DataSource = myDataView; }
Для того чтобы привязать созданные данные к элементу управления DataGrid, нам понадобилось создать экземпляр myDataView класса DataView. Каждый объект DataTable содержит объект DataView, причем этот объект, используемый по умолчанию, называется DataTable.DefaultView. Мы уже сталкивались с ним неоднократно, например, в предыдущем проекте CustomExpression для вывода данных:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Один объект DataTable может иметь несколько объектов DataView - это удобно для вывода одних и тех же данных, отфильтрованных или отсортированных различным образом. В следующей лекции мы рассмотрим подробно DataView. Запускаем приложение (рис. 8.13). Мы видим готовую структуру таблицы "Туры":

Рис. 8.13. Структура таблицы "Туры"
Мы не будем сейчас подключаться к какой-либо базе данных - попробуем заполнить таблицу записями программно. Для добавлений одной новой записи перед созданием экземпляра myDataView вставляем следующий фрагмент кода:
DataRow myRow = dtTours.NewRow(); myRow["Код тура"] = 1; myRow["Название"] = "Кипр"; myRow["Цена"] = 25000; myRow["Информация"] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow);
Запускаем приложение (рис. 8.14). В таблице появилась первая запись.

Рис. 8.14. Добавление записи в таблицу
Добавим еще одну запись:
DataRow myRow2 = dtTours.NewRow(); myRow2["Код тура"] = 2; myRow2["Название"] = "Греция"; myRow2["Цена"] = 32000; myRow2["Информация"] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Название, указываемое в квадратных скобках объектов myRow или myRow2, представляет собой имя столбца, которые мы определили в самом начале. К столбцу можно обращаться и по индексу - закомментируйте добавление двух записей и внесите следующий код:
DataRow myRow = dtTours.NewRow(); myRow[0] = 1; myRow[1] = "Кипр"; myRow[2] = 25000; myRow[3] = "В стоимость двух взрослых путевок входит цена одной детской (до 7 лет)"; dtTours.Rows.Add(myRow);
DataRow myRow2 = dtTours.NewRow(); myRow2[0] = 2; myRow2[1] = "Греция"; myRow2[2] = 32000; myRow2[3] = "В августе и сентябре действуют специальные скидки"; dtTours.Rows.Add(myRow2);
Нумерация столбцов начинается с нуля. Более удобный способ добавления записей - применение свойства ItemArray объекта DataRow, где можно задавать значения полей в виде массива:
DataRow myRow3 = dtTours.NewRow(); myRow3.ItemArray = new object[]{3,"Таиланд", 30000, null}; dtTours.Rows.Add(myRow3);
Здесь мы указали значение поля "Информация", равное null, - таким образом можно пропускать неизвестные поля (рис. 8.15):

Рис. 8.15. Вставка записи с одним значением null
В программном обеспечении к курсу вы найдете приложение Programm DataRow (Code\Glava4\ ProgrammDataRow).
Конечно, вставка записей вручную в объект DataSet, не связанный с хранилищем данных, имеет не очень большой смысл. Поэтому давайте рассмотрим, как вставлять (и изменять) данные в уже загруженный кэш данных. Скопируйте папку проекта CustomExpression и переименуйте ее в WorkWithRow. Чтобы не тратить время на привязку действий к элементам управления, будем далее писать код после отображения данных в элементе DataGrid:
dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView;
Для изменения, например, пятой строки, мы указываем в свойстве Rows объекта dtTours ее индекс (4, нумерация начинается с нуля), затем вызываем метод BeginEdit для начала редактирования, устанавливаем группу свойств и в заключение принимаем изменения, вызывая метод EndEdit:
DataRow myRow=dtTours.Rows[4]; myRow.BeginEdit(); myRow["Код тура"] = 5; myRow["Название"] = "Турция"; myRow["Цена"] = "27000"; myRow["Информация"] = "Осенние скидки с 15 октября"; myRow.EndEdit();
Тот же самый результат мы получим с помощью свойства ItemArray:
DataRow myRow=dtTours.Rows[4]; myRow.BeginEdit(); myRow.ItemArray = new object[]{5,"Турция", 27000, null, null, "Осенние скидки с 15 октября"}; myRow.EndEdit();
Здесь мы установили для третьего и четвертого полей, которые являются вычисляемыми, значения null, подразумевая, что они останутся по умолчанию, а при запуске заполнятся своими значениями (рис. 8.16):

Рис. 8.16. Пропущенные вычисляемые поля заполняются своими значениями
Для удаления заданной записи нужно создать объект DataRow, которому передается индекс строки, а затем вызвать метод Remove свойства Rows объекта DataTable:
DataRow myRow2 = dtTours.Rows[0]; dtTours.Rows.Remove(myRow2);
Этого достаточно для удаления строки, но для того, чтобы пометить заданную строку как удаленную, вызываем метод Delete:
myRow2.Delete();
В результате у нас удалится строка (рис. 8.17), причем объект DataTable пометит ее в качестве удаленной - это необходимо, чтобы избежать ошибок (например, в связанных записях).

увеличить изображение
Рис. 8.17. Первая строка, имеющая индекс 0, была удалена
В программном обеспечении к курсу вы найдете приложение WorkWith Row (Code\Glava4\ WorkWithRow).
Понятие DataSet, DataTable и DataColumn
Итак, DataSet представляет собой буфер для хранения данных из базы. Этот буфер предназначен для хранения структурированной информации, представленной в виде таблиц, поэтому первым, самым очевидным вложенным объектом DataSet является DataTable. Внутри одного объекта DataSet может храниться несколько загруженных таблиц из базы данных, помещенных в соответствующие объекты DataTable. Всякая таблица состоит из столбцов (называемых также полями или колонками) и строк. Для обращения к ним и для управления столбцами и строками в объекте DataTable предназначены специальные объекты - DataColumn и DataRow. Между таблицами, как мы знаем, могут быть связи - здесь они представлены объектом DataRelation. Наконец, в таблицах есть первичные и вторичные ключи - объект Constraint со своими двумя подклассами UniqueConstraint и ForeighKeyConstraint описывают их. Я все вр емя говорю "представлены", "описывают", избегая слов "отображают" и "определяются" - дело в том, что нельзя ставить знак равенства между, например, объектом DataRelation и связью таблиц. В загруженных таблицах не формируются автоматически все нужные объекты - кое-где мы должны делать это самостоятельно. Сами объекты имеют также довольно тонкую и сложную структуру, поэтому это было бы довольно грубым приближением. Однако, на первых порах, для понимания сути полезно держать в голове следующие "формулы":DataSet = одна или несколько таблиц = один или несколько объектов DataTable. DataTable = таблица. DataColumn = столбец, поле, колонка. DataRow = строка. DataTable = таблица = несколько полей, столбцов, колонок = несколько объектов DataColumn. DataTable = таблица = несколько строк = несколько объектов DataRow. DataRelation = связь между таблицами.
Возникает вопрос: для чего нужны эти объекты, если мы прекрасно обходились и без них для вывода содержимого таблицы, например в элемент DataGrid? Дело в том, что для простого отображения информации создавать эти объекты не требуется, но тогда все данные будут однородными текстовыми переменными, подобно таблицам в документе Microsoft Word.
DataSet не может сам сформировать структуру данных - тип переменных, первичные и вторичные ключи, связи между таблицами. Для управления структурой, для сложного отображения (например, вывод информации с привязкой к элементам, создаваемым в режиме работы приложения) и нужно определение этих объектов.
Лучший способ разобраться с работой всех объектов - применить их на практике. Создадим простую тестовую программу, в которой можно отвечать на вопросы, перемещаться по ним и определять количество верных ответов. Для хранения вопросов и вариантов ответов создадим базу данных Tests Microsoft SQL. База будет состоять всего из двух таблиц (рис. 8.1):

Рис. 8.1. Структура базы Tests (диаграмма "QuestVar")
Каждый вопрос будет содержать несколько ответов, для синхронного перемещения по структуре нужна связь по полю questID. Галочка "Allow Nulls" (Разрешить пустые значения) снята для всех полей - это означает, что все поля будут обязательными для заполнения. Структура таблиц Questions и Variants приводится в таблице 8.1:
| Questions - таблица вопросов | questID Номер вопроса |
| question Текст вопроса | |
| questType Тип вопроса (с одним правильным вариантом ответа или с несколькими) | |
| Variants - таблица вариантов ответов | id Номер ответа |
| questID Номер вопроса | |
| variant Текст варианта ответа | |
| isRight Является ли данный вопрос верным |
| 1 | Для переустановки операционной системы Windows XP вам необходимо экспортировать банк сообщений программы Microsoft Outlook Express, расположенный по адресу: | 0 |
| 2 | При компиляции программы в среде Microsoft Visual Studio .NET возникает систематическая ошибка в модуле AssemblyInfo из-за неудачного выбора имени пользователя и организации (были использованы кавычки) при установке системы (Для просмотра: "Мой компьютер - правая кнопка - Свойства - вкладка "Общие""). Вам необходимо изменить эти параметры | 0 |
| 3 | Выберите группы, состоящие из файлов, размер которых после архивирования составляет 5-10% от исходного | 1 |
| 4 | Укажите ряд, состоящий из агрегатных (агрегаторных) функций SQL | 0 |
| 5 | Вы изменили ключ BootExecute в разделе реестра [HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager]. В результате было выполнено следующее действие: | 0 |
| 1 | 1 | C:\Program Files\Outlook Express\Mail | 0 |
| 2 | 1 | C:\Documents and Settings\Имя_Пользователя\ Local Settings\Application Data\Identities\ {F4CB90C4-3FD5-406B-83FB-85E644627B87}\Microsoft\Outlook Express | 1 |
| 3 | 1 | C:\WINDOWS\system32\Microsoft\Outlook Express\Bases | 0 |
| 4 | 1 | C:\Documents and Settings\Default User\Cookies | 0 |
| 5 | 2 | "Мой компьютер - правая кнопка - Свойства - Вкладка "Общие" на подписи - правая кнопка - Свойства - Переименовать" | 0 |
| 6 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion] затем меняем параметры RegisteredOwner и RegisteredOrganization | 1 |
| 7 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT] удаляем раздел CurrentVersion | 0 |
| 8 | 2 | [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run] | 0 |
| 9 | 3 | *.mpeg, *.mdb, *. Htm | 0 |
| 10 | 3 | *.xls, *.txt , *.mht | 1 |
| 11 | 3 | *. jpeg, *.gif, *.mp3 | 0 |
| 12 | 3 | *. doc, *. xml, *.bmp | 1 |
| 13 | 4 | update, insert, sum | 0 |
| 14 | 4 | where, like, create | 0 |
| 15 | 4 | count, min, max | 1 |
| 16 | 4 | select, avg, into | 0 |
| 17 | 5 | Отключилась проверка на ошибки всех дисковых разделов при загрузке Windows ХР | 1 |
| 18 | 5 | Установлен таймер на автоматическое отключение | 0 |
| 19 | 5 | Отключены все диагностические сообщения | 0 |
| 20 | 5 | Система перестанет загружаться | 0 |
В поле isRight таблицы Variants правильные ответы отмечаются значением "1". Понятно, что для практического применения тестовой программы следует ограничить доступ к базе данных Tests. Мы, однако, будем использовать подключения без пароля.
Создайте новое Windows-приложение и назовите его "Tests". Устанавливаем следующие свойства формы:
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| Size | 550; 350 |
| Text | Тест |
| Location | 16;8 |
| Text | Вопрос: |
| Name | txtQuestion |
| Location | 12; 24 |
| Multiline | True |
| Size | 520; 90 |
| TabIndex | 8 |
| Text |
| Name | GbVariants |
| Location | 12; 120 |
| Size | 520; 150 |
| Text | Варианты ответов |
| Name | btnFirst |
| Location | 24; 280 |
| Text | << |
| Name | BtnPrev |
| Location | 99; 280 |
| Text | < |
| Name | BtnNext |
| Location | 174; 280 |
| TabIndex | 0 |
| Text | > |
| Name | BtnLast |
| Location | 249; 280 |
| Text | > |
| Name | BtnCheck |
| Location | 360; 280 |
| Size | 150; 23 |
| Text | Результат |
В свойстве Tables элемента DataSet нажимаем на кнопку
(...), запускается редактор Table Collection Editor (рис. 8.2), нажимаем кнопку "Add" и вводим следующие значения свойств:| TableName | Questions |
| Name | dtQuestions |

увеличить изображение
Рис. 8.2. Запуск редактора Table Collection Editor
Значение dtQuestions свойства Name указывает название созданного объекта DataTable, а значение Questions свойства TableName указывает название таблицы, которая будет помещена в DataTable.
Добавим поля к объекту DataTable. В редакторе Tables Collection Editor в поле свойства Columns нажимаем на кнопку
(...), появляется редактор Columns Collection Editor (рис. 8.3), нажимаем кнопку Add. Всего нужно будет создать три поля: questID, question и questType:| ColumnName | questID |
| DataType | System.Int32 |
| Unique | True |
| Name | dсQuestID |
| ColumnName | question |
| Name | dcQuestion |
| ColumnName | questType |
| DataType | System.Int32 |
| Name | dcQuestType |

увеличить изображение
Рис. 8.3. Запуск редактора Columns Collection Editor
Завершив работу с редактором Columns Collection Editor, нажимаем кнопку Close. Мы закончили создание объекта DataTable для таблицы Questions. Аналогичные действия надо проделать, чтобы создать DataTable для таблицы Variants (рис. 8.4) и соответствующих полей id, questID, variant и isRight:
| TableName | Variants |
| Name | dtVariants |
| ColumnName | id |
| DataType | System.Int32 |
| Unique | True |
| Name | dcID |
| ColumnName | questID |
| DataType | System.Int32 |
| Name | dcVariantQuestID |
| ColumnName | variant |
| Name | dcVariant |
| ColumnName | isRight |
| DataType | System.Boolean |
| Name | dcIsRight |

увеличить изображение
Рис. 8.4. Создание объектов DataTable и DataColumn для таблицы Variants
В базе данных Tests таблицы Questions и Variants мы связали по полю questID. Теперь при проектировании схемы базы нам следует также создать это отношение. В окне Properties объекта DataSet нажимаем на кнопку (_) в поле свойства Relations. В появившемся редакторе Relations Collection Editor нажимаем кнопку "Add" для добавления связи. Называем отношение "QuestionsVariants", а в качестве ключевого поля указываем questID (рис. 8.5):

Рис. 8.5. Создание отношения QuestionsVariants
Мы закончили работу с визуальными средствами. Программное создание объектов DataTable, DataColumn, DataRelation (см.
далее) обеспечивает, при прочих равных условиях, большую производительность, однако редакторы позволяют быстрее осуществлять редактирование и обладают большей наглядностью. На первых порах проще работать именно с ними, но по мере роста опыта следует отказаться от их применения.
Переходим в код формы, подключаем пространство имен:
using System.Data.SqlClient;
В классе создаем перечисление QuestionType - вопросу с одним правильным вариантом будет соответствовать постоянная SingleVariant, вопросу с несколькими вариантами - MultiVariant:
public enum QuestionType { SingleVariant, MultiVariant }
Создаем перечисление Direction для навигации по вопросам:
public enum Direction { First, Prev, Next, Last }
В методе LoadDataBase заполняем объект DataSet данными из базы данных:
private void LoadDataBase() { SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=Tests;Integrated Security=SSPI;");
SqlDataAdapter questAdapter = new SqlDataAdapter("select * from questions", conn); SqlDataAdapter variantsAdapter = new SqlDataAdapter("select * from variants", conn); conn.Open(); //Заполняем таблицу "Questions" данными из questAdapter questAdapter.Fill(dsTests.Tables["Questions"]); //Заполняем таблицу "Variants" данными из variantsAdapter variantsAdapter.Fill(dsTests.Tables["Variants"]); conn.Close(); }
Создаем два экземпляра класса Hashtable1):
// Экземпляр clientAnswers для хранения ответов пользователей Hashtable clientAnswers = new Hashtable(); // Экземпляр keys для хранения правильных ответов Hashtable keys = new Hashtable();
Создаем метод InitAnswersKeysTable, в котором связываем элементы экземпляров Hashtable с записями таблиц
private void InitAnswersKeysTable() { // Создаем цикл, длина которого равна числу записей в таблице "Questions" for(int i = 0; i < dsTests.Tables["Questions"].Rows.Count; i++) { // Выбираем записи из таблицы "Questions". DataRow drquestion = dsTests.Tables["Questions"].Rows[i];
// Выбираем записи из таблицы "Variants". DataRow[] drvariants = drquestion.GetChildRows (dsTests.Relations["QuestionsVariants"]); // Устанавливаем значение j, равное false для всех вариантов. bool[] answers = new bool[drvariants.Length]; for(int j = 0; j < answers.Length; j++) answers[j] = false;
// Добавляем значения к экземплярам Hashtable keys.Add(drquestion, drvariants); clientAnswers.Add(drquestion, answers); } }
Объект DataRow предназначен для просмотра и изменения содержимого отдельной записи в объекте DataTable. Для обращения к конкретной записи используется свойство Rows[i], где i - номер записи. Метод GetChildRows позволяет обращаться к дочерним записям, он принимает название отношения. Здесь мы фактически обращаемся к записям таблицы Variants. В экземпляре keys ключами будут записи из таблицы Questions, а значениями - элементы массива записей с вариантами ответов. В экземпляре clientAnswers ключами также будут записи из таблицы Questions, а значениями - элементы массива типа bool, зависящие от ответа пользователя.
В классе формы создаем экземпляр cmTest класса CurrencyManager для перемещения по записям:
CurrencyManager cmTest = null;
В методе InitDefaultSettings определяем настройки по умолчанию:
private void InitDefaultSettings() { //В свойство Tag каждой кнопки помещаем константу из перечисления Direction btnFirst.Tag = Direction.First; btnPrev.Tag = Direction.Prev; btnNext.Tag = Direction.Next; btnLast.Tag = Direction.Last;
//Для всех кнопок будет один обработчик btnFirst_Click btnFirst.Click += new EventHandler(btnFirst_Click); btnPrev.Click += new EventHandler(btnFirst_Click); btnNext.Click += new EventHandler(btnFirst_Click); btnLast.Click += new EventHandler(btnFirst_Click); //Вызываем метод LoadDataBase(); //Определяем действия для случая, //если нет записей в таблице "Questions" if(dsTests.Tables["Questions"].Rows.Count == 0) { txtQuestion.Text = "Нет данных о вопросах"; btnFirst.Enabled= false; btnPrev.Enabled= false; btnNext.Enabled= false; btnLast.Enabled= false; btnCheck.Enabled= false; } else { //Вызываем метод.
InitAnswersKeysTable(); //Связываем эземпляр cmTest с содержимым таблицы "Questions" cmTest = (CurrencyManager)this.BindingContext[dsTests, "Questions"]; //Определяем обработчик для события cmTest.PositionChanged += new EventHandler(cmTest_PositionChanged); ShowQuestion(dsTests.Tables["questions"].Rows[0]); //Включаем доступность кнопок ">" и ">>" btnFirst.Enabled= false; btnPrev.Enabled= false; btnNext.Enabled= true; btnLast.Enabled= true; } }
Создаем метод cmTest_PositionChanged, для обработки события PositionChanged объекта cmTest, в котором определяем доступность кнопок навигации:
private void cmTest_PositionChanged(object sender, EventArgs e) { if (cmTest.Position == 0) { btnPrev.Enabled = false; btnFirst.Enabled = false; btnNext.Enabled = true; btnLast.Enabled = true; } else if(cmTest.Position == dsTests.Tables["questions"].Rows.Count - 1) { btnNext.Enabled = false; btnLast.Enabled = false; btnPrev.Enabled = true; btnFirst.Enabled = true; } else { btnPrev.Enabled = true; btnFirst.Enabled = true; btnNext.Enabled = true; btnLast.Enabled = true; } }
Создаем метод btnFirst_Click - общий обработчик для всех кнопок навигации:
private void btnFirst_Click(object sender, EventArgs e) { Button btn = (Button)sender; Direction direction = (Direction)btn.Tag;
switch (direction) { case Direction.First: cmTest.Position = 0; break; case Direction.Prev: --cmTest.Position; break; case Direction.Next: ++cmTest.Position; break; case Direction.Last: cmTest.Position = dsTests.Tables["questions"].Rows.Count - 1; break; }
int rowIndex = cmTest.Position; //Вызываем метод ShowQuestion, который выводит вопросы на форму ShowQuestion(dsTests.Tables["questions"].Rows[rowIndex]); }
Вызываем метод InitDefaultSettings в конструкторе формы:
public Form1() { InitializeComponent(); InitDefaultSettings(); }
В методе ShowQuestion выводим вопрос на форму:
private void ShowQuestion(DataRow drquestion) { txtQuestion.Text = drquestion["question"].ToString(); //Вызываем метод ShowVariants, который выводит на форму варианты ответов ShowVariants(drquestion); }
В методе ShowVariants в зависимости от типа вопроса формируется набор элементов RadioButton или CheckBox c вариантами ответов, который затем выводится в элемент gbVariants:
private void ShowVariants(DataRow question) { //Удаляем все элементы из GroupBox gbVariants.Controls.Clear(); //Снова создаем экземпляр childVariants //для обращения к записям таблицы "Variants" DataRow[] childVariants = question.GetChildRows (dsTests.Relations["QuestionsVariants"]);
//Определяем тип вопроса bool[] vars = (bool[])clientAnswers[question]; int i = 0; QuestionType questType = (QuestionType)question["questType"]; switch(questType) { //Если вопрос имеет всего один правильный вариант, //на форме будут созданы элементы Radiobutton case QuestionType.SingleVariant: foreach(DataRow childVariant in childVariants) { RadioButton rb = new RadioButton(); #region Ищем выбранный ответ в таблице ответов bool selectedAnswer = (bool)vars[i++]; rb.Checked = selectedAnswer; #endregion //Определяем свойства созданного элемента RadioButton rb.Text = childVariant["variant"].ToString(); rb.Tag = childVariant; rb.CheckedChanged += new EventHandler(rb_CheckedChanged); int y = (gbVariants.Controls.Count == 0)?20: ((RadioButton)gbVariants.Controls[gbVariants.Controls.Count - 1]).Bottom + 2; //Определяем размеры создаваемых элементов RadioButton //500 - ширина в пикселях, rb.Height+5 - высота rb.Size = new Size(500, rb.Height+5); rb.Location = new Point(10, y); gbVariants.Controls.Add(rb); } break; //Если вопрос имеет несколько правильных вариантов, //на форме будут созданы элементы CheckBox case QuestionType.MultiVariant: foreach(DataRow childVariant in childVariants) { CheckBox chb = new CheckBox(); #region Ищем выбранный ответ в таблице ответов bool selectedAnswer = (bool)vars[i++]; chb.Checked = selectedAnswer; #endregion //Определяем свойства созданного элемента RadioButton chb.Text = childVariant["variant"].ToString(); chb.Tag = childVariant; chb.CheckedChanged += new EventHandler(chb_CheckedChanged); int y = (gbVariants.Controls.Count == 0)?20: ((CheckBox)gbVariants.Controls[gbVariants.Controls.Count - 1]).Bottom + 2; //Определяем размеры создаваемых элементов RadioButton //500 - ширина в пикселях, chb.Height+5 - высота chb.Size = new Size( 500, chb.Height+5); chb.Location = new Point(10, y); gbVariants.Controls.Add(chb); } break; } }
Когда пользователь отметит галочкой элементы CheckBox или выберет RadioButton, будет происходить событие CheckedChanged. В этом событии мы фиксируем положение отмеченного элемента - при возврате к решенному вопросу пользователь будет видеть свои ответы:
private void rb_CheckedChanged(object sender, EventArgs e) { RadioButton rb = (RadioButton)sender; if(!rb.Checked) return; //Создаем объект drvariant класса DataRow, с которым связываем //свойство Tag элемента RadioButton DataRow drvariant = (DataRow)rb.Tag; //Отмечаем текущее положение объекта cmTest int questIndex = cmTest.Position; DataRow drquestion = dsTests.Tables["Questions"].Rows[questIndex]; int answIndex = gbVariants.Controls.IndexOf(rb); //Выводим элемент RadioButton отмеченным, если он уже был выбран bool[] answers = (bool[])clientAnswers[drquestion]; for(int i = 0; i < answers.Length; i++) { if(i == answIndex) answers[i] = rb.Checked; else answers[i] = !rb.Checked; } }
private void chb_CheckedChanged(object sender, EventArgs e) { CheckBox chb = (CheckBox)sender; //Создаем объект drvariant класса DataRow, с которым связываем //свойство Tag элемента CheckBox DataRow drvariant = (DataRow)chb.Tag; //Отмечаем текущее положение объекта cmTest int rowIndex = cmTest.Position; DataRow drquestion = dsTests.Tables["Questions"].Rows[rowIndex]; int answIndex = gbVariants.Controls.IndexOf(chb); //Выводим элемент CheckBox отмеченным, если он уже был выбран bool[] answers = (bool[])clientAnswers[drquestion]; for(int i = 0; i< answers.Length; i++) { if (i == answIndex) { answers[i] = chb.Checked; break; } } }
Переключаемся в режим дизайна, щелкаем на кнопке "Результат" - в обработчике события подсчитываем количество правильных ответов:
private void btnCheck_Click(object sender, System.EventArgs e) { //Создаем счетчик double counter = 0; //Перебираем все ключи экземпляра key класса Hashtable, //в котором хранятся ответы пользователя foreach(object key in keys.Keys) { bool flag = true; DataRow[] drvariants = (DataRow[])keys[key]; bool[] answers = (bool[])clientAnswers[key]; int i = 0; foreach(DataRow variant in drvariants) { if(((bool)variant["isRight"]) == answers[i++]) continue; else { flag = false; break; } } if (flag) ++counter; } //Делим количество правильных ответов на общее число ответов, //результат умножаем на 100 int result = (int)(counter / dsTests.Tables["questions"].Rows.Count * 100); MessageBox.Show(String.Format("Вы ответили правильно на {0}% вопросов.", result), "Результат тестирования", MessageBoxButtons.OK, MessageBoxIcon.Information); }
Запускаем приложение (рис. 8.6). Мы рассмотрели простейший случай подсчета результатов - конечно же, в реальных приложениях кнопка "Результат" не может быть доступна в любой момент времени. Впрочем, это достаточно легко изменить.

увеличить изображение
Рис. 8.6. Готовое приложение Tests. А - вид формы с двумя правильными ответами, Б - вид формы с одним правильным ответом, В - результат тестирования
В программном обеспечении к курсу вы найдете приложение Tests (Code\Glava4\Tests).
Программное создание объектов
Еще в первой лекции мы узнали, что все объекты вкладки Data или все объекты ADO .NET можно создавать программно. Скопируйте папку приложения Tests и назовите ее "ProgrammTests". Открываем проект, удаляем объект DataSet с панели компонент формы. Для создания этого объекта программно достаточно следующей строчки кода:DataSet dsTests = new DataSet();
Поскольку созданный экземпляр dsTests будет использоваться в нескольких методах, эту строку следует написать в классе формы. Здесь dsTests - это название объекта DataSet, которое мы затем используем в коде - это известно нам с первой лекции. Среда Visual Studio .NET однако, сгенерировала следующий фрагмент кода (его можно найти в исходном проекте "Tests"):
this.dsTests = new System.Data.DataSet(); ... // // dsTests // this.dsTests.DataSetName = "NewDataSet"; this.dsTests.Locale = new System.Globalization.CultureInfo("ru-RU");
Как всегда, автоматический код избыточен - здесь указывается культура ru-RU. Дело в том, что у меня установлена русская версия Windows XP и соответствующие региональные настройки.
Среда определила еще свойство DataSetName = "NewDataSet", эквивалентное описание будет иметь следующий вид:
DataSet dsTests = new DataSet("NewDataSet");
Что же это за еще одно название объекта DataSet? Свойство DataSetName используется для работы с XSD-схемами, пока про это название (до лекции 10) мы можем просто забыть.
Создадим теперь объект DataTable для таблицы Questions:
DataTable dtQuestions = dsTests.Tables.Add("Questions"); //Или //DataTable dtQuestions = new DataTable("Questions"); //dsTests.Tables.Add(dtQuestions);
Здесь мы создаем экземпляр dtQuestions объекта DataTable, затем вызываем метод Add свойства Tables объекта dsTests, которому передаем название таблицы Questions. Далее создаем поля в объекте dtQuestions:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32)); dсQuestID.Unique = true; DataColumn dcQuestion = dtQuestions.Columns.Add("question"); DataColumn dcQuestType = dtQuestions.Columns.Add("questType", typeof(Int32));
Мы создаем поля, нужные для отражения соответствующих столбцов в таблице Questions. Перегруженный метод Add свойства Columns объекта dtQuestions позволяет задавать название столбца и его тип данных (рис. 8.7):

увеличить изображение
Рис. 8.7. Создание поля
Свойство Unique указывает, что в этом поле не должно быть повторяющихся значений, оно должно быть уникальным (здесь - поле questID является первичным ключом таблицы Questions).
Точно так же создаем объект DataTable для таблицы Variants и соответствующие поля:
//Cоздаем таблицу "Variants" DataTable dtVariants = dsTests.Tables.Add("Variants");
//Заполняем поля таблицы "Variants" DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32)); dcID.Unique = true; dcID.AutoIncrement = true; DataColumn dcVariantQuestID = dtVariants.Columns.Add("questID", typeof(Int32)); DataColumn dcVariant = dtVariants.Columns.Add("variant"); DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean));
Здесь мы дополнительно установили свойству AutoIncrement объекта dcID значение true. Свойство AutoIncrement (Счетчик) позволяет создать счетчик для поля, аналогичный типу данных "Счетчик" в Microsoft Access.
Теперь приступим к созданию связи между таблицами. В базе данных Microsoft SQL Tests между родительской таблицей Questions и дочерней Variants была установлена связь по полю questID, которое было в обеих таблицах. При программном создании объектов для поля questID таблицы Questions был создан объект dсQuestID, для этого же поля таблицы Variants создан объект dcVariantQuestID. В коде создание отношения между таблицами будет иметь следующий вид:
DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants", dсQuestID, dcVariantQuestID); dsTests.Relations.Add(drQuestionsVariants);
Здесь в конструкторе drQuestionsVariants - название экземпляра объекта (класса) DataRelation, а QuestionsVariants - свойство relationName - название связи, которая будет содержаться в объекте drQuestionsVariants.
Другими словами, drQuestionsVariants - название экземпляра DataRelation, которое мы будем использовать в коде, а свойство relationName - всего лишь название отражаемой связи, которую можно удалить или переименовать.
Итак, мы создали все объекты для отображения таблиц, полей и даже связей между таблицами.
Теперь нам осталось определить некоторые свойства таблиц, называемые ограничениями. Свойство ограничения (Constraint) объекта DataTable бывает двух типов - UniqueConstraint и ForeignKeyConstraint. Свойство UniqueConstraint определяет первичный ключ таблицы, например, в таблице Questions ключевым полем является questID. Объект dсQuestID представляет это поле:
DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32));
Ограничение UniqueConstraint, налагаемое на объект dсQuestID, запрещает появление дублированных строк:
UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID); dtQuestions.Constraints.Add(UC_dtQuestions);
Однако при создании объекта dсQuestID мы ведь уже определяли его уникальность:
dсQuestID.Unique = true;
Действительно, последняя строка представляет собой неявный способ задания ограничения UniqueConstraint. Если мы уже определили уникальное поле или поля, используя свойство Unique, задавать ограничение UniqueConstraint не нужно.
Второе ограничение - ForeignKeyConstraint - определяет, как должны себя вести дочерние записи при изменении родительских записей и наоборот. Конечно, интерфейс нашего приложения вообще не подразумевает внесение изменений, но ADO .NET требует точного описания объектов для управления ими. Ограничение ForeignKeyConstraint содержит следующие три правила:
Для этих правил могут применяться следующие значения:
Значением по умолчанию для правил UpdateRule и DeleteRule является Cascade, для правила AcceptRejectRule - None. Дополнительно, правило AcceptRejectRule принимает значения только Cascade или None.
Создадим ограничение для связи QuestionsVariants:
ForeignKeyConstraint FK_QuestionsVariants = new ForeignKeyConstraint(dtQuestions.Columns["questID"], dtVariants.Columns["questID"]); dtVariants.Constraints.Add(FK_QuestionsVariants);
Здесь задается вначале родительская колонка, а затем дочерняя (рис. 8.8). Добавлять созданное ограничение следует к объекту DataTable, представляющему дочернюю таблицу (в данном случае - объект dtVariants)

увеличить изображение
Рис. 8.8. Создание ограничения FK_QuestionsVariants
Этот фрагмент кода оставляет значения правил UpdateRule, DeleteRule и AcceptRejectRule заданными по умолчанию, т.е. Cascade и None, что соответствует значениям настройки с помощью мастера Relation (рис. 8.9):

увеличить изображение
Рис. 8.9. Мастер Relation и соответствующий фрагмент кода
Если бы нам потребовалось задать значение одному из правил, отличное по умолчанию, мы бы просто применили другой вариант конструктора (рис. 8.10):

увеличить изображение
Рис. 8.10. В этом конструкторе можно задать значения правил RejectRule, DeleteRule и UpdateRule
Полностью2) метод LoadDataBase в проекте ProgrammTests будет выглядеть так:
private void LoadDataBase() { SqlConnection conn = new SqlConnection("Data Source=.;Initial Catalog=Tests;Integrated Security=SSPI;"); SqlDataAdapter questAdapter = new SqlDataAdapter("select * from questions", conn); SqlDataAdapter variantsAdapter = new SqlDataAdapter("select * from variants", conn); //dsTests.EnforceConstraints = true; //Cоздаем таблицу "Questions" DataTable dtQuestions = dsTests.Tables.Add("Questions"); //Или //DataTable dtQuestions = new DataTable("Questions"); //dsTests.Tables.Add(dtQuestions); //Заполняем поля таблицы "Questions" DataColumn dсQuestID = dtQuestions.Columns.Add("questID", typeof(Int32)); dсQuestID.Unique = true; //Или //UniqueConstraint UC_dtQuestions = new UniqueConstraint(dсQuestID); //dtQuestions.Constraints.Add(UC_dtQuestions); DataColumn dcQuestion = dtQuestions.Columns.Add("question"); DataColumn dcQuestType = dtQuestions.Columns.Add ("questType", typeof(Int32)); //Cоздаем таблицу "Variants" DataTable dtVariants = dsTests.Tables.Add("Variants"); //Заполняем поля таблицы "Variants" DataColumn dcID = dtVariants.Columns.Add("id", typeof(Int32)); dcID.Unique = true; dcID.AutoIncrement = true; DataColumn dcVariantQuestID = dtVariants.Columns.Add("questID", typeof(Int32)); DataColumn dcVariant = dtVariants.Columns.Add("variant"); DataColumn dcIsRight = dtVariants.Columns.Add("isRight", typeof(Boolean)); //Создаем ограничение ForeignKeyConstraint FK_QuestionsVariants = new ForeignKeyConstraint(dtQuestions.Columns["questID"], dtVariants.Columns["questID"]); dtVariants.Constraints.Add(FK_QuestionsVariants); //Создаем отношение DataRelation drQuestionsVariants = new DataRelation("QuestionsVariants", dсQuestID, dcVariantQuestID); dsTests.Relations.Add(drQuestionsVariants); conn.Open(); //Заполняем таблицу "Questions" данными из questAdapter questAdapter.Fill(dsTests.Tables["Questions"]); //Заполняем таблицу "Variants" данными из variantsAdapter variantsAdapter.Fill(dsTests.Tables["Variants"]); conn.Close(); }
Обратим внимание на несколько деталей этого метода. Значение true свойства EnforceConstraints объекта dsTests разрешает использование ограничений. По умолчанию в созданном объекте DataSet это свойство и так принимает значение true, поэтому этот фрагмент кода закомментирован. Следует иметь в виду, что для снятия всех ограничений достаточно установить свойству EnforceConstraints значение false. Ограничение FK_QuestionsVariants создается перед отношением drQuestionsVariants - вначале следует создавать ограничения, а затем определять отношения. Соединение conn открывается как можно позже - непосредственно перед заполнением данными объектов DataAdapter, - и тут же закрывается. Открыть его в начале метода и закрыть в конце было бы нерациональным.
В программном обеспечении к курсу вы найдете приложение Programm Tests (Code\Glava4\ProgrammTests).
События объекта DataTable
Объект DataTable содержит ряд событий, которые могут применяться для слежения за происходящими изменениями. Наиболее часто используются следующие события:Скопируйте папку приложения RowVersion, назовите ее "Data TableEvents". В конструкторе формы добавим обработку четырех событий объекта DataTable:
public Form1() { ... dtTours.RowChanging += new DataRowChangeEventHandler(dtTours_RowChanging); dtTours.RowChanged += new DataRowChangeEventHandler(dtTours_RowChanged); dtTours.RowDeleting += new DataRowChangeEventHandler(dtTours_RowDeleting); dtTours.RowDeleted += new DataRowChangeEventHandler(dtTours_RowDeleted); }
В соответствующих методах просто выводим сообщение в текстовое поле:
private void dtTours_RowChanging(object sender, DataRowChangeEventArgs e) { rtbReport.Text += String.Format("Событие - изменение записи\n", e.Row["Название"]); }
private void dtTours_RowChanged(object sender, DataRowChangeEventArgs e) { rtbReport.Text += "\nСобытие - запись изменена\n"; }
private void dtTours_RowDeleting(object sender, DataRowChangeEventArgs e) { rtbReport.Text += String.Format("Событие - удаление записи\n", e.Row["Название"]); }
private void dtTours_RowDeleted(object sender, DataRowChangeEventArgs e) { rtbReport.Text += "\nСобытие - запись удалена\n"; }
Запускаем приложение. Нажимаем кнопку "Begin Edit", затем "End Edit" - происходят события RowChanging и RowChanged. Удаляем запись - происходят события RowDeleting и RowDeleted (рис. 8.21).

Рис. 8.21. Проект "DataTableEvents"
В обработчиках событий можно добавить соответствующие действия, например, подтверждение изменения (RowChanging) или удаления (RowDeleting).
В программном обеспечении к курсу вы найдете приложение DataTable Events (Code\Glava4\ DataTableEvents).
![]() |
![]() |
![]() |
1)
Дополнительные сведения об этом классе см. в конце Лекции 9.
2)
Не забудьте в классе формы создать объект DataSet: DataSet dsTests = new DataSet();

Создание столбцов, основанных на выражении
При создании базы данных не следует помещать в нее значения, которые могут быть получены из уже имеющихся данных. В первой лекции мы создали базу данных BDTur_firm.mdb, в которой есть таблица "Туры". В этой таблице имеется поле "цена", где указывается стоимость туров. На практике может понадобиться значение цены, выраженное в различных валютах, значение с учетом налогов, значение со скидкой и т.п. Для каждого конкретного случая можно получить дополнительное поле, не вводя его в саму базу данных. Технология ADO .NET позволяет создавать объекты DataColumn, основанные на выражении. Создайте новое Windows-приложение и назовите его CustomExpression. Перетаскиваем на форму элемент управления DataGrid, свойству Dock устанавливаем значение Fill. Переходим в код формы, подключаем пространство имен:using System.Data.OleDb;
В классе формы определяем строки CommandText и ConnectionString:
string commandText = "SELECT Информация, [Код тура], Название, Цена FROM Туры"; 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";
В конструкторе формы программно создаем все объекты для вывода таблицы "Туры" в элемент управления 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 dsTours = new DataSet(); DataTable dtTours = dsTours.Tables.Add("Туры"); DataColumn dсIDtour = dtTours.Columns.Add("Код тура", typeof(Int32)); dсIDtour.Unique = true; DataColumn dcName = dtTours.Columns.Add("Название"); DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal)); DataColumn dcInformation = dtTours.Columns.Add("Информация"); conn.Open(); dataAdapter.Fill(dsTours.Tables["Туры"]); conn.Close(); dataGrid1.DataSource = dsTours.Tables["Туры"].DefaultView; }
Запускаем приложение (рис. 8.11):

увеличить изображение
Рис. 8.11. Вывод содержимого таблицы "Туры"
Теперь добавим два объекта DataColumn, в которых будет вычисляться налог и скидка, после добавления объекта dcPrice:
... DataColumn dcPrice = dtTours.Columns.Add("Цена", typeof(Decimal)); DataColumn dcPriceNDS = dtTours.Columns.Add("Цена c НДС", typeof(Decimal)); dcPriceNDS.Expression = "Цена*0.15+Цена"; DataColumn dcPricewithDiscount = dtTours.Columns.Add("Цена cо скидкой", typeof(Decimal)); dcPricewithDiscount.Expression = "Цена-Цена*0.10"; ...
Свойство Expression созданного объекта DataColumn задает выражения для всех значений заданного поля (рис. 8.12):

увеличить изображение
Рис. 8.12. Значения полей "Цена с НДС" и "Цена со скидкой" получены в результате вычислений
Свойство Expression поддерживает также агрегатные функции, объединение строк, ссылки на родительские и дочерние таблицы.
В программном обеспечении к курсу вы найдете приложение Custom Expression (Code\Glava4\CustomExpression).
Свойство RowState
При работе с данными приходится постоянно вносить изменения в записи - добавлять, редактировать или удалять. Объект DataRow обладает свойством RowState, позволяющим отслеживать текущий статус строки. Создайте новое приложение, назовите его "RowState". Перетаскиваем на форму элементы RichTextBox, Splitter, DataGrid, устанавливаем для них следующие свойства:| Dock | Bottom |
| Text |
| Dock | Bottom |
| Dock | Fill |
В конструкторе формы мы создаем всего одно поле, затем одну запись, статус которой будем отслеживать:
public Form1() { InitializeComponent(); DataTable dtTours = new DataTable("Туры"); DataColumn IDtour = new DataColumn("Код тура", typeof(Int32)); dtTours.Columns.Add(IDtour); dataGrid1.DataSource = dtTours; DataRow myRow; // Создаем новую, отсоединенную запись myRow = dtTours.NewRow(); richTextBox1.Text += Convert.ToString("Новая запись: " + myRow.RowState); //Добавляем запись в объект DataTable dtTours.Rows.Add(myRow); richTextBox1.Text += Convert.ToString("\nДобавление записи: " + myRow.RowState); //Принимаем все изменения в объекте DataTable dtTours.AcceptChanges(); richTextBox1.Text += Convert.ToString("\nМетод AcceptChanges: " + myRow.RowState); //Редактируем запись myRow["Код тура"] = 1; richTextBox1.Text += Convert.ToString("\nРедактирование строки: " + myRow.RowState); //Удаляем строку myRow.Delete(); richTextBox1.Text += Convert.ToString("\nУдаление: " + myRow.RowState); //Отменяем все изменения в объекте DataTable dtTours.RejectChanges(); richTextBox1.Text += Convert.ToString("\nМетод RejectChanges: " + myRow.RowState); }
Запускаем приложение. В текстовое поле выводится статус записи myRow (рис. 8.18):

Рис. 8.18. Приложение RowState
Значение Detached означает, что запись не относится к объекту DataTable. После добавления ее статус изменяется на Added - теперь она существует в объекте DataTable, но ее нет в базе данных. Конечно, здесь мы не рассматривали взаимодействие с источником записей, но это же значение будет у записей, добавляемых в DataGrid после вывода данных из базы при наличии подключения. Вызывая метод AcceptChanges объекта DataTable, мы принимаем все изменения, поэтому статус DataRow изменяется на Unchanged - теперь запись считается "своей", она не была изменена после вызова метода. После вызова метода Delete запись помечается удаленной - она еще не полностью удалена, в случае отмены изменений статус будет восстановлен. Действительно, вызывая метод RejectChanges объекта DataTable, мы восстанавливаем запись до состояния Unchanged.
Свойство RowState всегда возвращает отдельное, доступное только для чтения значение. Это свойство используется для поиска записей, соответствующих заданному статусу, а также при передаче изменений в базу данных.
В программном обеспечении к курсу вы найдете приложение RowState (Code\Glava4\ RowState).
Свойство RowVersion
Свойство RowVersion предназначено для извлечения значения записи (объекта DataRow), зависящего от совершенных изменений. Возможны следующие версии записи:Создайте новое приложение, назовите его "RowVersion". Свойству Size формы устанавливаем значение 500; 300. Из окна Toolbox перетаскиваем следующие элементы и устанавливаем их свойства:
| Dock | Bottom |
| Dock | Left |
| CaptionText | Туры |
| Dock | Left |
| Name | rtbReport |
| Dock | Fill |
| Text |
| Name | BtnBeginEdit |
| Location | 14; 21 |
| Text | Begin Edit |
| Name | btnEndEdit |
| Location | 106; 21 |
| Text | End Edit |
| Name | btnCancelEdit |
| Location | 198; 21 |
| Text | Cancel Edit |
| Name | btnDelete |
| Location | 290; 21 |
| Text | Удалить |
| Name | btnClearReport |
| Location | 382; 21 |
| Size | 96; 23 |
| Text | Очистить отчет |
Переходим к коду. В классе формы объявляем объекты DataRow и DataTable:
DataRow myRow; DataTable dtTours;
В конструкторе формы создаем запись, определяем источник данных для элемента DataGrid, а также отключаем его доступность:
public Form1() { InitializeComponent(); dtTours = new DataTable("Туры"); DataColumn IDtour = new DataColumn("Название", typeof(string)); dtTours.Columns.Add(IDtour); myRow = dtTours.NewRow(); dtTours.Rows.Add(myRow); myRow["Название"] = "Таиланд"; dataGrid1.DataSource = dtTours; dataGrid1.Enabled = false; }
Создаем метод TestRowVersion, в котором будет проверяться свойство RowVersion записи:
private void TestRowVersion() { if(myRow.HasVersion(DataRowVersion.Original)) rtbReport.Text += String.Format("Значение original: {0}\n", myRow["Название", DataRowVersion.Original]); if(myRow.HasVersion(DataRowVersion.Current)) rtbReport.Text += String.Format("Значение current: {0}\n", myRow["Название", DataRowVersion.Current]); if(myRow.HasVersion(DataRowVersion.Default)) rtbReport.Text += String.Format("Значение default: {0}\n", myRow["Название", DataRowVersion.Default]); if(myRow.HasVersion(DataRowVersion.Proposed)) rtbReport.Text += String.Format("Значение proposed: {0}\n", myRow["Название", DataRowVersion.Proposed]); }
Метод HasVersion позволяет определить, поддерживает ли объект myRow версию данных, указываемую в скобках. В случае подтверждения будет выполняться код оператора - выводится в элемент rtbReport соответствующее сообщение.
В обработчике кнопки "Begin Edit" вызываем метод BeginEdit, устанавливаем новое значение записи:
private void btnBeginEdit_Click(object sender, System.EventArgs e) { myRow.BeginEdit(); myRow["Название"] = "Франция"; rtbReport.Text += "BeginEdit\n"; TestRowVersion(); }
В обработчике кнопки "End Edit" завершаем редактирование записи:
private void btnEndEdit_Click(object sender, System.EventArgs e) { myRow.EndEdit(); rtbReport.Text += "EndEdit\n"; TestRowVersion(); }
В обработчике кнопки "Cancel Edit" отказываемся от внесенных изменений:
private void btnCancelEdit_Click(object sender, System.EventArgs e) { myRow.CancelEdit(); rtbReport.Text += "CancelEdit\n"; TestRowVersion(); }
В обработчике кнопки "Удалить" удаляем объект myRow:
private void btnDelete_Click(object sender, System.EventArgs e) { myRow.Delete(); rtbReport.Text += "Запись удалена\n"; TestRowVersion(); }
В обработчике кнопки "Очистить отчет" просто удаляем содержимое текстового поля:
private void btnClearReport_Click(object sender, System.EventArgs e) { this.rtbReport.Text = ""; }
Запускаем приложение. После нажатия кнопки "BeginEdit" мы начинаем редактирование записи, вводится новое значение - "Франция". Оно становится значением по умолчанию "Default" и предполагаемым "Proposed", значение "Таиланд" является текущим "Current" (рис. 8.19, А). Отменяем редактирование, нажимая кнопку "Cancel Edit". При этом значение "Таиланд" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Б). Снова начинаем редактирование - картина повторяется (рис. 8.19, В). На этот раз завершаем его, нажимая кнопку "End Edit" - новое значение "Франция" становится текущим "Current" и по умолчанию " Default" (рис. 8.19, Г).
Нажимаем кнопку "Удалить" - при этом удаляется сам объект myRow и дальнейшее изменение его з начений оказывается невозможным (рис. 8.19, Д).

увеличить изображение
Рис. 8.19. Приложение "RowVersion"
В программном обеспечении к курсу вы найдете приложение RowVersion (Code\Glava4\ RowVersion).
Подобная функциональность вряд может нас устроить в реальных приложениях - в самом деле, мы вставляем только заранее определенное значение "Франция". Скопируйте папку приложения RowVersion и назовите ее RowVersion2. Добавим на форму элементы "надпись" и "текстовое поле":
| location | 24; 16 |
| Size | 72; 24 |
| Text | Название |
| Name | txtmyRowName |
| location | 104; 16 |
| Size | 344; 20 |
| Text |
private void btnBeginEdit_Click(object sender, System.EventArgs e) { myRow.BeginEdit(); rtbReport.Text += "BeginEdit\n"; TestRowVersion(); txtmyRowName.Enabled = true; }
private void btnEndEdit_Click(object sender, System.EventArgs e) { myRow["Название"] = txtmyRowName.Text; myRow.EndEdit(); rtbReport.Text += "EndEdit\n"; TestRowVersion(); txtmyRowName.Enabled = false; }
private void btnCancelEdit_Click(object sender, System.EventArgs e) { myRow.CancelEdit(); rtbReport.Text += "CancelEdit\n"; TestRowVersion(); txtmyRowName.Enabled = false; }
Обработчик кнопки "Удалить" изменим так же - теперь будет удаляться текущий экземпляр myRow и тут же создаваться новый:
private void btnDelete_Click(object sender, System.EventArgs e) { myRow.Delete(); rtbReport.Text += "Запись удалена\n"; TestRowVersion(); myRow = dtTours.NewRow(); dtTours.Rows.Add(myRow); }
В конструкторе формы отключим доступность текстового поля:
public Form1() { ... txtmyRowName.Enabled = false; }
Запускаем приложение. Выполняя знакомую последовательность действий, получаем похожий отчет; теперь при удалении записи можно продолжить работу - объект myRow будет воссоздан (рис. 8.20):

Рис. 8.20. Приложение RowVersion2
В программном обеспечении к курсу вы найдете приложение RowVersion2 (Code\Glava4\ RowVersion2).
Технология Microsoft ADO.NET
Класс ArrayList
Класс ArrayList, подобно классу Hashtable, определенный в пространстве имен System.Collections, представляет собой один из чрезвычайно простых и удобных способов работы с наборами элементов. Объекты этого класса не имеют фиксированного размера и при необходимости могут менять его. Объект ArrayList при своем создании резервирует место в памяти для 16 элементов - указателей на тип object. При добавлении семнадцатого элемента размерность ArrayList увеличивается до 32 элементов. Обращение к объектам осуществляется аналогично обращению к элементам массива. Создайте новое консольное приложение, назовите его "ClassArrayList". В таблице 9.2 приводится полный его листинг:| 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.
| Unchanged | Записи без изменений |
| New Новые | записи |
| Deleted | Удаленные записи |
| Current Modified | Измененные записи с их текущими значениями |
| Original Modified | Измененные записи с их первоначальными значениями |
Свойство 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
Появляется форма пользовательского (композитного) элемента управления. Устанавливаем следующие свойства:
| Name | FindCheckBox |
| Size | 230; 70 |
В окне Solution Explorer изменяем название "UserControl1.cs" на "FindCheckBox.cs". Перетаскиваем на форму из окна Toolbox текстовое поле, надпись и элемент CheckBox. Настраиваем их свойства:
| Name | chbForSearching |
| Location | 8; 32 |
| Text | |
| Name | txtColumnValue |
| Location | 32; 32 |
| Size | 184; 20 |
| Text |
| 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). Устанавливаем следующие свойства элементов:
| ColumnName | CustomerID |
| Name | fcbCustomerID |
| ColumnName | CompanyName |
| Name | fcbCompanyName |
| ColumnName | ContactName |
| Name | FcbContactName |
| ColumnName | Phone |
| Name | fcbPhone |
| 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. Дополнительно определяем следующие свойства элементов:| Name | dgInfoTourists |
| CaptionText | Информация о туристах |
| Name | dgTourists |
| CaptionText | Туристы |
(...). В редакторе Tables Collection Editor нажимаем кнопку "Add" для добавления таблицы "Туристы" (TableName - "Туристы", Name - "dtTourists"). В поле свойства Columns нажимаем на кнопку
(...) и создаем следующие столбцы:| ColumnName | Код туриста |
| DataType | System.Int32 |
| Unique | True |
| Name | dcTouristID |
| ColumnName | Фамилия |
| Name | dcLastName |
| ColumnName | Имя |
| Name | dcFirstName |
| ColumnName | Отчество |
| Name | dcMiddleName |
| TableName | Информация о туристах |
| Name | dtInfoTourists |
| ColumnName | Код туриста |
| DataType | System.Int32 |
| Unique | True |
| Name | dcInfoTouristsID |
| ColumnName | Серия паспорта |
| Name | dcPassport |
| ColumnName | Город |
| Name | dcCity |
| ColumnName | Страна |
| Name | dcCountry |
| ColumnName | Телефон |
| Name | dcPhone |
| 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).
Технология Microsoft ADO.NET
Атрибуты XML-документов
Атрибуты или свойства HTML-элементов представляют собой наиболее наглядное понятие - мы сталкивались с ними при определении фонового цвета странички "Пример HTML-документа.htm":при определении шрифта и его цвета:
Описание атрибута представляет собой пару "имя (BGCOLOR) - значение (LIGHTGREY)", атрибуты отвечают в основном за вид элементов на web-странице (см. рис. 10.2).
Атрибуты для XML-элементов предназначены для более широкого спектра задач, это своеобразные метки, по которым можно как форматировать затем элемент, применяя CSS или XSL, так и группировать. Они также используются для хранения различных свойств элемента, однако эти свойства не обязательно будут отображены. В спецификации XML не установлено строгих разграничений относительно типа информации, которую можно описывать с помощью атрибутов или внутри содержимого элемента.
Описание атрибута состоит из имени атрибута, вслед за которым идет знак равенства и значение атрибута. Можно выбирать любое имя атрибута, придерживаясь при этом следующих правил (таблица 10.11).
| Имя атрибута должно начинаться с буквы или символа подчеркивания (_), после чего могут следовать другие буквы, цифры, символы точки (.), тире (-) или подчеркивания | |||
| Каждое имя атрибута может только один раз присутствовать в одном и том же начальном теге или в теге пустого элемента | |||
| Значение атрибута должно быть заключено в одинарные (') или в двойные кавычки (") | |||
| Значение атрибута не может содержать внутри себя тот же символ кавычек, которыми оно ограничено | |||
| Значение атрибута не может содержать символ < (синтаксический анализатор может воспринять этот символ как начало описания XML-разметки) |
Среда Visual Studio .NET выделяет атрибуты красным шрифтом, при просмотре XML-документа их легко выделять. Мы уже сталкивались с атрибутами при создании самого первого XML-документа:
Здесь version и encoding - атрибуты, а "1.0" и "utf-8" - их значения соответственно.
Что такое HTML?
Язык разметки гипертекста HTML (Hyper Text Markup Language) представляет собой определенный набор элементов, которые используются для создания web-страницы. Примерами таких элементов являются заголовки, абзацы, списки, таблицы. Запускаем блокнот и вводим следующий текст:Несколько примеров
Жирный текст Подчеркнутый текст
Красный
Желтый
Зеленый
Сохраняем текст, назвав документ "Пример HTML-документа". Открываем "Мой компьютер \ Сервис \ Свойства папки \ вкладка "Вид"" и в окне "Дополнительные параметры" снимаем галочку "Скрывать расширения для зарегистрированных типов файлов". Теперь на компьютере все файлы будут в своем названии содержать и тип: например, рисунки - .bmp или .jpeg, программы - .exe, а файл блокнота - "Пример HTML-документа.txt". Меняем расширение на "Пример HTML-документа.htm" и запускаем. Появляется браузер, в котором открывается созданная страница (рис. 10.1):

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

Рис. 10.2. Просмотр HTML-кода страницы
Обратите внимание на форматирование HTML-кода - браузер игнорирует пробелы, отступы и абзацы; для их вывода на страницу используются специальные элементы.
Каждый элемент начинается с начального тега: текста, заключенного в угловые скобки (< >), который содержит имя элемента. В скобках указываются также дополнительные свойства (атрибуты) тега, например, серый фоновый цвет страницы определяет атрибут BGCOLOR со значением LIGHTGREY элемента BODY. Большинство элементов заканчиваются конечным тегом, в котором повторяется название элемента с символом косой черты(/), но другие могут оканчиваться иначе - например, тег
. Элемент "содержание" представляет собой текст, расположенный между начальным и конечным тегами. Сами теги могут быть написаны прописными () или строчными буквами() - в любом случае браузер отобразит страницу верно. Для формирования страницы были использованы следующие эл ементы (Таблица 10.1).
| Ключевой элемент, текст внутри него и представляет код страницы | |
| "Голова", шапка страницы | |
| Заголовок, отображаемый браузером | |
| "Тело" страницы, ее содержимое | |
| BGCOLOR | Атрибут, задающий цвет заднего фона |
| Новая строка | |
| Жирный текст | |
| Подчеркнутый текст | |
| Перечеркнутый текст | |
| Абзац | |
| Оформление шрифта | |
| COLOR | Цвет |
| SIZE | Размер |
| FACE | Вид шрифта |
| Бегущая строка |
Созданная 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.
| 1.htm | Первый заголовокВторой заголовокТретий заголовок | ![]() |
| 2.htm | Четвертый заголовокПятый заголовокШестой заголовок | ![]() |
| 3.htm | Первый заголовокВторой заголовокТретий заголовокЧетвертый заголовокПятый заголовокШестой заголовок | ![]() |
| 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} |
Итак, в самих документах нет описания того, как должны выглядеть страницы.
Вместо этого во всех трех страницах есть инструкция, указывающая на файл MyStyle.css:
Первые два параметра этого тега являются зарезервированными именами, требующимися для того, чтобы сообщить браузеру, что на этой страничке будет использоваться CSS. Третий параметр - HREF= "URL" - указывает на файл, который содержит описания стилей. Этот параметр должен содержать либо относительный путь к файлу - в случае если он находится на том же сервере, что и документ, из которого к нему обращаются, - либо полный URL ("http://...") в случае если файл стилей находится на другом сервере.
Файл MyStyle.css не выводится бразуером - в нем хранится лишь описание стилей. В рассматриваемом примере элементу BODY, отвечающему за содержимое страницы, устанавливается значение фонового цвета Dodgerblue:
BODY {background: Dodgerblue}
Вначале указывается название элемента (BODY), затем атрибут (background) со значением после двоеточия (Dodgerblue).
Аналогичная конструкция, задающая значение фонового цвета в виде атрибута, будет иметь следующий вид:
При описании нескольких свойств они разделяются точкой с запятой:
H1{font-family:Arial; color:Red; font-size:medium}
Простое задание атрибутов элемента 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):
| 1.htm |
Первый заголовокВторой заголовокТретий заголовок |
| 2.htm |
Четвертый заголовокПятый заголовокШестой заголовок |
| 3.htm |
Первый заголовокВторой заголовокТретий заголовокЧетвертый заголовокПятый заголовокШестой заголовок |
При запуске этих страниц их внешний вид не изменится, поскольку мы оставили стили страниц прежними. Описание располагается внутри тега STYLE:
.
Сама конструкция окружена символами комментария:
Несмотря на то, что подавляющее большинство браузеров поддерживает CSS, всегда следует учитывать обратную возможность - в этом случае знаки комментария позволят проигнорировать описание и избежать ошибки.
В программном обеспечении к курсу вы найдете папку "Внедрение" с рассмотренными документами (Code\Glava5\CSS\Внедрение).
Встраивание в теги документа
Третий вариант, когда описание стиля располагается непосредственно внутри тега описываемого элемента при помощи атрибута STYLE . Этот метод нежелателен, он приводит к потере одного из основных преимуществ CSS - возможности отделения информации от описания оформления информации. Впрочем, если необходимо описать лишь один элемент, этот вариант расположения описания стилей также вполне применим. Создадим новую папку и назовем ее "Встраивание". Скопируем в нее файлы 1.htm и MyStyle.css из папки "Связывание". Теперь у нас есть HTML-документ со связанной таблицей стилей. Сделаем копию документа 1.htm и назовем ее Встраивание1.htm. Изменим HTML-код этого документа:
Первый заголовок
Второй заголовок
Третий заголовок
Таблица стилей по-прежнему работает для этого документа, но описание тега h1 имеет больший приоритет (рис. 10.10):

Рис. 10.10. Встраивание стиля в тег документа (сравните с табл. 10.3)
В программном обеспечении к курсу вы найдете папку "Встраивание" с рассмотренными документами (Code\Glava5\CSS\Внедрение).
Импортирование
Последний способ применения CSS - импорт внешней таблицы стилей (расположенной на сервере) с помощью свойства @import:
Первый заголовок
Второй заголовок
Третий заголовок
Понятие корректно сформированных (well-formed) XML-документов
Документ называется корректно сформированным, если он соответствует минимальному набору правил для XML-документов:Это базовые критерии корректного формирования. Для других понятий языка XML (атрибутов, примитивов, связей) действуют свои правила, которые необходимо соблюдать. Можно сказать, что если документ создан правильно, верно и при его отображении и использовании не возникает никаких ошибок, то это и есть корректно сформированный документ. Если вы ошибетесь в каком-либо теге HTML-страницы, браузер просто проигнорирует соответствующий тег, а ошибка в теге XML сделает невозможным отображение страницы. В этом смысле написание XML похоже на программирование на C# - компилятор не запустит программу при наличии синтаксических ошибок. При наличии одной из ошибок встроенный в Internet Explorer анализатор (его иногда называют XML-процессор или парсер) определяет ее позицию (таблица 10.2).
| 1 | В документе находится два корневых элемента | ||
| Результат в Internet Explorer | |||
![]() | |||
| 2 | Тег INFORMATION начинается внутри тега TOUR, а заканчивается снаружи Правильная структура: Неправильная: | ||
| Результат в Internet Explorer | |||
![]() | |||
| 3 | Нет конечного тега IDTOUR | ||
| Результат в Internet Explorer | |||
![]() | |||
| 4 | Регистр начального и конечного тега не совпадают | ||
| Результат в Internet Explorer | |||
![]() | |||
| 5 | Название элемента 1PRICE начинается с цифры | ||
| Результат в Internet Explorer | |||
![]() | |||
В программном обеспечении к курсу вы найдете папку "Ошибки_XML", в которой находятся рассмотренные документы XML с ошибками (Code\Glava5\Ошибки_XML).
Применение атрибутов в XSL-схемах для фильтрации выводимого содержимого
Мы уже отмечали преимущества использования XSL-таблиц перед таблицами каскадных стилей. Содержимое XML может фильтроваться или сортироваться при выводе с помощью XSL-таблиц. Пример подобной фильтрации реализован в таблице 10.12.Таблица "Туры"Код тура: Название: Цена: Информация: | ||
| Вид в браузере Internet Explorer | ||
![]() | ||
Исходные документы XMLTour2.xml и XSLTour2. xsl были взяты из таблицы 10.8. Для туров с кодами 1 и 5 в тегах TOUR добавлены атрибуты Open:
...
Таблица XSL трактует атрибут, принадлежащий элементу в XML-документе, как дочерний элемент. Для ссылки на атрибут в образце XSL необходимо предварить имя атрибута символом @, - это указывает, что имя относится к атрибуту, а не к элементу:
В результате этого отбора были выведены только те элементы XML-документа, которые содержали описываемый атрибут.
В программном обеспечении к курсу вы найдете файлы XMLTour4.xml и XSLTour4.xsl в папке AttXSL (Code\Glava5\XSL\ AttXSL).
![]() |
![]() |
![]() |
WYSIWYG - "What You See Is What You Get" - "Что видишь, то и получаешь".
2)
Среда Microsoft Visual Studio.NET не содержит встроенных средств для проверки документа на действительность. Для этого можно использовать специализированные xml-редакторы, например,

Применение 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 и указываем связь с таблицей стилей:
Просматриваем документ в браузере - все дочерние элементы приняли свойства корневого (рис. 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 изменим связь:
... ...
Теперь каждый элемент имеет свое описание (рис. 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):
Использование критерия действительности необходимо для включения XML-документа в группу схожих документов, отвечающих определенной структуре или набору стандартов. Достаточно один раз выработать схему DTD и затем просто подключать ее к создаваемым документам. Стандарт XML определяет DTD как "грамматику для определенного класса документов".
Объявление типа документа представляет собой блок XML-разметки, который располагается в любом месте пролога корректно сформированного документа. Оно имеет следующую обобщенную форму записи:
DTD представляет собой определение типа документа. Оно состоит из символа левой квадратной скобки ([), после которой следует ряд объявлений разметки, заканчивающихся правой квадратной скобкой (]). Объявления разметки описывают логическую структуру документа, т.е. задают элементы документа, атрибуты и другие компоненты. Действительный XML-документ, содержащий DTD с единственным объявлением разметки, которое определяет один тип элемента в документе, TABLE, выглядит так (рис. 10.15).

Рис. 10.15. Простейший документ с DTD
В этом примере DTD документа указывает, что он может содержать только элементы типа TABLE (это единственный заданный тип элемента) и что элемент TABLE может иметь любое допустимое для данного типа содержимое (ключевое слово ANY).
DTD может содержать различные типы объявлений разметки: объявления типов документов, объявления типов атрибутов, инструкции по обработке и другие.
Объявление типа элемента имеет следующую обобщенную форму:
Некоторые возможные описания содержимого приведены в таблице 10.5.
| 1 | Элемент TABLE может содержать только символьные данные, дочерние элементы не допускаются | |
| ] > | ] > | |
| 2 | Элемент TABLE может содержать любые данные. Объявлять тип элемента в документе можно только один раз. | |
| ] > | ] > [] > | |
| 3 | Элемент TABLE может содержать один или несколько элементов TOUR | |
| ] > | ||
| 4 | Элемент TABLE должен быть пустым, т.е. не иметь содержимого | |
| ] > | ] > |
При создании документа XMLTour. xml мы определили элемент TOUR, который содержит дочерние элементы в следующей последовательности: IDTOUR, NAME, PRICE и INFORMATION. Далее, при заполнении списка туров для нас важно осуществлять проверку расположения элементов - дочерние элементы должны располагаться только так. В таблице 10.6 рассмотрены некоторые типичные примеры определения расположения дочерних элементов.
| 1 | Последовательная форма модели содержимого указывает, что элемент должен иметь заданную последовательность дочерних элементов. Имена типов дочерних элементов отделяются запятыми. Пропуск дочернего элемента или использование одного и того же типа дочернего элемента более одного раза также недопустимо | |
] > | ] > | |
| 2 | Выборочная форма модели содержимого указывает, что элемент может иметь один любой из серии допустимых дочерних элементов, разделяемых символом "|" | |
] > Правильные варианты: | ] > Неправильные варианты: |
Рассмотренные модели содержимого можно дополнять следующими знаками:
+ - один или несколько элементов;
* - ни одного или несколько элементов;
? - ни одного или один элемент.
Следующее объявление означает, что документ может содержать один или несколько элементов NAME и что элемент INFORMATION может не существовать или быть в единственном экземпляре:
... ...
Другое объявление означает, что документ может содержать ни одного или несколько элементов IDTOUR, либо элемент NAME, либо элемент PRICE, либо ни одного или один элемент INFORMATION:
... ...
Соответствующие правильные три варианта выглядят следующим образом:
Указанные модели и символы поддерживают сложные образования, образуемые вложением последовательной модели содержимого в выборочную, и наоборот. Примером такого сложного образования может служить следующее объявление:
] >
Для него будут верными следующие варианты корневого элемента:
Создание 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, указывающее на то, что это XML-документ, и содержащее номер версии. Объявление XML не является обязательным, хотя спецификация требует его включения. Если вы включаете XML-объявление, оно должно находиться в начале документа. Далее указывается кодировка документа - utf-8. Аналогично, если вы создадите HTML-страницу, выбрав соответствующий шаблон (см. рис. 10.3), в шаблоне будет указываться дополнительная информация, такая как версия языка HTML 4.0, язык EN, пакет верстки - Microsoft Visual Studio .NET 7.1 и другие:
Возвращаемся к нашему XML-документу и вводим комментарий - он начинается с символа "!--" и заканчивается символом "--":
Определяем так называемый элемент "Документ", или корневой элемент. Поскольку у нас таблиц несколько, логично будет назвать его TABLE:
При завершении написания начального тега среда автоматически вставляет конечный тег. Внутри корневого элемента определяем вложенные элементы:
Все! По сути, мы создали таблицу - переключаемся на вкладку
(Data) и вносим данные (рис. 10.4):
увеличить изображение
Рис. 10.4. Готовая таблица файла XMLTour.xml
Переключаемся в режим XML, для чего нажимаем соответствующую кнопку
и видим сформированный документ:Среда 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. Его начальный тег -

Рис. 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, соответствующая таблица стилей, содержащая один шаблон, и вид страницы в браузере.
Таблица "Туры"Код тура: Название: Цена: Информация: | ||
| Вид в браузере Internet Explorer | ||
![]() | ||
Для связывания XML-документа с таблицей XSL в прологе указывается ее адрес:
Наиболее часто используется относительный URL - обычно таблица стилей располагается в одной папке с документами. При необходимости можно также указать полный адрес.
Таблица стилей представляет собой обычный текстовый файл с расширением *.xsl. После создания файла для подсветки синтаксиса его можно открыть с помощью Visual Studio .NET (среда содержит шаблон XSLT-документа, шаблонов XSL в ней нет). Первая строка XSL таблицы указывает на то, что это XML-документ:
И действительно, открывая файл файл 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-таблицы стилей должен содержать один или несколько шаблонов элементов. Корневой элемент из рассмотренного примера содержит только один шаблон, имеющий следующую структуру:
Браузер использует шаблон для отображения определенной ветви элементов в иерархии XML-документа, с которым связана таблица стилей. Атрибут match шаблона указывает на определенную ветвь. (Атрибут match представляет собой аналог селектора в правиле CSS).
Значение атрибута match носит название образца (pattern). Образец в данном примере ("/") представляет корневой элемент всего XML-документа. Этот шаблон, таким образом, включает в себя инструкции для отображения всего XML-документа. Шаблон содержит два вида элементов - HTML и XSL. К HTML-элементам относится заголовок
Таблица "Туры"
и описание названия записи Код тура: . Это обычные HTML-элементы, единственное, что нужно делать при работе с ними - добавлять закрывающийся тег для всех элементов. Элементы XSL и отвечают за вывод содержимого XML-документа на web-страницу, - например, здесь выводится содержимое дочернего элемента IDTOUR, который принадлежит родительскому элементу TOUR:Порядок выводимых элементов определяется именно расположением XSL-элементов, например, если поменять строки, описывающие IDTOUR и NAME, то соответствующим образом изменится web-страница (рис. 10.17 ):
... Название:
Код тура:
Цена:
Информация:
...

Рис. 10.17. Теперь сначала выводится название тура, а затем код
В программном обеспечении к курсу вы найдете файлы XMLTour.xml и XSLTour.xsl в папке SimpleXSL (Code\Glava5\XSL\SimpleXSL)
Рассмотрим теперь вывод переменного числа дочерних элементов, входящих в корневой элемент XML-документа. В таблице 10.8 приводится описание этой структуры.
Таблица "Туры"Код тура: Название: Цена: Информация: | |
| Вид в браузере Internet Explorer | |
![]() | |
Для цикличного вывода содержимого дочернего элемента TOUR, принадлежащего элементу TABLE, используется элемент for-each:
Далее, внутри for-each задается описание текущего элемента:
Код тура:
...
В результате выводятся данные из всех элементов TOUR, найденных в документе, независимо от того, сколько этих элементов содержит документ.
В программном обеспечении к курсу вы найдете файлы XMLTour2.xml и XSLTour2.xsl в папке SomeElement (Code\Glava5\XSL\SomeElement).
Другой способ отображения повторяющихся XML-элементов состоит в создании отдельного шаблона для каждого элемента с последующим вызовом этого шаблона. Пример использования подобной методики приведен в таблице 10.9.
Таблица "Туры"Код тура: Название: Цена: Информация: |
Таблица стилей XSLTour3.xsl включает в себя два шаблона. Первый шаблон содержит инструкции для отображения коревого элемента - на это указывает атрибут match="/". Этот шаблон мы уже встречали ранее. Второй шаблон содержит инструкции для отображения элемента TOUR (атрибут match="TOUR"). Сначала браузер обрабатывает шаблон, соответствующий корневой части элемента:
Таблица "Туры"
Элемент apply-templates сообщает браузеру, что для каждого элемента TOUR внутри корневого элемента TABLE он должен обрабатывать шаблон, отвечающий элементу TOUR, - т. е. шаблон, для атрибута match которого установлено значение "TOUR". Далее описывается таблица стилей для этого элемента:
Код тура:
Название:
Цена:
Информация:
Доступ к дочерним элементам TOUR осуществляется посредством образца, содержащего только имя элемента:
В программном обеспечении к курсу вы найдете файлы 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):
| |
Технология Microsoft ADO.NET
Получение информации о структуре объекта DataSet
После загрузки схемы в объект DataSet можно получить информацию о его структуре - таблицах, столбцах, отношениях между таблицами. Для этого применяются свойства, приведенные в таблице 11.7.| 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
Запускаем приложение. В текстовое поле выводится информация о таблицах, полях, отношениях (рис. 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-схемы - в этом можно убедиться, открыв его:
При выборе объекта 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).
Ничего сложного в создании этих документов нет - их структура в точности такая же, как и структура документа XMLTour.xml. Названия полей были переведены по смыслу на английский язык и стали названиями тегов. Теперь нам нужно объединить все четыре документа в один документ XMLTourFull.xml следующим образом:
Готовый документ будет иметь следующий вид:
Переключаемся на вкладку
(Data) и видим, что представление XML-документа сильно изменилось (сравните с рис. 10.4). Теперь таблицы связаны (рис. 11.6).
увеличить изображение
Рис. 11.6. А - переход к записи таблицы "Сезоны", Б - переход к записи таблицы "Путевки", В - переход к записи таблицы "Оплата", Г - результирующий вид
Создадим схему документа XMLTourFull.xml, выбирая в главном меню пункт "XML\Create Schema". Графическое представление схемы отражает связь между соответствующими таблицами XML-документа (таблица 11.6).
![]() |
XML-схемы данных (XSD)
Схемы данных1) XSD (XML Schema Document, XSD) представляют собой альтернативный способ правил построения XML-документов. По сравнению с DTD, схемы обладают более мощными средствами для определения сложных структур данных, обеспечивают более понятный способ описания грамматики языка, способны легко модернизироваться и расширяться. Схема XSD может содержать следующую информацию:Для создания схемы данных в Visual Studio .NET в главном меню переходим File \ New \ File и выбираем из списка шаблонов (см. рис. 11.3) XML Schema. Появляется окно с надписью "Чтобы начать, перетащите объекты из окна Server Explorer или Toolbox на рабочую область (область дизайна) или щелкните правой кнопкой" (рис. 11.1):

Рис. 11.1. Создание новой XSD-схемы
Дело в том, что мы находимся в режиме дизайна. Переключаемся в режим кода, для чего нажимаем на кнопку
(XML):Первая строка - это уже знакомое указание на то, что схема представляет собой XML-документ с корневым элементом xs:schema. Префикс xs: предваряет все элементы схемы, указывая на свое пространство имен. Во второй строке располагается длинное, подробное и зачастую совершенно ненужное описание схемы (сравните с аналогичным кодом для HTML-страниц). Для корректной работы вполне достаточно ограничиться следующим представлением:
Впрочем, встроенные средства визуализации студии предполагают наличие этой "шапки", поэтому ее не следует удалять. Создание схемы, описывающей заданный XML-документ в среде Visual Studio .NET - довольно простая задача. Создадим следующий документ XMLEasy.xml:
Переключаемся на вкладку
(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-код, описывающий структуру документа:
Сюда входит также описание, необходимое для дальнейшего манипулирования схемой при помощи объектов ADO .NET. В исходном документе XMLEasy.xml появилась ссылка на схему данных:
Документ XMLEasy.xsd был автоматически создан в той же самой директории, где находится XMLEasy.xml.
Для того чтобы научиться понимать схемы XSD, вначале следует поработать с описанием данных в чистом виде, без дополнительных элементов. В таблице 11.1 приводится несколько простейших XML-документов и их схем, сформированных без привязки к объектам ADO .NET.
| | |
В документе XMLEasy.xml элемент TOUR - корневой элемент, содержащий дочерний элемент IDTOUR. Общая схема для корневого элемента имеет следующий вид: Дочерние элементы описываются так: | |
| | |
Корневой элемент TABLE содержит элемент TOUR, состоящий, в свою очередь, из группы дочерних элементов. Элемент choice определяет выбор других элементов, причем значение "unbounded" (неограниченно) атрибута maxOccurs указывает на возможность неограниченного наличия групп TOUR. | |
| | ![]() Рис. 11.5. Типы данных схемы XSD |
Для элемента IDTOUR был установлен тип данных int, для элемента CLOSED - тип boolean, для остальных - по умолчанию тип string. Изменять тип данных можно непосредственно в режиме XML-схемы данных, но более удобно - в режиме Schema (в данном случае режим будет называться DataSet) выбирать тип данных из выпадающего списка (рис. 11.5): | |
В программном обеспечении к курсу вы найдете все файлы этой таблицы в папке XSD (Code\Glava5\ XSD).
Задание типа данных в XML-документе (последний пример - табл. 11.1) - один из способов ограничения содержимого. Для ограничения значения заданного типа применяются дополнительные атрибуты. В следующем фрагменте схемы значение элемента PRICE должно быть в пределах от 50 до 100:
Для ограничения XML-документа некоторыми фиксированными значениями используется следующая конструкция:
Здесь элемент NAME может принимать только одно фиксированное значение из пяти названий стран.
Разработка XSD-схемы представляет собой довольно кропотливую работу. Визуальные средства среды Visual Studio .NET значительно облегчают эту задачу. Для освоения основных концепций желательно изучить несколько схем XML-документов, созданных автоматически. В таблицах 11.2-11.4 приводится описание основных элементов и атрибутов, которые при этом можно встретить.
| all | Вложенные элементы могут определяться в произвольном порядке |
| annotation | Родительский элемент элементов-комментариев |
| any | Любые вложенные элементы |
| anyAttribute | Любые атрибуты |
| appInfo | Элемент-коментарий. Задает титул схемы |
| attribute | Атрибут |
| attributeGroup | Группа атрибутов |
| choice | Выбор других элементов. Аналог оператора "|" в DTD |
| complexContent | Ограничения или расширения модели содержимого сложного типа |
| complexType | Элемент сложного типа |
| documentation | Элемент-комментарий. Предоставляет информацию о схеме |
| element | Элемент |
| extension | Расширения элемента |
| field | Объявление поля. Применяется внутри элемента |
| 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-схем
| 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
Рассмотрим теперь загрузку 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).
Загрузка XML-документов и XSD-схем в типизированный объект DataSet
Мы привыкли получать данные для объекта DataSet из базы Microsoft Access или SQL. Между тем можно загружать данные непосредственно из XML-файла с встроенной схемой или без нее. Для этого применяется метод ReadXml, который является перегруженным и может использоваться для чтения из XML-файла, из объекта подкласса TextReader, из объекта потока или из подкласса XmlReader (рис. 11.25):
увеличить изображение
Рис. 11.25. Перегруженный метод ReadXml
Метод ReadXml вторым параметром принимает XmlReadMode, применяемый для задания того, что содержит XML-файл и какая информация должна быть из него загружена. Этот параметр не обязателен, и если он не указан, используется значение по умолчанию - Auto. В таблице 11.8 приводятся другие значения параметра XmlReadMode.
| ReadSchema | Загружает и данные, и схему. Если в DataSet уже есть схема, таблицы из встроенной схемы добавляются. В случае, когда хотя бы одна из добавляемых таблиц уже есть в DataSet, генерируется исключение. При загрузке в объект DataSet, не содержащий схемы, XML-файла также без схемы - данные не будут прочитаны |
| IgnoreSchema | Игнорирует встроенную схему и загружает данные в уже имеющуюся схему DataSet. Не подходящие к этой схеме данные отбрасываются. Если в DataSet нет схемы, то данные не будут прочитаны |
| InferSchema | Игнорирует встроенную схему и выводит схему из структуры данных XML в файле. Если в DataSet уже есть схема, она расширяется путем добавления новых таблиц или путем добавления столбцов в уже существующие таблицы |
| DiffGram | Читает объект DiffGram и добавляет данные в уже существующую схему. (DiffGram-документ содержит всю информацию о данных объекта DataSet, включая первоначальное и текущее значение строк. Эти сведения можно использовать для проведения слияния объектов DataSet или обновления базы данных) |
| Fragment | Читает фрагменты XML до конца потока. Данные, соответствующие схеме DataSet, добавляются в конец нужной таблицы; несоответствующие - отбрасываются. |
| Auto | Используется по умолчанию. Исследует данные XML и выбирает наиболее подходящий режим: |
Для загрузки XSD-схемы в объект DataSet применяется метод ReadXmlSchema. Этот метод является перегруженным, так что можно указать источник информации в одном из следующих видов: имя файла, объект подкласса TextReader, поток или объект подкласса XmlReader (рис. 11.26):

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

Рис. 11.27. Пункты главного меню приложения TypedDataSetReadXML
| MnuFile | &Файл |
| MnuFill | &Заполнить |
| MnuOpen | &Открыть |
| MnuAuto | &Auto |
| mnuDiffGram | &DiffGram |
| mnuFragment | &Fragment |
| mnuIgnoreSchema | I&gnore Schema |
| mnuInferSchema | &Infer Schema |
| mnuReadSchema | &Read Schema |
| mnuReadXmlSchema | Открыть &схему |
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).
Запись содержимого и структуры обычного объекта 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.
| IgnoreSchema | В XML-документ записываются только данные объекта DataSet. Если данные не загружены, документ не создается. Это значение по умолчанию |
| WriteSchema | В XML-файл записываются данные объекта DataSet с добавлением схемы в качестве встроенного XSD-документа |
| DiffGram | В XML-документ сохраняются первоначальные и текущие значения данных |
Для записи схемы DataSet применяется метод WriteXmlSchema, который записывает данные в те же объекты, что и методы WriteXml или ReadXml (рис. 11.31):

Рис. 11.31. Перегруженный метод WriteXmlSchema
Скопируйте папку приложения TypedDataSetReadXML и назовите ее "TypedDataSetReadandWriteXML". Добавляем в главное меню следующие пункты (рис. 11.32):
| 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).
Технология Microsoft ADO.NET
Изменение записей
До этого момента мы рассматривали только извлечение записей для их просмотра. С помощью модели 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.| 1 | public DataAdapter () | Конструктор по умолчанию |
| 2 | public DataAdapter (string commandText, string connectionString) | Первый параметр представляет собой свойство CommandText объекта Command. В свою очередь, объект Command представляется свойством SelectCommand объекта DataAdapter. Второй параметр - строка подключения к базе данных ConnectionString |
| 3 | public DataAdapter (string commandText, Connection connection) | Первый параметр - свойство CommandText объекта Command, второй - объект класса Connection |
| 4 | public 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 приводится описание различных параметров этого метода:| 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. А - запуска редактора, Б - настройка элементов
| tpCustomers | Customers |
| tpEmployees | Employees |
| tpOrders | Orders |
Завершив работу, нажимаем "ОК" - на форме появляются три вкладки. Помещаем на каждую вкладку элемент управления 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 приводится описание свойств конструктора.
| parameterName | Название параметра. Например, "@Кодтуриста" или "Original_Кодтуриста" |
| dbType | Тип данных параметра |
| direction | Значение перечисления ParameterDirection, определяющее, является ли параметр входным, выходным, смешанным или возвращающим значение |
| isNullable | Определение, может ли параметр иметь значения null |
| precision | Точность числового параметра. Задается количеством знаков после запятой. Для нечисловых параметров это значение равно 0 |
| scale | Шкала - общее число знаков числового параметра |
| size | Размер параметра |
| sourceColumn | Источник данных - название поля (столбца) в объекте DataSet или DataTable |
| sourceVersion | Значение перечисления DataRowVersion, указывающее на версию передаваемых записей |
| value | Значение параметра |
(...) в поле свойства 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 обладает аналогичной структурой и свойствами.
![]() |
![]() |
![]() |
Здесь и далее под названием "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, которое может принимать следующие значения:Создайте новое приложение и назовите его "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, Б):| С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).
Технология Microsoft ADO.NET
Метод 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". Устанавливаем следующие значения свойств новой формы:
| FormBorderStyle | FixedSingle |
| MaximizeBox | False |
| MinimizeBox | False |
| Size | 250; 220 |
| Text | Добавление туриста |
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 со следующими свойствами:
| Name | rbTours |
| Location | 26; 16 |
| Tag | Туры |
| Text | Туры |
| Name | rbSeasons |
| Location | 138; 16 |
| Tag | Сезоны |
| Text | Сезоны |
| Name | rbPasses |
| Location | 250; 16 |
| Tag | Путевки |
| Text | Путевки |
| Name | rbPayments |
| Location | 362; 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).
// // 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).
Технология Microsoft ADO.NET
Изменение стандартного отчета
Основная задача 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 изменяем цвет заголовка:После тега "body" вставляем рисунок и ссылку:
...Сохраняем страницу, запускаем 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" соответственно.На панели размещаем две кнопки, устанавливая им следующие свойства:
| Name | btnFill |
| Location | 26; 16 |
| Text | Заполнить |
| Name | btnUpdate |
| Location | 122; 16 |
| Size | 144; 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 { ///
Компилируем сервис, нажимая кнопку 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-сервиса.
Программирование: Языки - Технологии - Разработка
- Программирование
- Технологии программирования
- Разработка программ
- Работа с данными
- Методы программирования
- IDE интерфейс
- Графический интерфейс
- Программирование интерфейсов
- Отладка программ
- Тестирование программ
- Программирование на Delphi
- Программирование в ActionScript
- Assembler
- Basic
- Pascal
- Perl
- VBA
- VRML
- XML
- Ada
- Lisp
- Python
- UML
- Форт
- Языки программирования












































(XML)



