Порой кажется, что не успели еще разработчики приложений баз данных привыкнуть к новой технологии, как компания Microsoft предложила совершенно новую модель доступа к базам данных. В этой главе основное внимание уделяется модели ADO.NET, которая является наиболее новым воплощением модели ADO. Сначала обосновывается необходимость введения новой модели доступа к базам данных (с точки зрения авторов), а затем предлагается описание самой модели и ее архитектуры.
В этой главе преследуется цель изложить основные принципы функционирования технологии ADO.NET. Здесь обсуждаются базовые операции и описываются базовые объекты провайдера данных ADO.NET, в частности Connection, Command, Parameter и DataReader. Далее (в главах 5, "ADO.NET: объект DataSet", 6, "ADO.NET: объект DataAdapter", и 7, "ADO.NET: дополнительные компоненты") рассматриваются более сложные объекты, которые тесно связаны с основным объектом ADO.NET — DataSet.
Обзор технологии ADO.NET
Разработчики приложений для работы с базами данных на основе Visual Basic уже привыкли к тому, что Microsoft каждые несколько лет предлагает новую усовершенствованную модель доступа к данным. Кроме новой трехбуквенной аббревиатуры, технология ADO.NET также предлагает новую модель API-интерфейсов и объектов. В течение последних нескольких лет разработчики уже успели познакомиться с предшественниками ADO.NET — технологиями ODBC, DAO, RDO и ADO. При знакомстве с каждой новой технологией им требовалось тщательно изучить ее назначение и принципы работы. При этом они часто задавали себе один и тот же вопрос: имеет ли смысл переходить на новую технологию? В большинстве случаев ответ был положительным, если только новшества не оказывали никакого влияния на текущие и будущие требования проекта. Действительно, чаще всего переход на новые технологии был вполне обоснован, за исключением технологии RDO (Remote Data Objects) для проектов с процессором баз данных Jet (потому что технология ОАО по-прежнему является более удачной технологией работы с Jet).
Мотивация и философия
Итак, зачем же все-таки нужна новая объектная модель доступа к данным? Наиболее простой ответ на этот вопрос можно сформулировать по аналогии с рекламным лозунгом компании Toyota конца 1970-х годов: "Спрашивали? Так получите". Технология ADO.NET предоставляет множество функциональных возможностей, о которых разработчики мечтали с момента появления технологии ADO. Некоторые компоненты, например отсоединенные от источника данных наборы записей и поддержка XML, были добавлены в модель ADO уже после ее выпуска. Но, поскольку они являлись добавлениями к основной модели, которые отсутствовали в исходной архитектуре ADO, способ их применения был крайне неудобным.
Классическая модель ADO основана на модели COM и одном объекте recordset, применение которого может быть весьма разнообразным. В зависимости от параметров конфигурации, например типа курсора, его расположения и типа блокировки, объект recordset будет действовать по-разному и выполнять различные операции.
В технологии ADO.NET разные функции разделены и выполняются разными объектами, которые могут использоваться либо отдельно, либо вместе с другими объектами.
Это дает разработчику возможность специализированного применения объектов без дополнительного обременительного "багажа". В то же время совершенно разные объекты могут взаимодействовать друг с другом для выполнения более сложных функций.
Поддержка распределенных приложений и отсоединенной модели программирования
В технологии ADO.NET предусмотрена эффективная и гибкая поддержка приложений, распределенных между несколькими компьютерами (серверами баз данных, серверами приложений и клиентскими рабочими станциями). В частности, особая поддержка предусмотрена для отсоединенных (трехуровневых или n-уровневых) приложений с минимизацией нагрузки при работе с параллельными операциями доступа к данным и блокировкой ресурсов сервера базы данных. В результате повышаются возможности масштабирования приложений, т.е. поддержки большего количества параллельно работающих пользователей за счет постепенного увеличения количества клиентских компьютеров. Это преимущество имеет особенно большое значение при создании Web-приложений.
Расширенная поддержка XML
Хотя классическая модель ADO способна сохранять и считывать данные в формате XML, фактически используемый для этого формат имеет несколько необычную форму и не так прост в применении. Кроме того, поддержка XML в модели ADO была добавлена в ходе ее эволюции, а потому обладает некоторыми ограничениями, в то время как поддержка XML в технологии ADO.NET является ее ключевым элементом. Философия ADO.NET формулируется очень кратко и просто: "Данные — это данные". Независимо от источника поступления, они могут считываться и обрабатываться как реляционные или иерархические данные, исходя из поставленной задачи и используемых инструментов.
XML используется как формат передачи данных между уровнями и компьютерами. Это не только исключает проблему прохождения COM-объектов через брандмауэры, но и позволяет совместно использовать данные сразу несколькими приложениями, которые работают на платформах, отличных от Windows (так как практически любая платформа способна обрабатывать текстовые данные XML).
Интеграция с .NET Framework
ADO.NET – это не просто следующая версия ADO, она специально предназначена для использования на платформе.NET Framework. Код всех объектов ADO.NET полностью управляемый, т.е. работает под управлением платформы.NET Framework. Технология ADO.NET является составной частью платформы.NET Framework, что позволяет избежать проблем с контролем версий объектов, которые хорошо известны разработчикам, имеющим опыт работы с ADO.
Внешний вид объектов ADO.NET
Хотя ADO.NET является составной частью платформы .NET Framework, многие ее компоненты будут знакомы опытным разработчикам, поскольку внешне они во многом напоминают объекты ADO. Даже совершенно новые элементы ADO.NET реализованы так, чтобы максимально упростить процесс их изучения и применения на основе имеющихся знаний.
ADO.NET И ADO 2.X
При работе с моделью ADO.NET нужно учитывать перечисленные ниже отличия от классической модели ADO.
• В классической модели ADO предусмотрен присоединенный к источнику данных способ доступа и использование физической модели данных.
• В модели ADO.NET проводиться четкое разделение между присоединенным способом доступа к данным и отсоединенной моделью программирования.
• В модели ADO.NET нет свойств CursorType, CursorLocation или LockType, потому что в ADO.NET предусмотрены только статические курсоры, клиентские курсоры и оптимистическая блокировка.
• Вместо использования простого многоцелевого объекта recordset в ADO.NET разные функции распределены среди объектов меньшего размера — DataReader, DataSet и DataTable.
• В ADO.NET разрешается полноценное манипулирование данными в формате XML, а не только использование его в качестве формата ввода-вывода данных.
В ADO.NET предусмотрена поддержка строго типизированных наборов данных DataSet, а не использование для всех полей типа Variant. Это позволяет эффективнее обнаруживать ошибки времени выполнения и повышает производительность работы приложений.
Место ADO.NET в архитектуре .NET Framework
На рис. 4.1 показано место классов ADO.NET в архитектуре.NET Framework. В основе этой платформы лежит общеязыковая исполняющая среда (Common Language Runtime — CLR), которая образует единую среду выполнения для всех .NET-совместимых приложений, независимо от используемого языка программирования. Среда CLR включает общую систему типов, управление памятью и жизненным циклом объектов.
На следующем логическом уровне над средой CLR располагаются базовые системные классы. Именно эти классы отвечают за выполнение базовых функций, которые могут использоваться в .NET-приложениях. На рис. 4.1 показаны только некоторые классы библиотеки классов .NET Framework, которая, по сути, является новым набором API-интерфейсов Windows. В прошлом доступ к функциям операционной системы Windows осуществлялся только через API-интерфейсы, которые состояли из большого набора разрозненных и плохо продуманных функций. На платформе.NET Framework такой доступ организован на основе свойств и методов, которые предлагаются базовыми системными классами. Это объектно-ориентированный, последовательный и комфортабельный способ создания приложений Windows, независимо от типа клиентского приложения: традиционного настольного приложения, броузера или Web-службы.
РИС. 4.1. Классы ADO.NET в структуре платформы .NET Framework
Этот уровень включает несколько пространств имен (групп классов и других определений), предназначенных для организации доступа к данным: System.Data, System.OleDb и System.Data.SqlClient. В оставшейся части данной главы, а также в главах 5, 6 и 7 эти классы и пространства имен рассматриваются более подробно.
Прикладные интерфейсы
На этом наиболее высоком уровне происходит дифференциация, или разделение, выполняемых функций, которые разработчики могут использовать в разных типах приложений. Он содержит классы и элементы управления для создания (классических) приложений Windows на основе форм (Windows Forms), другие классы и элементы управления для создания Web-ориентированных приложений (Web Forms), а также классы для создания приложений на основе Web-служб. Однако все они используют для прикладной логики базовую библиотеку классов — системные базовые классы.
Теперь после первого знакомства с расположением классов ADO.NET в общей структуре платформы.NET Framework рассмотрим подробнее основные объекты ADO.NET.
Провайдеры данных ADO.NET
Несмотря на подчеркнутое значение отсоединенной модели программирования, для извлечения, обновления, вставки и удаления данных все же придется подключиться к физической базе данных. Программное обеспечение ADO.NET для подсоединения и взаимодействия с физической базой данных называется провайдером данных ADO.NET. Провайдер данных (data provider) — это управляемый код .NET, который эквивалентен провайдеру OLEDB или драйверу ODBC. Провайдер данных состоит из нескольких объектов, которые реализуют необходимую функциональность в соответствии с определениями своих классов и интерфейсов.
В настоящее время существует три разных провайдера данных ADO.NET, каждый из которых определен в своем собственном пространстве имен. Для всех объектов в этих пространствах имен используются следующие префиксы: OleDb, Sql и Odbc. Однако при упоминании этих объектов в рамках своего пространства имен имя объекта можно указывать без употребления префикса данного пространства имен.
Провайдер данных SqICIient
Оптимизирован для работы с SQL Server версии 7.0 (или выше) и позволяет добиться более высокой производительности по следующим причинам:
• взаимодействует с базой данных непосредственно через собственный протокол табличной передачи данных (Tabular Data Stream — TDS), а не через OLEDB с отображением интерфейса OLEDB на протокол TDS;
• исключает накладные расходы, связанные с использованием COM-служб взаимодействия;
• отсутствуют ненужные функции, которые не поддерживаются в SQL Server (объекты этого провайдера данных находятся в пространстве имен System.Data.SqlClient).
Провайдер данных Oledb
Основан на существующем COM-поставщике OLEDB и COM-службах взаимодействия платформы .NET Framework, предназначенных для доступа к базе данных. Этот провайдер данных используется для работы с SQL Server более ранних версий, чем 7.0. Он позволяет осуществлять доступ к любой базе данных, для которой имеется поставщик OLEDB. Объекты этого провайдера данных находятся в пространстве имен System.Data.Oledb.
Провайдер данных Odbc
Используется для доступа к базам данных, которые не имеют собственного провайдера данных .NET или COM-поставщика OLEDB. Иногда драйвер ODBC демонстрирует более высокую производительность, чем драйвер OLEDB, поэтому для сравнения их фактической производительности при работе с конкретной базой данных рекомендуется провести ряд тестов. Объекты этого провайдера данных находятся в пространстве имен System. Data.Odbc.
НА ЗАМЕТКУ
Создание провайдера данных для ODBC несколько задержалось и отстало от создания платформы .NET Framework и Visual Studio .NET. Поэтому он не был включен в исходный выпуск Visual Studio .NET и его можно скопировать с Web-узла компании Microsoft. Кроме того, в скором будущем следует учитывать возможное появление дополнительных .NET-совместимых провайдеров данных.
В настоящее время на Web-узле компании Microsoft также можно скопировать провайдер данных Oracle.NEТ. Эти провайдеры данных ОDBС и Oracle будут включены в следующую версию 1.1 платформы .NET Framework и Visual Studio .NET 2003. В результате этого пространство имен провайдера данных ODBC Microsoft.Data.Odbc станет называться System.Data.Odbc.
В примерах этой главы используется провайдер данных ODBC версии 1.0, поэтому при использовании провайдера данных ODBC версии 1.1 нужно изменить имя его пространства имен.
Основные объекты
Каждый провайдер данных имеет четыре основных объекта, которые указаны в табл. 4.1.
Таблица 4.1. Основные объекты провайдера данных
Объект | Краткое описание |
Connection | Устанавливает соединение с указанным источником данных |
Command | Выполняет команду по отношению к источнику данных. Содержит коллекцию объектов Parameters и методы для выполнения команд разного типа |
DataReader | Считывает данные и возвращает из источника поток данных, предназначенный только для чтения и только в одном направлении |
DataAdapter | Соединяет набор данных DataSet и источник данных для извлечения и сохранения данных |
Каждый объект основан на базовом родовом классе и реализует родовой интерфейс, но имеет собственную реализацию. Например, объекты SqlDataAdapter, OleDBDataAdapter и OdbcDataAdapter являются производными от класса DbDataAdapter и реализуют те же интерфейсы. Однако каждый из них реализует их своим собственным способом для соответствующего источника данных.
Пространство имен System. Data. OleDb содержит объекты:
• OleDbConnection;
• OleDbCommand;
• OleDbDataReader;
• OleDbDataAdapter.
Пространство имен System.Data.SqlClient содержит объекты:
• SqlConnection;
• SqlCommand;
• SqlDataReader;
• SqlDataAdapter.
Пространство имен Microsoft.Data.Odbc содержит объекты:
• OdbcConnection;
• OdbcCommand;
• OdbcDataReader;
• OdbcDataAdapter.
Аналогично, все будущие провайдеры данных будут находиться в отдельных пространствах имен и содержать такие же объекты, но с другими реализациями и префиксами.
Объект Connection
Этот объект модели ADO.NET очень похож на объект Connection в классической модели ADO. Его предназначение очевидно: он служит для установления соединения с заданным источником данных и с указанным в строке подключения учетным именем и паролем. Соединение можно настроить, редактируя нужным образом значения параметров строки подключения. Объект Command (или DataAdapter) может затем использовать это подключение для выполнения нужных операций с источником данных.
НА ЗАМЕТКУ
В отличие от объекта Connection в модели ADO в объекте Connection в модели ADO.NET нет методов Execute и ОpenSchema. Для выполнения команд SQL следует использовать объекты Command или DataAdapter. Функции метода OpenSchema реализуются с помощью методов GetOleSchemaTable объекта OleDbConnection.
Хотя объекты OleDbConnection, SqlConnection и OdbcConnection реализуют одинаковые интерфейсы, они все же имеют разные реализации. Например, они имеют разный формат строки подключения. В объекте OleDbConnection используется стандартный формат строки подключения OLEDB с незначительными исключениями. В объекте OdbcConnection также используется стандартный формат строки подключения ODBC, но с незначительными отклонениями. Наконец, в объекте SqlConnection используется совершенно другой формат строки подключения, который имеет отношение только к SQL Server версии 7.0 или выше.
Более того, некоторые объекты обладают дополнительными свойствами. Например, объект OleDbConnection имеет свойство Provider для указания используемого провайдера данных OLEDB, а объект OdbcConnection имеет свойство Driver для указания используемого драйвера ODBC. Объект SqlConnection вообще не имеет этих свойств, так как используется с предопределенным источником данных, т.е. с SQL Server. Однако он имеет свойства PacketSize и WorkstationID, которые используются только для работы с SQL Server и не нужны для других типов подключения.
Итак, приступим к созданию кода. Познакомимся последовательно со всеми типами источников данных, начав с самого простого примера, который по мере продвижения к концу главы будет постепенно усложняться.
1. Запустите интегрированную среду разработки приложений Visual Studio .NET.
2. Создайте новый проект Visual Basic Windows Application. Для этого в диалоговом окне New Project (Новый проект) выберите тип проекта Visual Basic Project в области Project Types (Типы проектов), а затем шаблон Windows Application (Приложение Windows) в области Templates (Шаблоны).
3. Назовите проект DataProviderObjects.
4. Укажите путь к файлам проекта.
5. Увеличьте размер формы Form1.
6. В окне Properties укажите значение Data Provider Objects для свойства Text формы Form1.
7. В верхнем левом углу формы создайте кнопку, перетаскивая ее из панели элементов управления.
8. В окне Properties укажите значение cmdConnection для свойства (Name) и значение Connection для свойства Text этой кнопки.
9. В верхнем правом углу формы создайте текстовое поле, перетаскивая его из панели элементов управления.
10. В окне Properties укажите значение txtResults для свойства (Name), значение True для свойства Multiline и значение Both для свойства ScrollBars этого текстового поля.
11. Увеличьте размер текстового поля, чтобы оно занимало до 80% всей площади формы.
По окончании этих операций форма будет выглядеть так, как на рис. 4.2.
РИС. 4.2. Форма Form1 проекта DataProviderObjects
Перейдите в окно редактирования кода формы и включите в верхнюю часть файла приведенные ниже строки, которые импортируют пространства имен, используемые здесь и в следующих примерах главы.
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.OleDb
Imports Microsoft.Data.Odbc
Обратите внимание, что эти пространства имен содержат классы и определения объектов ADO.NET для каждого провайдера данных.
НА ЗАМЕТКУ
Среда Visual Studio .NET может не обнаружить пространство имен Data.Odbc, потому что оно является расширением базовой версии продукта. В таком случае выполните ряд действий.
1. Скопируйте инсталлятор провайдера данных ODBC с Web-узла компании Microsoft и выполните все инструкции по инсталляции.
2. В окне SolutionExplorer щелкните правой кнопкой мыши на папке References проекта DataProviderObjects.
3. Выберите в контекстном меню команду Add Reference.
4. Во вкладке.NET диалогового окна Add Reference прокрутите список компонентов и найдите файл Microsoft.Data.Odbc.dll.
5. Щелкните дважды на файле Microsoft.Data.Odbc.dll для включения его в список избранных компонентов Selected Components в нижней части диалогового окна Add Reference.
6. Щёлкните на кнопке ОК для закрытия диалогового окна Add Reference.
Если по какой-либо причине не распознано какое-то другое импортированное пространство имен, потребуется привести ссылку на файл System.Data.dll. Для этого выполните действия, перечисленные в пп. 2-6, где вместо файла Microsoft.Data.Odbc.dll при выполнении п. 4 нужно использовать файл System.Data.dll.
Теперь для кнопки btnConnection нужно создать код, приведенный в листинге 4.1, для создания подключения к базе данных pubs сервера SQL Server. Этот код создает подключение и отображает состояние подключения до и после попытки подключения к базе данных.
Листинг 4.1. Код открытия подключения и отображения его состояния
Private Sub btnConnection Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnConnection.Click
' Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection()
' Создание строки подключения
cnn.ConnectionString = "server=localhost;uid=sa;database=pubs"
txtResults.Clear()
' Отображение состояния подключения
If (cnn.State = System.Data.ConnectionState.Open) Then
txtResults.Text = txtResults.Text & "Connection is Open"
Else
txtResults.Text = txtResults.Text & "Connection is Closed"
End If
txtResults.Text = txtResults.Text & ControlChars.CrLf
' Открытие подключения
txtResults.Text & "Opening DB connection…" _
& ControlChars.CrLf & ControlChars.CrLf
cnn.Open()
' Отображение состояния подключения
If (cnn.State = System.Data.ConnectionState.Open) Then
txtResults.Text = txtResults.Text & "Connection is Open"
Else
txtResults.Text = txtResults.Text & "Connection is Closed"
End If
txtResults.Text = txtResults.Text & ControlChars.CrLf
End Sub
СОВЕТ
Новым полезным компонентом Visual Basic .NET является возможность получения текстового представления для значений перечисления (enum) вместо создания специальной подпрограммы на основе операторов select-case для всех возможных значений перечисления. Все типы перечисления, которые являются объектами, наследуют метод ToString, возвращающий строку с текстовым представлением текущего значения. В листинге 4.1 используется приведенный ниже фрагмент кода для отображения состояния подключения на основе операторов if-else.
' Отображение состояния подключения – вариант 1
If (cnn.State = System.Data.ConnectionState.Open) Then
txtResults.Text = txtResults.Text& "Connection is Open"
Else
txtResults.Text = txtResults.Text& "Connection is Closed
End If
Его можно заменить другим вариантом кода, содержащим только одну строку.
' Отображение состояния подключения вариант 2
txtResults.Text & "Connection is " & cnn.State.ToString & ControlChars.CrLf
После запуска полученного приложения DataProviderObjects и щелчка на кнопке Connection в текстовом поле появятся строки о закрытии подключения, о состоянии подключения и повторном открытии подключения, как показано на рис. 4.3.
НА ЗАМЕТКУ
При создании рабочего кода нужно выбрать и реализовать стратегию обработки исключительных ситуаций для большинства подпрограмм и операций. Обычно основана на блоках try-catch обработки исключительных ситуаций. Этот код не включен в приведенные здесь примеры, потому что наша цель — сфокусировать внимание на концепциях программирования баз данных, а не на описании общих принципов программирования для Visual Basic .NET.
Объект Command
Аналогично объекту Connection, объект Command модели ADO.NET очень похож на своего предшественника из прежней модели ADO 2.X. Объект Command позволяет выполнять команды по отношению к источнику данных и получать возвращенные данные или результаты выполнения команд.
РИС. 4.3. Состояние приложения DataProviderObjects до и после открытия подключения с помощью кода из листинга 4.1
Этот объект имеет следующие свойства: CommandText и СommandType для определения текста и типа фактической команды; Connection для указания подключения, используемого для выполнения команды; СommandTimeout для указания времени ожидания, по истечении которого команда отменяется и выдается сообщение об ошибке; Parameters для коллекции параметров команды; Transaction для указания транзакции, в которой используется данная команда.
Все три версии объекта Command (в пространствах имен OleDb, Sql, Odbc) имеют идентичные свойства и методы, за исключением того, что объект SqlCommand имеет дополнительный метод, которого нет у двух других вариантов этого объекта, а именно ExecuteXmlReader. Он использует преимущества SQL Server для автоматического возвращения данных в формате XML (если в запрос SQL добавлено предложение FOR XML).
НА ЗАМЕТКУ
Еще одно отличие между версиями объекта Command для разных провайдеров данных заключается в использовании значений свойства CommandType. Все они поддерживают значения Text и StoredProcedure, а объекты OleDbCommand и SqlCommand поддерживают еще одно, третье возможное значение — TableDirect. Это позволяет эффективно загружать все содержимое таблицы за счет установки значения TableDirect для свойства CommandType и имени таблицы для свойства CommandText.
Продолжим работу с формой, показанной на рис. 4.3.
1. Добавим еще одну кнопку сразу под кнопкой btnConnection, перетаскивая пиктограмму кнопки из панели элементов управления.
2. В окне свойств Properties установите значение btnCommand для свойства Name и значение Command для свойства Text.
3. Добавьте для кнопки код, показанный в листинге 4.2.
Листинг 4.2. Код открытия подключения к базе данных и подготовки объекта Command
Private Sub btnCommand_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCommand.Click
' Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection( _
"server=localhost;uid=sa;database=pubs")
' Создание экземпляра объекта Command
Dim cmd As SqlCommand = New SqlCommand() txtResults.Clear()
' Открытие подключения cnn.Open()
' Указание подключения и текста команды
cmd.Connection = cnn
cmd.CommandType = CommandType.Text
cmd.CommandText = _
"Select au_lname, state from authors"
' Вывод текста команды
txtResults.Text = "Command String:" & ControlChars.CrLf
txtResults.Text = txtResults.Text & ControlChars.Tab & cmd.CommandText() & ControlChars.CrLf
End Sub
После запуска на выполнение приложения DataProviderObjects щелкните на кнопке Command, и в текстовом поле будет показана команда SQL, которая находится в свойстве CommandText объекта SqlCommand, а именно: SELECT au_lname, state FROM authors.
НА ЗАМЕТКУ
Многие классы платформы .NET Framework, а также созданные другими разработчиками классы перегружают конструкторы объектов. Иначе говоря, существует несколько способов создания нового экземпляра класса, где каждый конструктор принимает свойственный только ему набор аргументов. Таким образом можно выбрать версию, оптимальную для выполнения текущей задачи. Конструктор в листинге 4.2 для объекта SqlConnection отличается от го по умолчанию конструктора в листинге 4.1, в котором не используется никаких аргументов. Позже строке подключения присваивается объект SqlConnection с помощью свойства ConnectionString.
' Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection()
' Создание строки подключения
cnn.ConnectionString = "server=localhost;uid=sa;database=pubs"
В листинге 4.2 используется конструктор объекта SqlConnection, который принимает строку подключения в качестве параметра. Это позволяет создать объект и сразу же указать для него строку подключения в одной строке кода.
' Создание экземпляра объекта Connection
Dim cnn SqlConnection = New SqlConnection("server=localhost;uid-sa;database=pubs")
Применение объекта Command с параметрами и хранимыми процедурами
При создании запросов или команд для источника данных часто требуется передавать значения параметров действия (обновление, вставка или удаление данных) или хранимой процедуры. Для решения этой проблемы в объекте Command предусмотрено свойство Parameters, которое является объектом-коллекцией ParameterCollection и содержит коллекцию объектов-параметров Parameter. Это аналогично способу работы, применимому в модели ADO 2.X.
Объекты Parameter и ParameterCollection тесно связаны с соответствующим провайдером данных, поэтому они должны быть реализованы как составная часть провайдера данных ADO.NET. Способы программирования объекта SqlParameterCollection и использование объектов OdbcParameterCollection и OledbParameterCollection имеют существенные отличия. Объекты OdbcParameterCollection и OledbParameterCollection основаны на позиционных параметрах, а объект SqlParameterCollection – на именованных параметрах. Эти различия в значительной степени влияют на способ определения запросов и параметров.
Начнем с создания простого запроса с параметрами для извлечения из базы данных pubs имен и фамилий всех авторов из заданного штата.
С одной стороны, при использовании провайдеров данных OLEDB или ODBC запрос будет иметь следующий вид:
SELECT state, au_fname, au_lname from authors WHERE state = ?
Здесь вопросительный знак заменяет один параметр, а для нескольких параметров можно использовать несколько вопросительных знаков. Порядок расположения параметров в коллекции ParameterCollection должен точно соответствовать порядку, в котором располагаются их заменители в запросе или хранимой процедуре.
С другой стороны, при использовании провайдера данных SqlClient запрос будет выглядеть следующим образом:
SELECT state, au_fname, au_lname from authors WHERE state = @MyParam
Здесь заменителем параметра является его имя, а дополнительные параметры также обозначаются их именами, поэтому порядок расположения параметров в коллекции ParameterCollection не имеет значения.
Объект Parameter можно создать явно, используя конструктор Parameter (т.е. с использованием оператора New) или передавая нужные аргументы методу Add объекта-коллекции ParameterCollection (свойство Parameters объекта Command). Помните, что оба метода (конструктор Parameter и метод Add) имеют перегруженные версии.
Ниже приведен один из способов включения параметра команды за счет явного указания объекта-параметра.
Dim rayParameter As New OdbcParameter("@MyParam", OdbcType.Char, 2)
myParameter.Direction = ParameterDirection.Input
myParameter.Value = "CA"
cmd.Parameters.Add(myParameter)
А способ включения параметра команды с помощью метода Add выглядит иначе.
cmd.Parameters.Add("@MyParam", OdbcType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
Второй метод короче и обычно предпочтительнее, если только нет особой причины для повторного использования объекта Parameter.
Для метода Add объекта Parameter обычно требуется указать имя, тип и длину параметра. Затем нужно указать направление передачи данных: Input, Output, InputOutput или ReturnValue. По умолчанию используется направление Input. Наконец, для присвоения значения параметру нужно использовать свойство Value объекта Parameter. Кроме того, для параметра можно указать другие свойства, например масштаб (свойство Scale), точность (свойство Precision) и допустимость использования неопределенных значений (свойство IsNullable).
При использовании провайдера данных SqlClient можно применять практически идентичный код. Единственным отличием являются префиксы Odbc вместо префиксов Sql, а также тип перечисления SqlDbType вместо OdbcType.
Dim myParameter As New SqlParameter("@MyParam", SqlDbType.Char, 2)
myParameter.Direction = ParameterDirection.Input
myParameter.Value = "CA"
cmd.Parameters.Add(myParameter)
Аналогично выглядит способ включения параметра команды с помощью метода Add.
cmd.Parameters.Add("@MyParam", SqlDbType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
СОВЕТ
Для передачи параметру неопределенного значения можно использовать свойство Value объекта DBNull.
cmd.Parameters("@MyParam").Value = DBNull.Value
Измените код кнопки cmdButton, как показано в листинге 4.3. После запуска программы и щелчка на кнопке cmdButton в текстовом поле появится текст запроса, а также имя и значение параметра.
Листинг 4.3. Код подготовки и отображения команды и ее параметров
Private Sub btnCommand_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCommand.Click
' Создание экземпляра объекта Connection
Dim cnn As SqlConnection = New SqlConnection( _
"server=localhost;uid=sa;database=pubs")
' Создание экземпляра объекта Command и объектов Parameter
Dim cmd As SqlCommand = New SqlCommand()
Dim prm As SqlParameter = New SqlParameter()
txtResults.Clear()
' Открытие подключения cnn.Open()
' Указание подключения и текста команды
cmd.Connection = cnn
cmd.CommandType = CommandType.Text
cmd.CommandText = _
"Select au_lname, state from authors where state = @MyParam"
' Создание параметра и указание его значения
cmd.Parameters.Add(New SqlParameter("@MyParam", SqlDbType.Char, 2))
cmd.Parameters("@MyParam").Value = "CA"
' Вывод текста команды
txtResults.Text = "Command String:" & ControlChars.CrLf
txtResults.Text = txtResults.Text & ControlChars.Tab & _
cmd.CommandText() & ControlChars.CrLf
' Вывод параметров команды
txtResults.Text = txtResults.Text & "Command parameters:" & _
ControlChars.CrLf
For Each prm In cmd. Parameters
txtResults.Text = txtResults.Text & ControlChars.Tab & _
prm.ParameterName & " = " & prm.Value & ControlChars.CrLf
Next
End Sub
Аналогично вызываются хранимые процедуры, за исключением того, что вместо свойства CommandType.Text используется свойство CommandType.StoredProcedure, а имя хранимой процедуры присваивается свойству CommandText. Таким образом, код вызова хранимой процедуры GetAuthorsFromState с двухсимвольным параметром для извлечения информации обо всех авторах заданного штата будет выглядеть, как показано ниже.
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "GetAuthorsFromState"
cmd.Parameters.Add("@MyParam", SqlDbType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
СОВЕТ
Для вызова хранимой процедуры с помощью OdbcCommand нужно использовать стандартные последовательности символов ODBC (с участием фигурных скобок), а не только имя процедуры для свойства CommandText. В качестве заменителей параметров в ODBC используются вопросительные знаки. Вот как выглядит приведенный выше для провайдера данных ODBC:
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "{GetAuthorsFromState ?}"
cmd.Parameters.Add("@MyParam", OdbcType.Char, 2)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Input
cmd.Parameters("@MyParam").Value = "CA"
Хранимая процедура также возвращает значение, которое указывается с помощью специальной последовательности символов (вопросительного знака и знака равенства) у открывающей скобки, как показано ниже.
cmd.CommandText = "{? = GetAuthorsFromState ?}"
Чтобы хранимая процедура возвращала значение, нужно указать направление Output, а затем считать значение свойства параметра после вызова хранимой процедуры. В данном примере также определяется возвращаемое значение хранимой процедуры. Поскольку здесь указан тип Int сервера SQL Server, то для этого параметра не нужно указывать длину, так как по определению она составляет 4 байт.
cmd.Parameters.Add(New SqlParameter("result", SqlDbType.Int)
cmd. Parameters ("result").Direction = ParameterDirection.ReturnValue
cmd.Parameters.Add(New SqlParameter("@MyParam", SqlDbType.Int)
cmd.Parameters("@MyParam").Direction = ParameterDirection.Output
' Вызов хранимой процедуры
Msg (cmd.Parameters("@MyParam").Value)
НА ЗАМЕТКУ
При определении параметра, который будет использоваться для возвращаемого значения ReturnValue хранимой процедуры его нужно указать первым в списке параметров коллекции Parameters. Это обязательное условие для провайдеров данных OLEDB и ODBC, потому что, как отмечалось выше, параметры этих провайдеров определяются по порядку их расположения, а возвращаемое значение может располагаться только с начала. Однако при определении возвращаемого значения для провайдера данных SQL его можно pасполагать в произвольном месте коллекции параметров, потому что параметры провайдера данных SQL определяются по именам.
Далее представлены другие фрагменты кода с указанием параметров команд и хранимых процедур, а также описываются способы их выполнения.
Выполнение команд
До сих пор мы только указывали свойства и параметры объекта Command, но не выполняли эти команды. Существует три стандартных способа выполнения команд для объекта Command и один способ для объекта SqlCommand.
• Метод ExecuteNonQuery. Выполняет команду SQL и не возвращает записей.
• Метод ExecuteScalar. Выполняет команду SQL и возвращает первое поле первой записи.
• Метод ExecuteReader. Выполняет команду SQL и возвращает набор записей с помощью объекта DataReader.
• Метод ExecuteXmlReader (только для объекта-команды SqlCommand). Выполняет команду SQL и возвращает набор записей в формате XML с помощью объекта XmlReader.
Первые три метода рассматриваются далее в этой главе, а последний – в главе 10, "ADO.NET и XML", при обсуждении способов использования XML в модели ADO.NET.
Метод ExecuteNonQuery
Этот метод позволяет выполнять команды без возвращения значений (наборов записей или скалярных значений), кроме значения, сообщающего об успешном или неудачном исходе выполнения команды. Это наиболее эффективный способ выполнения команд по отношению к источнику данных. Таким образом можно выполнять команды SQL или хранимые процедуры, которые являются DDL-командами определения данных (например, для создания или изменения структуры базы данных: таблиц, представлений или хранимых процедур) либо DML-командами управления данными (например, их обновления, вставки или удаления).
НА ЗАМЕТКУ
Метод ExecuteNonQuery возвращает только целочисленное значение, сообщающее об успешном или неудачном исходе выполнения команды.
При удачном выполнении DDL-команд определения данных для изменения структуры баз данных возвращается значение -1, а при удачном выполнении DML управления данными для их обновления, вставки или удаления возвращается количество строк, задействованных в команде. При неудачном выполнении команд обоих типов возвращается значение 0.
Продолжая работу с проектом DataProviderObjects, попробуем использовать объекты пространства имен OleDb и базу данных pubs. Наша задача – создать новую таблицу tblStateZipCodes для этой базы данных с помощью DDL-команды. Новая таблица tblStateZipCodes предназначена для организации связи между почтовыми индексами и штатами. Определения ее полей совпадают с определениями полей в других таблицах базы данных pubs, но отличаются от определений полей в других таблицах базы данных Novelty. Эта таблица имеет два поля: ZipCode для почтового индекса и State для названия соответствующего штата. Ниже приведена команда SQL для создания этой таблицы.
CREATE TABLE tblStateZipCodes (
ZipCode char (5) NOT NULL,
State char (2) NOT NULL
)
Теперь нужно изменить исходную форму Form1, выполнив ряд действий.
1. Откройте форму Form1 в интегрированной среде разработки Visual Studio .NET.
2. В верхнем левом углу формы создайте еще одну кнопку, перетаскивая ее пиктограмму из панели элементов управления.
3. В окне свойств Properties укажите значение btnNonQuery для свойства (Name) и значение ExecuteNonQuery для свойства Text.
Затем создайте код подпрограммы btnNonQuery_Click, который приведен в листинге 4.4.
Листинг 4.4. Код создания таблицы базы данных с помощью объектов пространства имен OleDb
Private Sub btnNonQuery_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnNonQuery.Click
' Создание экземпляра объекта Connection.
Dim cnn As OleDbConnection = New OleDbConnection( _
"provider=SQLOLEDB;server=localhost;uid=sa;database=pubs")
Dim sql As String Dim result As Integer
' Создание экземпляра объекта Command.
Dim cmd As OleDbCommand = New OleDbCommand()
' Указание подключения и текста команды
cmd.Connection = cnn
cmd.CommandType = CommandType.Text
' Указание команды SQL для создания новой таблицы
sql = "CREATE TABLE tblStateZipCodes (" & _
"ZipCode char (5) NOT NULL, " & _
"State char (2) NOT NULL) "
MsgBox(sql) cmd.CommandText = sql
' Открытие подключения перед вызовом метода ExecuteNonQuery.
cnn.Open()
' Для обработки исключительных ситуаций нужно поместить
' код в блоке Try-Catch, потому что неудачное выполнение
' команды ТАКЖЕ генерирует ошибку времени выполнения.
Try
result = cmd.ExecuteNonQuery()
Catch ex As Exception
' Отображение сообщения об ошибке.
MessageBox.Show(ex.Message)
End Try
' Вывод результатов выполнения команды.
If result = -1 Then
MessageBox.Show("Command completed successfully")
' MessageBox.Show("Команда выполнена успешно")
Else
MessageBox.Show("Command execution failed")
' MessageBox.Show("Команда не выполнена")
End If
cnn.Close()
End Sub
После запуска полученного приложения и щелчка на кнопке ExecuteNonQuery сначала появится диалоговое окно с сообщением об успешном выполнении команды. Правильность выполнения команды можно проверить, просматривая список таблиц базы данных pubs в диалоговом окне Server Explorer интегрированной среды разработки Visual Studio .NET (которое описывается в главе 1, "Основы построения баз данных") или в программе SQL Server Enterprise Manager (которая рассматривается в главе 3, "Знакомство с SQL Server 2000").
При повторном щелчке на кнопке ExecuteNonQuery появятся два диалоговых окна с сообщениями: одно с сообщением о возникшей исключительной ситуации (оно создается блоком обработки исключительных ситуаций try-catch), а другое — о неудачном выполнении команды.
Аналогичным способом можно создать представление или хранимую процедуру. Для создания представления EmployeeJobs_view, которое возвращает отсортированный по должностям перечень сотрудников с указанием их имен, фамилий и должностей, замените команду SQL в листинге 4.4 приведенной ниже командой.
sql = "CREATE VIEW EmployeeJobs_view AS" & _
"SELECT TOP 100 PERCENT jobs. job_desc," & _
"employee.fname, employee.lname" &_
"FROM jobs INNER JOIN" & _
"employee ON jobs. job_id = employee. job_id &_
"ORDER BY jobs.job_desc"
НА ЗАМЕТКУ
Для включения предложения ORDER BY в определение представления с сортировкой результатов нужно включить в команду SELECT предложение ТОР.
Для создания хранимой процедуры, которая принимает один параметр и возвращает значение, нужно изменить эту команду SQL, как показано в листинге 4.5.
Листинг 4.5. Код, содержащий команду SQL для создания хранимой процедуры AuthorsInState1
sql = "CREATE PROCEDURE AuthorsInState1 @State char(2)" & _
" AS declare @result int" & _
" select @result = count (*) from authors " & _
" where state = @State" &_
" return (@result)"
НА ЗАМЕТКУ
Хотя метод не возвращает записи, входные и выходные параметры, а также возвращаемые значения можно передавать или возвращать с помощью коллекции Parameters. Это более эффективный подход, чем выполнение команды, которая возвращает набор записей или скалярное значение.
Обратимся теперь ко второму типу команд SQL для управления данными, т.е. их обновления, вставки или удаления. Для этих команд обычно требуется указать параметры, особенно при работе с хранимыми процедурами.
Продолжая работу с проектом DataProviderObjects, предположим, что издательство, которое создало базу данных pubs, решило повысить авторам выраженный в процентах гонорар. Включение новой кнопки и нового текстового поля в форму позволяет главному бухгалтеру издательства повысить гонорары авторов с помощью параметра команды UPDATE. Для этого нужно выполнить перечисленные ниже действия.
1. Создайте новую кнопку под кнопкой cmdExecuteNonQuery.
2. В окне свойств Properties для этой кнопки укажите значение cmdUpdate для свойства (Name) и значение Update для свойства Text.
3. Создайте новое текстовое поле под новой кнопкой Update.
4. В окне свойств Properties для этого текстового поля укажите значение txtParam1 для свойства (Name) и значение 0 для свойства Text. Установка такого значения гарантирует, что при запуске программы и случайном нажатии кнопки Update не будет причинен ущерб данным.
5. Создайте код подпрограммы btnUpdate_Click, приведенный в листинге 4.6.
Листинг 4.6. Код обновления таблицы базы данных с помощью команды UPDATE с параметром
Private Sub btnUpdate_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnUpdate.Click
Dim result As Integer
' Создание экземпляра объекта Connection.
Dim cnn As SqlConnection = New SqlConnection(_
"server=localhost;uid=sa;database=pubs")
' Создание экземпляра объекта Command.
Dim cmd As SqlCommand = New SqlCommand()
txtResults.Clear()
' Указание подключения и текста команды.
cmd.Connection = cnn
cmd.CommandType = CommandType.Text
cmd.CommandText = "UPDATE roysched SET royalty = royalty + @param1"
' Создание параметра и указание его значения.
cmd.Parameters.Add(New SqlParameter("@param1", SqlDbType.Int))
cmd.Parameters("@param1").Direction = ParameterDirection.Input
cmd.Parameters("@param1").Value = Val(txtParam1.Text)
' Открытие подключения перед вызовом метода ExecuteReader().
cnn.Open()
result = cmd.ExecuteNonQuery()
MessageBox.Show(result & " records updated", "DataProviderObjects")
cnn.Close()
End Sub
Теперь таблицу с гонорарами авторов в базе данных pubs можно обновить, запустив приложение DataProviderObjects, задав новое значение гонорара в текстовом поле под кнопкой Update и щелкнув на этой кнопке. После этого на экране появится диалоговое окно с указанием количества охваченных записей. Этот результат можно проверить с помощью программы SQL Server Enterprise Manager, просматривая данные о гонорарах в таблице roysched до и после обновления.
Точно такое же обновление можно выполнить с помощью хранимой процедуры, что позволяет добиться более высокой производительности и централизованно хранить код. Потенциальным недостатком использования хранимых процедур является необходимость назначения администратора базы данных или специалиста с опытом создания хранимых процедур. В крупных организациях порой уходят целые дни на то, чтобы администратор базы данных изменил хранимые процедуры, которые можно самостоятельно изменить за несколько минут. Хранимые процедуры создаются с помощью программ SQL Server Enterprise Manager или SQL Query Analyzer, которые описаны в главе 3, "Знакомство с SQL Server 2000". Это можно также сделать с помощью проекта DataProviderObjects, изменив команду SQL, как это делалось ранее.
Итак, в данном примере хранимая процедура имеет следующий вид:
CREATE PROCEDURE UpdateRoyalties
@param1 int
AS
UPDATE roysched SET royalty = royalty + @param1
В листинге 4.6 для организации вызова хранимой процедуры потребуется заменить свойства CommandText и CommandType объекта Command.
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "UpdateRoyalties"
Запуск этой программы должен привести к тем же результатам, что и прежде, однако теперь обновление происходит благодаря хранимой процедуре, а не команде SQL из кода приложения.
Метод ExecuteScalar
Иногда нужно выполнить команду, которая возвращает скалярное значение, т.е. только одно значение. Типичными примерами являются команды SQL для вычисления суммы всех значений SUM и общего количества значений COUNT. Другими примерами являются справочные таблицы для подстановки одного значения или команды, возвращающие логическое значение. Метод ExecuteScalar выполняет заданную команду и возвращает значение первой записи из первого поля возвращенного набора записей, а все другие поля и записи игнорируются.
Включим приведенную ниже хранимую процедуру в базу данных pubs.
CREATE PROCEDURE AuthorsInState2
@param1 char(2)
AS
SELECT count(*) FROM authors WHERE state = @param1
Хранимая процедура AuthorsInState2 принимает параметр, который имеет двухсимвольный код штата, и возвращает из таблицы authors количество авторов из этого штата. Эта процедура с функциональной точки зрения эквивалентна процедуре AuthorsInState1 из листинга 4.5, но возвращает вычисленное значение для набора записей, а не только индикатор успешности выполнения команды.
НА ЗАМЕТКУ
Использование метода ExecuteScalar вместо метода ЕxecuteNonQuerу и передача скалярного значения с помощью параметра ReturnValue связаны с дополнительными накладными расходами. Зачем же его используют? Он проще в употреблении, потому что не нужно заботиться об указании параметров в определениях и вызовах команд.
Для вызова данной хранимой процедуры с помощью провайдера данных ODBC выполните следующее.
1. Создайте дополнительную кнопку под текстовым полем txtParam1.
2. В окне свойств Properties укажите значение cmdScalar для свойства (Name) и значение ExecuteScalar для свойства Text.
3. Создайте код подпрограммы btnExecuteScalar_Click, приведенный в листинге 4.7.
Листинг 4.7. Код извлечения скалярного значения из хранимой процедуры с помощью провайдера данных ODBC
Private Sub btnExecuteScalar_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnExecuteScalar.Click
Dim result As Integer
' Создание экземпляра объекта Connection.
Dim cnn As OdbcConnection = New OdbcConnection( _
"DRIVER={SQL Server};server=localhost;uid=sa;database=pubs")
' Создание экземпляра объекта Command.
Dim cmd As OdbcCommand = New OdbcCommand()
txtResults.Clear()
' Указание подключения и текста команды.
cmd.Connection = cnn
cmd.CommandType = CommandType.StoredProcedure
cmd.CommandText = "{call AuthorsInState2(?) }"
' Создание параметра и указание его значения
cmd.Parameters.Add("@param1", OdbcType.Char, 2)
cmd.Parameters("@param1").Value = txtParam1.Text
' Открытие подключения перед вызовом метода
ExecuteReader. cnn.Open()
result = cmd.ExecuteScalar()
MessageBox.Show("Count is " & result, "DataProviderObjects")
cnn.Close()
End Sub
Запустите приложение и введите в текстовом поле над кнопкой ExecuteScalar двухсимвольный код штата. После щелчка на кнопке ExecuteScalar появится диалоговое окно с сообщением о количестве авторов в данном штате. Полученный результат можно проверить с помощью программы SQL Server Enterprise Manager, просматривая данные в таблице authors в базе данных pubs.
НА ЗАМЕТКУ
Учтите, что по умолчанию база данных pubs содержит двух авторов из штата Юта (код UТ) и 15 авторов из штата Калифорния (код СА).
Метод ExecuteReader
Этот метод применяется для возвращения набора записей. В большинстве приложений для работы с базами данных именно он используется чаще всего. Работа этого метода основана на объекте DataReader, с помощью которого записи обрабатываются последовательно одна за другой. Более подробно метод ExecuteReader и объект DataReader рассматриваются в следующем разделе.
Объект DataReader
Данный объект предназначен для чтения в прямом направлении небуферизуемого потока записей, полученных от метода ExecuteReader объекта Command. Объект DataReader в основном эквивалентен объекту Recordset модели ADO 2.X, который также предназначен для чтения в прямом направлении. Объект DataReader предлагает наиболее быстрый способ доступа к источнику данных, но в нем не предусмотрены возможности прокрутки и обновления данных. Поскольку данные не буферизуются и не сохраняются в кэше, этот метод прекрасно подходит для извлечения большого объема данных. Для перехода к следующей записи объекта DataReader нужно вызвать его метод Read.
Кроме коллекции Fields, доступ к полям каждой записи можно осуществить с помощью строго типизированных методов. Доступ к полям с помощью коллекции Fields аналогичен способу доступа к полям в модели ADO 2.X.
X = MyReader("Myfield")
НА ЗАМЕТКУ
Объект DataReader не имеет явного конструктора, т.е. его нельзя создать с помощью оператора New(). Для инициализации нового объекта нужно вызвать метод ExecuteReader объекта Command.
Зная тип данных в поле, доступ к нему можно получить с помощью типизированных методов доступа. Эти методы извлекают значение поля по номеру поля, нумерация которого начинается с нуля, например:
X = MyReader.GetInt16(1)
или
X = MyReader.GetString(2)
В самом первом примере используется имя поля Myfield, что значительно упрощает его чтение, использование и совместимость с унаследованными программами. Во втором примере используется номер поля, что значительно повышает производительность, поскольку исключается преобразование имени поля в его номер.
Для демонстрации способов использования этих методов выполните следующее.
1. Создайте под кнопкой ExecuteScalar дополнительную кнопку.
2. В окне свойств Properties укажите значение cmdExecuteReader для свойства (Name) и значение ExecuteReader для свойства Text.
3. Создайте код подпрограммы btnExecuteReader_Click, приведенный в листинге 4.8.
НА ЗАМЕТКУ
Кроме способов использования объекта DataReader, в этом примере демонстрируются и другие функциональные возможности. Например, здесь помимо членов Text и StoredProcedure свойства-перечисления CommandType для указания типа команды используется член TableDirect. Он содержит имя таблицы, все поля которой возвращаются данной командой. Учтите, что этот член перечисления поддерживается только для провайдера данных ODBC.
Представления обычно обрабатываются точно также, как и таблицы. Следовательно, при использовании члена TableDirect свойства-перечисления CommandType можно указывать не только имя таблицы, но и имя представления.
Листинг 4.8. Код создания объекта DataReader и извлечения значений полей с помощью представления и члена TableDirect
Private Sub btnExecuteReader_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnExecuteReader.Click
' Создание экземпляра объекта Connection.
Dim cnn As OleDbConnection = New OleDbConnection( _
"provider=SQLOLEDB;server=localhost;uid=sa;database=pubs")
' Создание экземпляра объекта Command.
Dim As OleDbCommand = New OleDbCommand()
txtResults.Clear()
' Указание подключения и текста команды
cmd.Connection = cnn
cmd.CommandType = CommandType.TableDirect
cmd.CommandText = "EmployeeJobs_View"
' Открытие подключения перед вызовом метода
ExecuteReader. cnn.Open()
Dim reader As OleDbDataReader
reader = cmd.ExecuteReader{)
While reader.Read()
txtResults.Text = txtResults.Text & reader("fname") & _
ControlChars.Tab & reader("lname") & _
ControlChars.Tab & ControlChars.Tab & _
reader("job_desc") & ControlChars.CrLf
End While
reader.Close()
cnn.Close()
End Sub
(Здесь предполагается, что представление EmployeeJobs_view уже создано с помощью подпрограммы btnNonQuery_Click из листинга 4.4, как описано выше. – Прим. ред.)
НА ЗАМЕТКУ
Перед попыткой доступа к данным объекта DataReader не забывайте вызывать метод Read(). В отличие от объекта Recordset в модели ADO 2.X, в которой после загрузки данных текущее расположение автоматически находится на первой записи, в модели ADO.NET в объекте DataReader нужно явно указать текущее расположение возле первой записи с помощью исходного вызова метода
Для организации доступа к данным можно также применить цикл While с более эффективными строго типизированными методами доступа к полям.
While reader. Read()
txtResults.Text = txtResults.Text & reader.GetString(1) & _
ControlChars.Tab & reader.GetString(2) & _
ControlChars.Tab & ControlChars.Tab & _
reader. GetString(0) & ControlChars.Ctrlf
End While
Еще одно изменение, которое диктуется личным вкусом и стилем программирования автора, заключается в объединении определения объекта DataReader и выполнения метода ExecuteReader в одной строке, т.е. вместо фрагмента кода
Dim reader As OleDbDataReader reader = cmd.ExecuteReader()
можно использовать следующую строку:
Dim reader As OleDbDataReader = cmd.ExecuteReader()
После запуска приложения DataProviderObjects щелкните на кнопке ExecuteReader, и в текстовом поле справа будут отображены данные из представления EmployeeJobs_view, как показано на рис. 4.4.
РИС. 4.4. Результаты выполнения команды ExecuteReader из листинга 4.8
НА ЗАМЕТКУ
По окончании использования объекта DataReader следует вызвать метод Close. Дело в том, что выходные данные или возвращаемые значения объекта Command недоступны до тех пор, пока объект DataReader открыт. Он остается открытым до тех пор, пока открыто само подключение или объект DataReader.
Объект DataReader также предлагает простой и эффективный способ создания Web-страниц для работы сданными на основе элемента управления DataGrid, который подробно рассматривается в главе 11, "Web-формы: приложения на основе ASP.NET для работы с базами данных".
Использование объектов Connection и Command во время создания приложения
Вкладка Data панели элементов управления среды Visual Studio .NET содержит компоненты, которые соответствуют некоторым методам доступа к данным. С помощью окна Properties они позволяют указывать значения свойств во время создания приложения, а не только во время его выполнения. Они также предлагают визуальные инструменты указания значений для более сложных свойств.
Например, для реализации кода из листинга 4.8 с помощью таких элементов управления выполните перечисленные ниже действия.
1. Создайте еще одну форму Form2 в проекте DataProviderObjects.
2. В окне свойств Properties укажите значение Connection and Command Components для свойства Text формы Form2.
3. Увеличьте размер формы.
4. Включите в форму текстовое поле Textbox1.
5. В окне свойств Properties укажите значение True для свойства Multiline и значение Both для свойства Scrollbars этого текстового поля.
6. Увеличьте размер текстового поля так, чтобы оно покрывало большую часть формы.
7. Из вкладки Data панели элементов управления перетащите элемент управления OleDbConnection в форму Form2. Этот компонент невидим во время выполнения приложения, поэтому он появится в разделе невизуальных компонентов в нижней части окна редактирования формы.
8. В окне свойств Properties укажите приведенное ниже значение для свойства ConnectionString элемента управления OledbConnection1.
provider=SQLOLEDB;server=localhost;uid=sa;database=pubs
9. Из вкладки Data панели элементов управления перетащите еще один элемент управления OleDbCommand в форму Form2. Этот компонент также невидим во время выполнения приложения, поэтому он появится в нижней части окна редактирования формы.
10. В окне свойств Properties укажите значение OledbConnection1 для свойства Connection и приведенное ниже значение для свойства CommandText элемента управления OledbCommand1.
SELECT * FROM EmployeeJobs_view
11. Создайте код подпрограммы Form2_Load, приведенный в листинге 4.9.
СОВЕТ
В главе 6, "ADO.NET: объект DataAdapter" представлены графические инструменты, которые позволяют автоматически создать строку подключения и текст команды SQL вместо создания вручную их кода.
Листинг 4.9. Код создания объекта DataReader и извлечения значений полей с помощью компонентов SqlConnection и SqlCommand
Private Sub Form2_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
' Открытие подключения перед вызовом метода ExecuteReader.
OleDbConnection1.Open()
Dim reader = OleDbCommand1.ExecuteReader()
TextBox1.Clear()
While reader.Read()
TextBox1.Text = TextBox1.Text & reader("fname") & _
ControlChars.Tab & reader("lname") & _
ControlChars.Tab & ControlChars.Tab & _
reader("job_desc") & ControlChars.CrLf
End While
' Отмена выбора всех строк в текстовом поле.
TextBox1.SelectionLength = 0
reader.Close()
OleDbConnection1.Close()
End Sub
12. Щелкните правой кнопкой мыши на проекте DataProviderObjects в окне Solution Explorer и выберите команду Properties в контекстном меню.
13. В папке Common Properties выберите элемент General, а затем выберите форму Form2 в текстовом поле Startup object (Объект запуска) данного приложения.
После запуска приложения DataProviderObjects в текстовом поле формы Form2 будут отображены данные из представления EmployeeJobs_view, как показано на рис. 4.5.
Другие провайдеры данных
Выше были представлены классы нескольких основных провайдеров данных (например, Parameter и Parameters), а также четыре основных объекта в табл. 4.1. В главе 5, "ADO.NET: объект DataSet", более подробно рассматривается объект DataSet и связанные с ним объекты, а в главе 6, "ADO.NET: объект DataAdapter", – объект DataAdapter.
В оставшейся части главы рассматривается еще один провайдер данных — объект-транзакция Transaction. Транзакции используются для гарантированного успешного завершения сразу нескольких связанных операций по принципу "все или ничего". Это значит, что либо все операции транзакции успешно выполняются, либо они вообще не выполняются. Классическим примером транзакции является банковская операция перечисления денежных средств. Эта операция состоит из двух этапов: удержание денежной суммы с одного счета и зачисление ее на другой счет. При этом желательно избегать ситуаций, когда успешно выполняется только первый этап транзакции!
РИС. 4.5. Результаты отображения данных в форме Form2 с помощью элементов управления OleDbConnection и OleDbCommand
Провайдеры данных ADO.NET содержат объект Transaction, который имеет фундаментальные методы обработки транзакций. Метод Commit фиксирует текущую транзакцию, а метод Rollback – откатывает (отменяет) текущую транзакцию. Выполнение транзакции и создание объекта Transaction осуществляется с помощью вызова метода BeginTransaction по отношению к открытому объекту Connection. Способ использования объекта Transaction демонстрируется на примере бизнес-ситуации 4.1.
Бизнес-ситуация 4.1: создание процедуры для архивирования старых заказов по годам
После относительно длительного использования системы управления базами данных некоторые данные рекомендуется архивировать. В каждой рабочей системе операцию архивирования следует включить в состав обязательных и регулярно выполняемых операций резервного копирования. Архивируемые данные — это данные, которые нужны не для постоянного использования (т.е. в оперативном режиме), а только изредка. Удаление этих данных из основных оперативных таблиц базы данных может повысить производительность операций доступа к этим таблицам, так как при этом приходится обрабатывать и фильтровать меньше записей. Однако архивная таблица часто хранится в идентичном формате таблицы и доступ к ней в случае необходимости можно организовать аналогичным образом. В этой бизнес-ситуации создается простая форма для выполнения простого архивирования данных из таблицы tblOrder базы данных Novelty. Она позволит выбирать и архивировать заказы по годам, т.е. после выбора нужного года выполняются перечисленные ниже действия.
1. Сначала в базе данных создается новая таблица tblOrderXXXX, где ХХХХ обозначает тот год, записи о заказах которого будут архивироваться.
2. Затем все записи о заказах за указанный год копируются из таблицы tblOrder втаблицу tblOrderXXXX.
3. Все скопированные записи о заказах за указанный год удаляются из таблицы tblOrder.
Хитрость здесь заключается в том, чтобы отменить всю транзакцию при неудачном выполнении какой-либо ее операции. Нам не нужна новая таблица, если в нее нельзя скопировать данные. Не нужно архивировать данные, если они не удаляются из основной таблицы. Аналогично, не нужно удалять никакие записи из основной таблицы, если они не скопированы в архивную таблицу. Для решения этой задачи можно использовать объект Transaction и вернуть (откатить) базу данных в исходное состояние в случае сбоя каких-то операций. Для создания формы с этими функциями выполните перечисленные ниже действия.
Запустите интегрированную среду разработки Visual Studio .NET.
2. Создайте новый проект Visual Basic Windows Application.
3. Назовите проект BusinessCase4.
4. Укажите путь к файлам проекта.
5. Увеличьте размер формы Form1.
6. В окне Properties укажите значение frmArchive для свойства (Name) и значение Archive Orders для свойства Text формы Form1.
7. Создайте в форме поле со списком lstYears, надпись Label1, кнопку bntOK и кнопку btnCancel, перетаскивая их из панели элементов управления.
8. В окне Properties укажите значение Archive all orders for the year для свойства Text надписи, значение OK для кнопки btnOK и значение Cancel для кнопки btnCancel.
9. Расположите все элементы управления, как показано на рис. 4.6.
РИС. 4.6. Расположение элементов управления в форме frmArchive
В верхней части файла с исходным кодом вставьте приведенную ниже строку кода для импорта пространства имен SqlClient.
Imports System.Data.SqlClient
В теле определения класса для формы frmArchive включите код из листинга 4.10.
Листинг 4.10. Код архивирования данных в новой таблице
Private Sub frmArchive_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
lstYears.Items.Add("1995")
lstYears.Items.Add("1996")
lstYears.Items.Add("1997")
lstYears.Items.Add("1998")
lstYears.Items.Add("1999")
lstYears.Items.Add("2000")
lstYears.Items.Add("2001")
lstYears.Items.Add("2002")
' Указание значения по умолчанию.
lstYears.SelectedIndex = 0
End Sub
Private Sub btnCancel_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnCancel.Click
Me.Close()
End Sub
Private Sub btnOK_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles btnOK.Click
Dim sql As String
Dim result As Integer
Dim records As Integer
Dim SelectedYear As String
' Создание экземпляров объектов Connection и Command.
Dim cnn As SqlConnection = New SqlConnection( _
"server=localhost;uid=sa;database=novelty")
Dim cmd As New SqlCommand()
Dim trans As SqlTransaction
' Получение значения года.
SelectedYear = lstYears.SelectedItem.ToString
' Размещение кода внутри блока Try-Catch для
' обработки исключительных ситуаций.
Try
' Открытие объекта Connection и запуск транзакции.
cnn.Open()
trans = cnn.BeginTransaction
' Включение команды в транзакцию.
cmd.Connection = cnn
cmd.Transaction = trans
' Указание команды SQL для вставки соответствующих
' записей в архивную таблицу.
sql = "SELECT * INTO tblOrder" & SelectedYear & _
FROM tblOrder WHERE year (OrderDate) = " & SelectedYear
' Передача текста команды SQL в транзакцию.
cmd.CommandText = sql
result = cmd.ExecuteNonQuery()
' Отображение результатов вставки записей в архивную таблицу.
If result > 0 Then
records = result MessageBox.Show(records & _
" records inserted successfully into tblOrder" & SelectedYear)
Else
MessageBox.Show( _
"No records inserted into tblOrder" & SelectedYear)
' При отсутствии записей созданная таблица
' не нужна и транзакцию нужно откатить.
trans.Rollback()
End If
If records > 0 Then
' Команда SQL для удаления соответствующих
' записей из текущей таблицы.
sql = "delete FROM tblOrder WHERE year (OrderDate) = " _
& SelectedYear
' Эта команда находится в той же транзакции.
cmd.CommandText = sql
result = cmd.ExecuteNonQuery()
' Показать результаты удаления записей.
If result = records Then
MessageBox.Show(records & _
" records deleted successfully")
' Все действия успешно выполнены, можно фиксировать транзакцию.
trans.Commit()
Else
MessageBox.Show("No records deleted!")
End If
Else
' Никаких действий.
End If
Catch ex As Exception
' Какие-то действия не выполнены, поэтому нужно
' отменить (откатить) всю транзакцию.
Try
' Отображение сообщения об ошибке.
MessageBox.Show(ex.Message & _
ControlChars.CrLf & ControlChars.CrLf & _
"Transaction Failed!")
trans.Rollback()
Catch ex2 As Exception
End Try
Finally
cnn.Close()
End Try
End Sub
Подпрограмма frmArchive_Load инициализирует список lstYears значениями, из которых можно выбрать год архивирования, и выбирает используемый по умолчанию год. Конечно, эту подпрограмму можно было бы усовершенствовать так, чтобы в списке отображались только те годы, для которых существуют записи о заказах. Однако для демонстрации принципов работы транзакций достаточно и такой подпрограммы.
Подпрограмма btnCancel_Click обработки щелчков мышью на кнопке Cancel просто закрывает форму, что в данном случае приводит к закрытию программы. Все необходимые действия выполняются обработчиком щелчков мышью на кнопке OK. После объявлений переменных следует получить выбранный год из списка lstYears и сохранить его для дальнейшего использования. Для гарантированной отмены транзакции в случае возникновения любой исключительной ситуации следует окружить активный код блоком Try-Catch-Finally.
Поскольку транзакции определяются на уровне подключения, то сначала нужно открыть подключение, а затем создать объект Transaction с помощью вызова метода BeginTransaction для открытого подключения. Объекты Connection и Transaction присваиваются объекту Command, который будет использоваться для выполнения команд по отношению к базе данных.
Первые два этапа создания архивной таблицы и копирования выбранных строк выполняются с помощью одной команды SELECT, которая содержит предложение INTO имя_таблицы. Указанная таким образом таблица создается автоматически, а если такая таблица уже существует, то генерируется исключительная ситуация. Выбранное значение года добавляется к имени таблицы tblOrder для создания имени новой архивной таблицы.
НА ЗАМЕТКУ
Команда SELECT INTO не создает индекс, если он существует в исходной таблице. Для повышения производительности выполнения запросов по отношению к данной таблице, вероятно, придется создать индексы по одному или нескольким полям.
Для выполнения команды SQL вызывается метод ExecuteNonQuery, который возвращает количество охваченных запросом записей. Если это возвращаемое значение больше нуля, следовательно, нужные записи найдены и вставлены в новую таблицу. В противном случае это значит, что таблица не может быть создана либо для копирования не найдено никаких записей. В любом из этих двух случаев транзакция откатывается, даже если таблица создана, чтобы база данных не наполнялась пустыми и бесполезными таблицами.
Если хотя бы одна запись скопирована в таблицу, то ее прототип в исходной таблице tblOrder удаляется с помощью команды DELETE, содержащей предложение WHERE с заданным годом. В случае успешного выполнения этой команды, т.e. если количество скопированных и удаленных строк совпадает, транзакция считается успешно завершенной и фиксируется. В противном случае, т.е. если какая-то отдельная операция завершится неудачно (нарушится процесс удаления записей, будет отменено разрешение на удаление архивируемых данных или произойдет сбой сервера), вся транзакция будет отвергнута. Откат транзакции гарантирует, что при неудачной попытке удаления корректных записей из таблицы tblOrder архивная таблица tblOrderХХХХ будет удалена.
До сих пор рассматривались только те исключительные ситуации, которые возникают при нарушении последовательного выполнения операций подпрограммы. Однако следует также позаботиться о многих других исключительных ситуациях, которые могут возникнуть во время выполнения. Например, исключительная ситуация может возникнуть при попытке создания уже существующей таблицы. Для ее перехвата и обработки следует использовать блок Try-Catch, который в данном случае выводит текстовое сообщение об исключительной ситуации, а вся транзакция отвергается.
НА ЗАМЕТКУ
Еще один блок Try-Catch потребуется для обработки исключительной ситуации, когда архивная таблица не может быть создана (например, из-за наличия именно такой таблицы). Основная причина, по которой она предлагается, заключается в том, что транзакция началась, но никаких изменений данных не произошло, а потому в файл регистрации транзакций не записано никаких действий, которые нужно откатить.
В блоке Finally используемое подключение закрывается независимо от того, произошла или нет исключительная ситуация.
Выполните все описанные выше действия и поэкспериментируйте с этим приложением, создавая архивы для записей разных лет. Для проверки полученных результатов сопоставьте содержимое новой архивной и исходной таблиц до и после архивирования. Не забывайте, что содержимое исходной таблицы tblOrder всегда можно вернуть в первоначальное состояние, запуская сценарий ее создания и наполнения данными.
Резюме
В этой главе приводятся общие сведения об ADO.NET и некоторых объектах провайдеров данных .NET. Провайдеры данных образуют интерфейс ADO.NET для взаимодействия с физическими хранилищами данных и предлагают модель программирования в режиме подключения. Здесь кратко рассматриваются свойства и методы объектов Connection, Command, Parameter, DataReader и Transaction, включая стандартные провайдеры данных SqlClient, OleDb и Odbc. В главе 5, "ADO.NET: объект DataSet", описываются способы работы с данными в отключенном режиме на основе объектов DataSet и DataAdapter.
Вопросы и ответы
Судя по содержанию этой главы, модель ADO.NET предназначена для работы в отключенном режиме и нет никакой поддержки для серверных курсоров или пессимистической блокировки. А что же делать, если уже существующее приложение использует их или спецификации нового проекта требуют их применения? Следует ли мне использовать для этого только Visual Basic 6.0?
Во-первых, необходимо тщательно проанализировать приложение и убедиться в том, что действительно всегда нужно использовать серверные курсоры или пессимистические блокировки. Если они (или какие-то другие компоненты, которые не поддерживаются в ADO.NET) действительно необходимы, не стоит отчаиваться. Visual Basic. NET все равно можно использовать для таких приложений, так как на платформе .NET Framework предусмотрена расширенная поддержка взаимодействия с COM, что позволяет использовать в .NET-совместимом приложении COM-объекты, которые, в свою очередь, могут использовать управляемый (.NET-совместимый) код. Иначе говоря, можно использовать не только ADO 2.X, но и любые другие COM-объекты, для которых нет аналогов в .NET. Конечно, за организацию взаимодействия между COM и.NET придется заплатить снижением производительности. В какой мере? Ответ на этот вопрос можно получить только после тщательного тестирования приложения.
Похоже, что программирование объектов, методов и свойств ничем не отличается от способов их программирования в модели ADO 2.X. Зачем же переходить к модели ADO.NET?
Конечно, выполнение основных операций подключения к базе данных не очень отличается от таких же операций в модели ADO 2.X. Однако, кроме уже упомянутых здесь небольших усовершенствований, следует иметь в виду ряд важных отличий.
1. Visual Basic .NET и платформа.NET представляют собой совершенно новый мир, а модель ADO.NET является способом доступа к данным в этом мире.
2. Хотя при создании .NET-совместимых приложений можно продолжать использование уже существующих COM-компонентов, например из модели ADO 2.X, такой способ связан с сокращением производительности при доступе к COM-компонентам и необходимости их корректной инсталляции и регистрации.
3. Рассмотренные в этой главе объекты разных провайдеров данных отвечают только за часть всех операций с базой данных, а именно за чтение и запись данных в подключенном режиме. Другая часть операций выполняется в отключенном режиме и связана с объектом DataSet. Именно в этом режиме прекрасно проявляются преимущества модели ADO.NET, и потому в следующей главе описываются базовые блоки объекта DataSet.