|
||||||||||||
|
ГЛАВА 4Модель ADO.NET: провайдеры данных Порой кажется, что не успели еще разработчики приложений баз данных привыкнуть к новой технологии, как компания 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 FrameworkADO.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. НА ЗАМЕТКУ Основные объектыКаждый провайдер данных имеет четыре основных объекта, которые указаны в табл. 4.1. Таблица 4.1. Основные объекты провайдера данных
Каждый объект основан на базовом родовом классе и реализует родовой интерфейс, но имеет собственную реализацию. Например, объекты 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) может затем использовать это подключение для выполнения нужных операций с источником данных. НА ЗАМЕТКУ Хотя объекты 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 для каждого провайдера данных. НА ЗАМЕТКУ Теперь для кнопки 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 СОВЕТ После запуска полученного приложения DataProviderObjects и щелчка на кнопке Connection в текстовом поле появятся строки о закрытии подключения, о состоянии подключения и повторном открытии подключения, как показано на рис. 4.3. НА ЗАМЕТКУ Объект 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). НА ЗАМЕТКУ Продолжим работу с формой, показанной на рис. 4.3. 1. Добавим еще одну кнопку сразу под кнопкой btnConnection, перетаскивая пиктограмму кнопки из панели элементов управления. 2. В окне свойств Properties установите значение btnCommand для свойства Name и значение Command для свойства Text. 3. Добавьте для кнопки код, показанный в листинге 4.2. Листинг 4.2. Код открытия подключения к базе данных и подготовки объекта CommandPrivate 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. НА ЗАМЕТКУ Применение объекта 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" СОВЕТ Измените код кнопки 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" СОВЕТ Чтобы хранимая процедура возвращала значение, нужно указать направление 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) НА ЗАМЕТКУ Далее представлены другие фрагменты кода с указанием параметров команд и хранимых процедур, а также описываются способы их выполнения. Выполнение командДо сих пор мы только указывали свойства и параметры объекта 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-командами управления данными (например, их обновления, вставки или удаления). НА ЗАМЕТКУ Продолжая работу с проектом 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. Код создания таблицы базы данных с помощью объектов пространства имен OleDbPrivate 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" НА ЗАМЕТКУ Для создания хранимой процедуры, которая принимает один параметр и возвращает значение, нужно изменить эту команду SQL, как показано в листинге 4.5. Листинг 4.5. Код, содержащий команду SQL для создания хранимой процедуры AuthorsInState1sql = "CREATE PROCEDURE AuthorsInState1 @State char(2)" & _ " AS declare @result int" & _ " select @result = count (*) from authors " & _ " where state = @State" &_ " return (@result)" НА ЗАМЕТКУ Обратимся теперь ко второму типу команд 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, но возвращает вычисленное значение для набора записей, а не только индикатор успешности выполнения команды. НА ЗАМЕТКУ Для вызова данной хранимой процедуры с помощью провайдера данных ODBC выполните следующее. 1. Создайте дополнительную кнопку под текстовым полем txtParam1. 2. В окне свойств Properties укажите значение cmdScalar для свойства (Name) и значение ExecuteScalar для свойства Text. 3. Создайте код подпрограммы btnExecuteScalar_Click, приведенный в листинге 4.7. Листинг 4.7. Код извлечения скалярного значения из хранимой процедуры с помощью провайдера данных ODBCPrivate 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. НА ЗАМЕТКУ Метод ExecuteReaderЭтот метод применяется для возвращения набора записей. В большинстве приложений для работы с базами данных именно он используется чаще всего. Работа этого метода основана на объекте DataReader, с помощью которого записи обрабатываются последовательно одна за другой. Более подробно метод ExecuteReader и объект DataReader рассматриваются в следующем разделе. Объект DataReaderДанный объект предназначен для чтения в прямом направлении небуферизуемого потока записей, полученных от метода ExecuteReader объекта Command. Объект DataReader в основном эквивалентен объекту Recordset модели ADO 2.X, который также предназначен для чтения в прямом направлении. Объект DataReader предлагает наиболее быстрый способ доступа к источнику данных, но в нем не предусмотрены возможности прокрутки и обновления данных. Поскольку данные не буферизуются и не сохраняются в кэше, этот метод прекрасно подходит для извлечения большого объема данных. Для перехода к следующей записи объекта DataReader нужно вызвать его метод Read. Кроме коллекции Fields, доступ к полям каждой записи можно осуществить с помощью строго типизированных методов. Доступ к полям с помощью коллекции Fields аналогичен способу доступа к полям в модели ADO 2.X. X = MyReader("Myfield") НА ЗАМЕТКУ Зная тип данных в поле, доступ к нему можно получить с помощью типизированных методов доступа. Эти методы извлекают значение поля по номеру поля, нумерация которого начинается с нуля, например: X = MyReader.GetInt16(1) или X = MyReader.GetString(2) В самом первом примере используется имя поля Myfield, что значительно упрощает его чтение, использование и совместимость с унаследованными программами. Во втором примере используется номер поля, что значительно повышает производительность, поскольку исключается преобразование имени поля в его номер. Для демонстрации способов использования этих методов выполните следующее. 1. Создайте под кнопкой ExecuteScalar дополнительную кнопку. 2. В окне свойств Properties укажите значение cmdExecuteReader для свойства (Name) и значение ExecuteReader для свойства Text. 3. Создайте код подпрограммы btnExecuteReader_Click, приведенный в листинге 4.8. НА ЗАМЕТКУЛистинг 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, как описано выше. – Прим. ред.) НА ЗАМЕТКУ Для организации доступа к данным можно также применить цикл 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 также предлагает простой и эффективный способ создания 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. СОВЕТЛистинг 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 для создания имени новой архивной таблицы. НА ЗАМЕТКУ Для выполнения команды SQL вызывается метод ExecuteNonQuery, который возвращает количество охваченных запросом записей. Если это возвращаемое значение больше нуля, следовательно, нужные записи найдены и вставлены в новую таблицу. В противном случае это значит, что таблица не может быть создана либо для копирования не найдено никаких записей. В любом из этих двух случаев транзакция откатывается, даже если таблица создана, чтобы база данных не наполнялась пустыми и бесполезными таблицами. Если хотя бы одна запись скопирована в таблицу, то ее прототип в исходной таблице tblOrder удаляется с помощью команды DELETE, содержащей предложение WHERE с заданным годом. В случае успешного выполнения этой команды, т.e. если количество скопированных и удаленных строк совпадает, транзакция считается успешно завершенной и фиксируется. В противном случае, т.е. если какая-то отдельная операция завершится неудачно (нарушится процесс удаления записей, будет отменено разрешение на удаление архивируемых данных или произойдет сбой сервера), вся транзакция будет отвергнута. Откат транзакции гарантирует, что при неудачной попытке удаления корректных записей из таблицы tblOrder архивная таблица tblOrderХХХХ будет удалена. До сих пор рассматривались только те исключительные ситуации, которые возникают при нарушении последовательного выполнения операций подпрограммы. Однако следует также позаботиться о многих других исключительных ситуациях, которые могут возникнуть во время выполнения. Например, исключительная ситуация может возникнуть при попытке создания уже существующей таблицы. Для ее перехвата и обработки следует использовать блок 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. |
|
||||||||||
Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх |
||||||||||||
|