Книги, научные публикации Pages:     | 1 | 2 | 3 | 4 |   ...   | 8 |

David Sceppa Microsoft Press Дэвид Москва 2003 Р Г Г If Р F г V А Л А - л г УДК 004.45 32.973.26-018.2 С28 Д. ...

-- [ Страница 2 ] --

62 Часть II Подключаемся: поставщика данных Microsoft не рекомендует сохранять пароль в приложении подобным образом, поскольку это небезопасно. Visual Studio создает управляемый код code), который разрешается декомпилировать, т. пользователи могут приложение и узнать пароль. Если это создает вам неудобства, опустите пароль в строке подключения и предложите пользователю ввести его самостоятельно. Кроме того, предусмотрена интегрированная проверка подлинности. Подробно о ней Ч далее.

Интегрированная проверка подлинности Кто сказал, что для соединения с БД нужен пароль? Некоторые например SQL Server, позволяют подключаться с использованием реквизитов для входа в сеть.

Эту возможность называют по-разному Ч сетевая проверка подлинности, проверка подлинности средствами Windows, подлинности Windows NT и интег рированная проверка подлинности. Концепция ее достаточно проста. Не запра шивая у пользователя имя и пароль, БД просит сеть пользова теля. Затем БД проверяет свой список пользователей и определяет, имеются ли у пользователя разрешения на подключение.

Подробнее об применении интегрированной проверки подлинности в SQL Server Ч в разделе Administering SQL справочной системы SQL Server Books Добавление соединений в приложение Теперь, когда вы научились добавлять соединения в Server Explorer, я расскажу, как с помощью Server Explorer добавить объект Connection в приложение. Все делает ся простым перетаскиванием и Выберите Server Explorer нужное соединение, перетащите его в конструктор проекта и затем отпустите. В панели компонентов конструктора появится новый объект Connection.

Панель компонентов Многие элементы проекта Visual Studio например Windows-формы, классы компонентов, Web-формы и Web-сервисы ASP.NET, позволяют визуально взаимо действовать с элементами управления и компонентами. При работе с простой можно добавить на нес видимые управления Ч текстовые поля и т.д. Свойства этих элементов задаются в окне Properties. Эта возможность давно знакома разработчикам, использующим такие программные продукты, как Visual Basic и Microsoft Visual А что, если добавить компонент, у которого, в отличие от, например, кнопки на форме, нет пользовательского интерфейса? В предыдущих версиях Visual Basic работа с такими компонентами осуществлялась, как с элементами и они отображались в виде значков на форме, видимых только в период разработ ки. В Visual Studio -NET используется несколько другой подход. Компоненты без пользовательского интерфейса отображаются в панели под конструктором панели компонентов. Она позволяет вам работать с такими элементами и зада вать их свойства в период разработки с помощью окна Properties, ГЛАВА 3 Подключение к базе данных S Использование нового соединения Перетащив соединение из окна Server Explorer в конструктор, вы создали объект Connection в панели компонентов конструктора. Тип этого объекта зависит от типа соединения, выбранного в Server Explorer. При перетаскивании соединения с SQL Server создается объект SqlConnection, а при перетаскивании соединения с любым другим источником данных Ч Примечание На момент написания этой книги перетаскивать из Server соединения, использующие драйверы ODBC, было нельзя. Система вы давала сообщения о невозможности использования поставщика OLE Provider for ODBC Drivers. Предполагается, что поддержка создания тов (объектов Connection для поставщика данных ODBC появится в будущих версиях Visual Studio.

На рис. 3-9 показано, что произойдет после перетаскивания соединения из окна Server Explorer в конструктор Windows-формы.

[ Б * Рис. 3-9. Новый объект SqlConnection в панели компонентов Visual Studio Поскольку мы перетащили в конструктор соединение с SQL будет со здан объект SqlConnection. Visual Studio автоматически задает ему имя на ос нове имени класса. Новый объект Connection уже Его свойству задана строка подключения, соответствующая которое вы перетащили из Server Explorer.

Создание объектов Connection с помощью панели инструментов Объекты Connection можно также создавать, перетаскивая элементы с вкладки Data панели инструментов Visual Studio При этом разрешается указывать, объект Connection какого данных вам нужен. На рис. показан но Часть использование поставщика данных вый объект созданный путем перетаскивания элемента из панели инструментов.

X }. used to со Рис. 3-Ю. Настройка строки подключения с помощью окна Properties Заметьте: свойства не заданы, пока... не заданы. Основное свойство объекта независимо от используемого поставщика данных Ч Можно выбрать это свойство в окне Properties и ввести его значение вруч Можно также запросить помощь у Visual Studio Выберите в окне Properties свойство и в раскрывающемся списке просмотрите список доступных в Server Explorer соединений. Список заканчива ется пунктом Если щелкнуть его, откроется диалоговое окно Data Link.

Использование нового соединения в период выполнения Сейчас поговорим о том, как созданное на этапе разработки новое соединение в период выполнения. Перетащите на Windows-форму кнопку и дважды ее. Откроется редактор кода, и Visual Studio создаст процедуру для обработки события Click кнопки. Добавьте в это событие следующий код:

Visual Basic closed Visual C# SqlConnectionl opened closed ГЛАВА 3 Подключение к базе данных $ Примечание Если в вашем проекте используется объект а SqlConnection, замените в предыдущих фрагментах кода на 1.

Теперь запустите проект и щелкните кнопку. Откроются диалоговые с сообщением, что вы успешно установили и разорвали соединение с БД.

Где же код?

сейчас у нас есть очень простое приложение. Мы создали объект в период разработки, а также добавили кнопку и код, чтобы использовать и период выполнения. Добавленный нами код показан на рис. Но как в при ложение попадает объект Рис. 3-11* код Visual Studio преобразует объекты, создаваемые вами в период разработ ки (например, SqlConnection в панели компонентов и кнопку на форме), в код помещает его в процедуру компонента. По умолчанию процедура скрыта в области Windows Form Designer Generated Code (рис.

Есть две причины скрывать этот код. Во-первых, разработчики обычно хотят уделять основное внимание а не сгенерированному мастером коду.

необходимости вы можете щелкнуть значок чтобы раскрыть нужную область.

Во-вторых, что более Microsoft крайне не рекомендует изменять код в скры той области.

Большинство конструкторов Visual Studio генерирующих добавляемый в эту область код, являются конструкторами повторного входа. Например, кнопка на форме. Как видно из рис. конструктор Forms Designer преобра зовал добавленную нами на этапе разработки кнопку в код периода Часть II Подключаемся: использование поставщика данных If Х. I Рис. 3-12. Код. сгенерированный конструктором Windows Forms Designer Если закрыть и повторно открыть проект, а затем просмотреть форму в кон структоре Windows Forms Designer, он воспользуется этим кодом и выведет на форме кнопку. Microsoft крайне не рекомендует изменять код в скрытой области, так как возможно, что конструкторы, считывающие не сумеют интерпрети ровать ваши изменения, а значит, вам не удастся редактировать компонент с по мощью конструктора.

Преждевременно ли это предупреждение? Думаю, нет. Знаете ли вы заранее.

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

Особенности объекта При работе над этой книгой я упорно боролся за структуру глав Я хотел предоставить разработчикам подробное руководство по но при этом выдержать плавность повествования. Полагаю, сейчас вы уже понимаете роль объекта Connection в приложения для взаимодействия с БД и можете использо вать основные его функции. Следующий раздел познакомит вас с используемы ми несколько реже, но не менее важными от этого функциями объекта, Прочитайте этот раздел сейчас или оставьте на потом. В нем нет каких-то новых характеристик или неожиданных сведений, которые повлияют на ваше понима ние последующих без лишних предисловий я приступаю к описанию свойств и методов объекта OleDbConnection.

ГЛАВА 3 Подключение к базе данных Свойства объекта Единственное свойство класса OleDbConnection, значение которого можно изме нять, Ч Оно принимает строку подключения, с помощью кото рой объект OleDbConnection при вызове метода Open будет подключаться к источ нику данных. В табл. 3-1 перечислены наиболее часто используемые свойства объекта OleDbConnection.

Таблица 3-1. Наиболее часто используемые свойства объекта OleDbConnection Свойство Тип данных Описание String Определяет, как объект OleDbConnection будет под ключаться к источнику данных Задает (в в течение ко торого OleDbConnection пытается установить соеди нение с источником данных только для чтения) Database Возвращает имя БД. к которой вы подключаетесь/ подключены (доступно только для чтения) String Возвращает размещение БД, к которой вы подклю чаетесь/подключены (доступно только для чтения) Provider String Возвращает имя поставщика OLE DB, используемого объектом OleDbConnection для подключения к БД (доступно только для чтения) String Возвращает версию источника данных (доступно только для чтения) State Указывает текущее состояние объекта OleDbConnection (активен или уничтожен) (доступ но только для чтения) Свойство ConnectionString как объект OleDbConnection будет подключаться к источнику Значение ConnectionString можно только когда объект отсоединен от источника данных. Если объект подсоединен к источнику, свой ство доступно только для чтения.

Свойство ConnectionTimeout Задает интервал времени (в секундах), в течение которого поставщик OLE DB ожидает установки соединения с источником данных.

Это свойство доступно только для чтения, так как его поддерживают не все поставщики. Например, поставщик Microsoft OLE DB Provider for SQL Server дан нос свойство поддерживает, а поставщики Microsoft Jet 4.0 OLE DB Provider и Microsoft OLE DB Provider for Oracle Ч нет.

Так как же сообщить поставщику OLE DB требуемый интервал ожидания?

пользуйтесь атрибутом Connect Timeout строки подключения. Следующая строка подключения использует поставщика Microsoft OLE DB Provider for SQL Server и атрибут Connect Data Trusted Connect 68 Часть Подключаемся: использование поставщика данных Если в коде вы задали значение атрибута Timeout и используемый вами поставщик OLE DB не поддерживает эту возможность, при вызове метода Open объекта приложение сгенерирует исключение.

Свойства Database и Термины база и данных часто используют как взаимозаме няемые;

так поступаю и я в этой книге. Тем не менее объект Connection предос тавляет два отдельных свойства. Чем же они различаются?

Читая о строках подключения, вы, возможно, заметили, что в каждой из них имеется атрибут Data Source, за которым следует расположение нужной нам БД.

Аналогично этому свойство DataSource объекта Connection содержит расположе ние источника данных, указанного в строке подключения;

Если вы работаете с серверным хранилищем данных, например SQL Server или свойство Data Source вернет имя компьютера, выступающего в роли сервера. Для файловых БД, например Access, DataSource возвращает размещение файла данных.

Из обсуждения строк, использующих поставщика OLE DB SQL вы узна ли, как в атрибуте Data Source указать нужный экземпляр SQL Server. Если в вашей строке подключения указан экземпляр SQL Server, свойство DataSource возвратит его имя.

А что же возвращает свойство Оно предназначено для источников данных, поддерживающих несколько БД, например SQL Server. При изучении подключения к SQL Server мы указывали нужную нам БД с Initial Catalog.

Примечание Драйвер ODBC SQL Server обеспечивает функциональность с помощью атрибута Database. В случае с поставщиком OLE DB SQL Server атрибуты Database и Initial Catalog строки подключения взаимо заменяемы.

Свойство Provider Класс предоставляет свойство позволяющее поставщика OLE DB, указанного в строке подключения. Класс предоставляет аналогичное свойство, которое возвращает имя драйвера ODBC из строки подключения. Класс SqlConnection позволяет подключаться только к БД SQL Server, и поэтому ему не требуется свойство с такой функциональностью, Свойство В новых версиях большинства СУБД постепенно появляются новые функции. Так, SQL Server позволяет возвращать результаты запроса в XML-формате. Именно поэтому рекомендуется проверить свойство ServerVersion и что все передаваемые серверу вызовы допустимы. Свойство ServerVersion строку с БД, к которой вы Разработчикам, имеющим опыт работы с SQL Server, знаком запрос SELECT Свойство ServerVersion возвращает поднабор данных, возвращаемых этим запросом, а именно номер версии БД.

ваше приложение взаимодействует с различными БД SQL Server при помощи поставщика OLE DB, ODBC или SQL и некоторые запросы мо ГЛАВА 3 Подключение к базе данных гут выполняться только к БД SQL Server версии 2000 или более новой. В такой ситуации определить БД. к которым разрешено выполнять эти запросы, введя следующий код:

Visual Basic Dim As String = & Dim As New If >= "08" Then сюда End If Visual C# string strConn = + "Initial = new if >= "08") { сюда запрос Свойство State текущее состояние соединения как элемент перечисления State из пространства имен В табл. 3-2 перечислены чения и описания возможных состояний соединения.

Таблица 3-2. Константы, обозначающие состояние соединения Константа Значение Описание Broken 16 Соединение разорвано. Оно считается таковым, если было открыто и затем по какой-то причине (проблемы с сетью, перезагрузка сервера и т.д.) потеряло возможность действовать с источником данных. В начальной версии ADO.NET эта константа не используется 0 Соединение закрыто Connecting 2 Соединение устанавливается. В начальной версии ADO.NET эта константа не используется 4 Соединение выполняет запрос. В начальной версии ADO.NET эта константа не используется 8 Соединение занято выборкой данных. В начальной версии ADO.NET эта константа не используется Open 1 Соединение открыто Перечисление содержит ряд значений, не используемых в на чальной версии ADO.NET. В текущей версии данной объектной модели то State объекта Connection возвращает только Open или Closed. В следующих 70 Часть II Подключаемся: использование поставщика данных ях, возможно, будут поддерживаться комбинации этих значений, что соединение открыто и выполняет запрос.

Чтобы что значение свойства State изменилось, можно использовать событие StateCbange объекта Connection.

Методы объекта В таблице 3-3 перечислены методы объекта OleDbConnection. Методы, предостав ляемые большинством объектов Framework, например и опущены.

Таблица 3-3. Наиболее часто используемые методы объекта OleDbConnection Метод Описание При открытом соединении начинает При открытом соединении переключает вас на указанную БД Close Закрывает соединение Создает объект для текущего соединения Получает информацию схемы из источника данных Open Открывает соединение из соединений OLE Метод Для начала транзакции по открытому (это например, чтобы задать блокировку на данные или убедиться, что вы сможете подтвердить или откатить серию изменений в хранилище данных) вызовите метод объекта Connection. Он вернет объект Transaction (подробнее о нем Ч в разделе главы 10, посвященном обновлению БД), Примечание Разработчики, использовавшие объекты соединения в ADO, RDO и вероятно, ожидают, что у объекта Connection имеются методы для и отката транзакции. В объектной модели ме тод генерирует новый объект Transaction. Чтобы подтвер дить или откатить транзакцию, вызовите соответственно методы Commit и Rollback этого объекта.

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

Visual Basic Dim txn As = Dim txn As New = 3 Подключение к базе данных Visual C# txn = Ч или Ч OleDbTransaction txn = new = Метод Как уже говорилось на одном сервере SQL Server может размещаться не сколько БД. Чтобы при работе с SQL Server изменить текущую БД, выполните за прос, аналогичный следующему:

USE ADO.NET также предоставляет более изящный способ смены БД. Метод объекта Connection упрощает данный процесс. Следующие фрагменты кода эквивалентны:

Visual Basic Dim As New Ч или Dim As New Dim cmd As OleDbCommand = = "USE Northwind" C# = new OleDbConnection(strConn);

Ч или OleDbConnection = new OleDbConnection(strConn);

OleDbCommand cmd = = "USE Northwind";

72 Часть И Подключаемся: использование поставщика данных Метод Close Для закрытия соединения вызывают метод объекта Connection. Помните: при использовании пула соединений подключение к источнику данных просто помещается в пул.

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

Метод Позволяет создавать новые объекты Command. Он не принимает аргументов и возвращает новый объект Command, свойству Connection которого задан объект Connection, создавший данный объект Command.

Следующие фрагменты кода эквивалентны:

Basic As String = Data & "Initial Dim As New Dim As = Ч или Dim strConn As String = & Dim As New Dim cmd As New = C# string strConn = + "Initial = new QleDbConnection(strConn);

cmd = Ч или Ч string strConn = + "Initial OleDbConnection = new OleDbCommand cmd = new = Метод Позволяет получить информацию схемы о БД. Чтобы указать нужный тип мации схемы (таблицы, столбцы, хранимые процедуры и т.д.), передайте методу GetOleDbSchemaTable значение из перечисления Метод также принимает обязательный параметр Restrictions, выступающий в качестве фильтра для возвращаемых сведений схемы. Например, это позволит получать информацию не обо всех столбцах БД, а только о столб ГЛАВА 3 Подключение к базе данных цах конкретной таблицы. Параметр Restrictions содержит массив значений. Каж дый тип схемы допускает использование разных наборов ограничений.

Чтобы получить информацию обо всех столбцах всех таблиц вашей БД, опус тите параметр Restrictions:

Visual Basic Dim As & "Initial Dim As New Dim As DataTable = Nothing) Visual C# string strConn = new DataTable tbl;

tbl = null);

Если вы хотите получить столбцы конкретной таблицы, воспользуйтесь парамет ром Restrictions и укажите имя нужной таблицы. В документации говорит что структура массива Restrictions для элемента Table перечисления должна быть такой:

Таким следующий код возвращает список столбцов таблицы Customers:

Visual Basic Dim strConn As String = & _ "Initial Dim As New cn.OpenO Dim As = New ObjectO Nothing, "Customers", Dim tbl As DataTable tbl = objRestrictions) Visual C# string strConn = Data "Initial = new string objRestrictions;

objRestrictions new object[] null, "Customers", 74 Часть II использование поставщика данных DataTable tbl;

= Подробнее о составляющих для конкретных значений Ч в документации Метод GetOleDbSchemaTable возвращает объект DataTable (подробнее о нем в главе б), содержащий нужную вам информацию схемы. Структура возвращен ного методом объекта DataTable зависит от типа запрошенной вами схемы. Сле дующий код просматривает записи таблицы со списком полей, возвращенной методом Visual Basic tbl = in Customers Each In & Next row Visual C# tbl = in Customers row in + Можно создать простое приложение, которое будет с помощью метода GetOleDb SchemaTable выводить информацию схемы о вашей БД (таблицы, хранимые процедуры и т.д.) почти так же, как это делает Server Explorer.

Метод GetOleDbSchemaTable основывается на поставщика OLE DB, используемого вашим объектом He все поставщики пре доставляют все методы для работы со схемой. Если вы запросите не под держиваемую вашим поставщиком OLE DB, приложение сгенерирует перехваты ваемое исключение.

Метод Орел Для установки соединения с БД вызывают метод Open объекта Connection. Объект пытается подключиться к БД, используя значение своего свойства Если соединение установить не объект Connection генерирует исключение.

Visual Basic Dim strConn As String & "Initial Dim As New Try Catch ex As Exception to connect & & End Try ГЛАВА 3 Подключение к базе данных Visual string = "Initial = new { } catch (Exception ex) { to connect + Если вызвать метод Open открытого объекта Connection, соединение будет за крыто и повторно открыто. При использовании пула в этом случае, будет создано дополнительное соединение с источником данных, Когда начальное соединение оно помещается в пул. Однако поскольку пул соединений обрабатывается отдельным в момент, когда объект Connection запросит соединение с данных, основываясь на строке подключения, начального соединения может не оказаться в пуле.

Метод Позволяет управлять пулом соединений OLE из компонентов.

вызвать метод ReleaseObjectPool для объекта Connection или непосредственно для класса соединение будет удалено из пула.

По правде сказать, этот метод используется редко. В бета-версии Visual Studio большинство разработчиков применяли ReleaseObjectPool, что бы действительно закрыть физическое соединение с источником данных, а не помещать его в пул. В таком случае лучше сразу создать соединение, которое не будет помещаться в пул. Для этого вставьте в строку подключения следующий фрагмент:

OLE DB Servlces=-4;

В результате после вызова метода Close класса OleDbConnection соединение с источником данных закрывается, а не помещается в пул.

События объекта OleDbConnection Объект OleDbConnection предоставляет два события Ч InfoMessage и (табл. 3-4).

Таблица 3-4. События объекта Событие Описание InfoMessage когда объект Connection получает от источника данных сообщение Наступает при изменении свойства State объекта OleDbConnection 76 Часть Подключаемся: поставщика данных Событие Некоторые СУБД, например SQL Server, поддерживают сообще ния. SQL Server позволяет передавать сообщения клиентам с помощью команды PRINT. Такие сообщения не являются сообщениями об ошибках и не включают результаты запросов.

Событие InfoMessage объекта Connection позволяет перехватывать информаци онные сообщения, как показано ниже.

Visual Basic Dim As String = & "Initial Dim New With = "PRINT End With Public Sub sender As Object, e As event & received: & End Sub Visual C# string strConn = Data + "Initial = new += new cn.OpenO;

= = "PRINT cmd.

static void sender, e) < event received:

;

Примечание SQL Server также способен генерировать информационные сооб щения с помощью команды Информационными считаются сообщения, степень значимости которых меньше 10. Подробнее Ч в справочной системе SQL Server Books Online.

ГЛАВА 3 Подключение к базе данных 7/ Фрагменты на Basic использующие события В Basic предусмотрено два способа кода для обра ботки предоставляемых каким-либо объектом. который я и применяю в книге, Ч оператора Кроме того, можно добавить код для обработки событий, требующий ввода меньшего объема На 3-13 показан кода Basic. Имеется переменная область которой Ч уровень модуля. Оператор содержит клю чевое Объявив переменную с помощью слова, вы без труда, средствами Visual Basic создадите процедуры для событий объекта.

| sub - Рис. 3-13. кода для обработки событий в Visual Basic Над кодом располагаются два раскрывающихся списка. В левом списке перечислены модуль кода и все объектные переменные из области дей ствия модуля, которые предоставляют события. Как видно, в данном списке выбран объект Если выбрать в левом списке перемен предоставляющую в правом списке появится перечень этих событий.

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

см. след. стр.

78 Часть II Подключаемся: использование поставщика данных уровне As As * & Public sender AS _ e As & & & "To Sub Событие если изменяется значение свойства State объекта Connection. Это со бытие окажется вам полезно, если вы отображаете текущее состояние соедине ния, скажем, в информационной панели приложения.

Visual Basic Dim strConn As String = & "Initial Dim As New Public Sub sender As Object, e As event occurred") & "From & & "To & e.CurrentState.ToString) End Sub Visual C# string strConn = + "Initial = += new cn.CloseO;

static void cn_StateChange(object sender, StateChangeEventArgs e) { event ГЛАВА 3 Подключение к базе данных " & Вопросы, которые стоит задавать почаще Вопрос. Добавив соединение в Server Explorer, я могу завершить и Visual Studio будет помнить мной параметры.

Где хранит эти параметры Server Explorer?

Ответ. Server Explorer хранит параметры отдельных пользователей. Если на од ном с Visual Studio работают несколько разработчиков, их па раметры Server Explorer храниться отдельно, в файлах с именем (Default Этот файл находится в одной из вложенных папок каталога Settings пользователя. Таким если файловая система диска отличается от пароли доступны любому который сядет за компьютер.

Вопрос. Я разрабатываю приложение, использующее SQL Server качестве сер верной БД. Какую проверку подлинности мне следует задействовать Ч стандарт ную или интегрированную?

Ответ. Это сложный вопрос. Решение зависит от архитектуры вашего приложе ния и как вы управляете безопасностью БД SQL Server. В двухуровневых при ложениях я предпочитаю использовать а в многоуровневых Ч стандартную проверку подлинности, При использовании интегрированной проверки подлинности вам не требуется предлагать пользователю ввести имя и пароль или жестко задавать эти реквизи ты в приложении. При использовании в многоуровневых (где код доступа к данным выполняется в виде Web-сервиса или компонента стан дартной проверки подлинности вы сможете задействовать пул соединений. В та кой архитектуре компонент определяет пользователя. Это похоже на уход от ответственности, и именно так иногда обстоят дела. Тем не менее я счи таю данное решение полностью пригодным.

А что, если единственная точка проверки реквизитов пользователя компонен том промежуточного уровня Ч код доступа к данным? Если БД сообщает, что у пользователя нет доступа к ней, это обычно означает, что пользователю вообще не требовалось обращаться к компоненту промежуточного В таком слу чае вы зря тратите время и компрометируете систему безопасности, позволяя пользователю выполнять в компоненте код до подключения к БД.

Рекомендую настроить систему безопасности так, чтобы пользователь предо ставлял реквизиты для доступа к компоненту. Затем компонент будет использо вать стандартную строку подключения. Поскольку все клиенты работают с оди наковой строкой, компонент сможет поместить их соединения в один пул. Если все клиенты подключаются к БД под разными реквизитами, использовать пул не стоит.

Подробнее о стандартной и интегрированной проверке подлинности Ч в SQL Server Books Online.

80 Часть II Подключаемся: использование поставщика данных Вопрос. В целях безопасности я ограничил доступ используемого мной в при ложении объекта Connection к БД. Тем не менее мне нужны некоторые админист ративные функции Server Explorer, например возможность и изменять структуру моих таблиц. Как сбалансировать безопасность объекта Connection, используемого мной в приложении в период выполнения и необходимую мне Б период разработки функциональность?

Ответ. Используйте в Server Explorer несколько соединений. Можно просто до бавить новое соединение, которое использует учетную запись с расширенными привилегиями доступа к БД. Если соединение в вашем приложении фактически не применяется, оно не будет включено в него.

Выполнение запросов к базе данных О предыдущей главе рассказывалось о подключении к БД с использованием объек та Connection Microsoft ADO.NET. Здесь обсуждается выполнение к БД при помощи объекта Command модели ADO.NET.

Основное внимание уделено конкретным задачам, связанным с использованием объекта Также я о двух других объектах ADO.NET Ч DataReader и Parameter. Первый из них позволяет просматривать результаты запросов, а вто рой Ч выполнять запросы. Затем я покажу, как быстро и просто создавать объекты Command средствами среды разработки Microsoft Visual Studio Кроме того, мы рассмотрим свойства, методы и события объектов Command, DataReader и Parameter.

Использование объектов Command в коде Объекты Command позволяют выполнять разнообразные запросы. Некоторые из этих объектов возвращают данные в форме набора результатов, а другие ют структуру хранилища данных. Сейчас вы узнаете, как создавать объекты Com mand и выполнять с их помощью нужные вам задачи.

Создание объекта Command Известны три способа создания объекта Command. Первый Ч когда просто со здается новый экземпляр объекта при помощи ключевого слова New и затем за даются необходимые свойства. Второй Ч применить один из имеющихся конст рукторов и затем указать строку запроса и объект Connection. Третий Ч вызывать метод объекта Connection (подробнее об этом в главе 3):

82 Часть Подключаемся: использование поставщика данных Visual Basic Dim As String = & _ "Initial strSQL = "SELECT FROM Customers" Dim As New Dim As OleDbCommand = = strSQL cmd = New = strSQL cmd = New Visual C# string strConn, strSQL;

strConn = + "Initial strSQL = "SELECT CustomerlD, FROM Customers";

= new cn.OpenO;

OleDbCommand cmd;

cmd = cn.CreateCommandO;

strSQL;

cmd = new OleDbCommandO;

= strSQL;

= cmd = new Выполнение запроса, не возвращающего записей Запрос, не набор результатов, обычно называется командным (action query). Этот термин я буду использовать в тексте. Основных видов командных запросов два:

Х (Data Manipulation Language, язык управления данными).

Также называются обновлениями под запросов (query-based updates.

они изменяют содержимое БД. Вот пара примеров SET CompanyName = CustomerlD INSERT INTO Customers (CustomerlD, CompanyName) ГЛАВА 4 Выполнение запросов к базе данных VALUES DELETE FROM Customers CustomerlD = Х DDL-запросы (Data Definition язык определения данных). Эти запросы изменяют структуру БД:

TABLE int NOT NULL CONSTRAINT KEY, Field ALTER VIEW AS SELECT Fieldl, Field2 Tablel DROP PROCEDURE Для выполнения командного запроса создайте объект задайте его свойству Connection активное подключение, CommandText Ч нужную строку запроса и вызовите метод Visual Basic Dim As New = & "Initial Dim As = = "UPDATE Customers SET CompanyName = & WHERE CustomerlD = Visual C# = new OleDbConnectionO;

Data + "Initial cmd = = "UPDATE Customers SET CompanyName = + "WHERE CustomerlD = Примечание Несмотря на свое имя, метод ExecuteNonQuery выполняет полно ценные единственная которых в том, что они не возвращают записей. Сказать точно, почему метод назвали именно я не могу (я просто тут работаю).

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

CREATE TABLE int NOT NULL CONSTRAINT KEY, OtherField 84 Часть II использование поставщика данных Этот запрос либо успешно новую таблицу, либо возвращает ошибку из за существования одноименной неверного синтаксиса запроса или от открытого соединения с БД. Смысл в том, если запрос выполнился успешно и не вернул будет успешно создана новая таблица.

В случае с командными запросами, изменяющими или удаляющими существу ющую запись, одного только успешного выполнения недостаточно. Еще раз рас смотрим запрос из фрагмента кода, который изменяет название компании ретного клиента:

UPDATE Customers SET = WHERE = Х В некоторых случаях запрос не изменит название компании клиента Ч напри мер, если нужная запись удалена из таблицы. Запрос выполнится, но, поскольку ни одна запись БД не удовлетворяет критерию из раздела WHERE, запрос ничего не изменит. С точки зрения БД такой результат не ошибку.

Так как же узнать, изменил ли запрос одну запись? Если вы выполняли запрос с помощью утилиты типа Microsoft SQL Server Query Analyzer, появится сообще ние, аналогичное следующему:

(1 affected) Помните: если командный запрос не изменил какие-либо записи БД из-за того, что записи, удовлетворяющие критериям раздела WHERE, запрос не возвращает ошибку.

Объект Command сообщает о результатах выполнения запроса при помощи возвращаемого значения метода которое содержит число обра ботанных записей:

Visual Basic Dim As = & _ "Initial Dim As New Dim cmd = = "UPDATE Customers SET CompanyName = & = Dim As Integer = If intRecordsAffected = 1 Then Else intRecordsAffected = failed") End If Visual C# string strConn;

strConn = + ГЛАВА 4 Выполнение запросов к базе данных "Initial = new = = Customers SET = + "WHERE = = if (intRecordsAffected == 1) else intRecordsAffected = В коде предполагается, что, если запрос не изменил одну запись, он вообще не ничего не менял. Тем не менее метод может возвращать и другие значения. Если выполнить не а запрос другого типа, ExecuteNonQuery вернет -1. Кроме того, в некоторых случаях DML-запрос изменяет несколько за писей.

Как бы то ни было, в разделе WHERE данного фрагмента кода используется поле первичного ключа таблицы. Поскольку поле основного ключа таблицы Customer Ч CustomerlD, наличие двух записей с одинаковым поля не возможно. Следовательно, запросу не удастся изменить больше одной записи.

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

Объект DataReader аналогичен другим считывающим объектам Framework, таким, как TextReader и StreamReader. Все это Ч эффективные и про стые в использовании объекты, позволяющие просматривать (но не изменять) предоставляемые соответствующим объектом данные. Например, методы объек та позволяют построчно считывать содержимое текстового файла, а свойства и методы объекта DataReader Ч просматривать результаты запроса.

Разработчикам, ADO или низкоуровневые API-интерфей сы ODBC и OLE DB, знаком термин пожарный курсор (firehose cursor). Это меха низм, средствами которого БД максимально быстро возвращают результаты за проса. Пожарные курсоры отказываются от функциональности в пользу произ водительности. После того как вы считаете один ряд из набора результатов и пе рейдете к следующему, предыдущий ряд станет недоступен. Результаты поступа ют к вам быстро и интенсивно, как вода из пожарного рукава, Ч отсюда и назва ние курсора. Объект DataReader ADO.NET позволяет обращаться к пожарному курсору напрямую. Чтобы создать объект DataReader, вызовите метод ExecuteReader объекта Command.

86 Часть II Подключаемся: использование поставщика данных Выборка результатов Вот как с помощью объекта DataReader просмотреть результаты простого запроса:

Visual Basic Dim As String strConn = _ "Initial Dim As New strSQL = "SELECT FROM Customers" cmd As New Dim As OleDbDataReader = While & - & End While Visual C# string strConn, strConn = + "Initial OleDbConnection = OleDbConnection(strConn);

strSQL = "SELECT CustomerlD, FROM Customers";

cmd = new OleDbDataReader = cmd. ExecuteReaderO;

while (rdr.ReadO) + - + Заметьте: перед считыванием первой записи набора результатов код вызывает метод поскольку сразу после вызова ExecuteReader эта запись недоступна.

Такое отличает от предыдущих объектных моделей, напри мер ADO. Объект возвращаемый объектом не предостав ляет первой записи данных до вызова метода Read, При первом метода Read объект DataReader переходит к первой за писи набора а при последующих вызовах Ч к следующим записям, Метод Read возвращает логическое значение, указывающее, имеется ли в объекте DataReader следующая запись. Так, если метод вернул True, DataReader перешел к следующей доступной записи;

если False Ч объект достиг конца на бора результатов.

Ускоренная выборка Объект DataReader предоставляет свойство Item. В преды дущем фрагменте кода свойство Item использовалось для доступа к значе ниям столбцов CustomerlD и CompanyName набора результатов. Тем не менее та кой код неэффективен. Поэтому разработчики придумали целых два способа повысить его производительность.

ГЛАВА 4 Выполнение запросов к базе Поиск по порядковым номерам В приведенном ранее фрагменте кода указано имя столбца. Чтобы вернуть соот ветствующее значение, объект DataReader должен найти этот столбец во ней структуре набора результатов, основываясь на переданной вами строке. По мните: в данном случае мы просим объект DataReader выполнить поиск по заданной строке во всех записях набора результатов. Если указать индекс или порядковый номер столбца, производительность кода повысится.

Данный метод программирования применим практически ко всем объектам, наборы. Я рекомендовал его многим разработчикам, исполь зовавшим ADO в своем коде и искавшим способы повышения производительно сти. Большинство из них согласились, что производительность возросла, однако некоторые опасались, что их приложения станут менее гибкими.

Порядок столбцов в наборе результатов меняется, только если вы меняете строку запроса или структуру объекта БД (таблицы, представления или хранимой про цедуры) и выбираете все возвращаемые этим объектом столбцы. В большинстве приложений можно без каких-либо проблем жестко задать порядковые номера всех столбцов.

Тем не менее в некоторых ситуациях вам известно только имя столбца, но не его порядковый номер. DataReader предлагает изящный способ определить по рядковый номер столбца по имени последнего. Метод принимает строку, представляющую имя столбца, и возвращает целое значение, порядковому номеру столбца. Этот метод Ч весьма приятное усовершенствова ние объектной модели ADO.NET;

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

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

Visual Basic Dim rdr As = Dim As Integer = Dim As Integer = While End While Visual C# OleDbDataReader = 88 Часть II Подключаемся: использование поставщика данных int intCustomerlDOrdinal = int = + - + Но мы еще не закончили...

Использование подходящего, уникального для конкретного типа метода Get Объект также предоставляет ряд методов, возвращающих значения с различными типами данных Framework (string, double и т.д.). Сейчас в нашем фрагменте кода неявно используется свойство Item, возвращающее содер жимое указанного столбца с универсальным типом данных Object. Для вывода данных, хранящихся в окне консоли, консоль должна преобразовать значение с типом Object в строковое представление. Поскольку поля и Customer Name содержат строковые данные, можно воспользоваться методом объек та DataReader и вернуть содержимое столбцов в виде строки:

Visual Basic Dim As OleDbDataReader = Dim intCustomerlDOrdinal As Integer = Dim intCompanyNameOrdinal As Integer = While End While rdr.CloseO Visual C# OleDbDataReader rdr = int intCustomerlDOrdinal = int = while (rdr.ReadO) + - + Всегда применяйте уникальный для типа метод Get, который соответствует данным, возвращаемым столбцом из набора результатов. нам нужно выб рать из таблицы Order Details БД значения столбцов и и вывести результаты в виде списка. Несмотря на то, что в списке данные отобра жаются в строковом для выборки содержимого каждого столбца следует воспользоваться методом Получив данные с типом мы можем преобразовать их в строковое и вывести в списке.

ГЛАВА 4 Выполнение запросов к базе данных Выборка нескольких наборов результатов Некоторые БД, например SQL Server, позволяют выполнять пакет запросов, воз вращающий несколько наборов результатов. Предположим, нам нужно выполнить к БД следующий запрос:

SELECT CompanyName, Phone Customers;

SELECT FROM Orders;

SELECT OrderlD, Quantity, FROM [Order Details] Предыдущие фрагменты кода, объект циклично просматривали результаты запроса до тех пор, пока метод Read не возвращал False.

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

Объект DataReader предоставляет метод позволяющий перейти к результатам следующего запроса, который возвращает записи. Этот метод анало гичен методу Read в том, что возвращает логическое значение, указывающее, есть ли еще результаты. Тем не менее в отличие от метода Read, предварительно вы зывать метод не требуется.

Если метод Read возвращает False, наличие дополнительных результатов, ожи дающих выборки, можно проверить средствами метода NextResult. Если и он вер нет False, наборов результатов больше нет. Следующий фрагмент кода выбирает результаты пакетного запроса при помощи метода NextResult.

Visual Basic Dim As String = "SELECT CustomerlD, CompanyName FROM & "SELECT OrderlD, CustomerlD FROM & _ "SELECT OrderlD, ProductID [Order Dim As New Dim rdr As = Do Do While & - Loop Loop While Visual C# string StrSQL = "SELECT CustomerlD, CompanyName + "SELECT OrderlD, CustomerlD FROM + "SELECT OrderlD, ProductID FROM [Order cmd = new OleDbDataReader rdr = do 90 Часть И Подключаемся: использование поставщика данных while + - + } while Выполнение пакетов командных запросов Пытаясь с помощью ADO и SQL Server получить набор результатов, генерируемых хранимой процедурой, разработчики часто сталкиваются с одной проблемой. Если вы при помощи OLD DB-поставщика SQL Server вызвали хранимую процедуру SQL Server, которая перед запросом, возвращающим записи, выполняет командный запрос, ваш объект Recordset помечается как закрытый и он не содержит резуль таты запроса, возвращающего записи.

Эта модель поведения обусловлена архитектурой. Закрытый объект Recordset соответствует командному запросу. Если он соответствует информацион ному сообщению row(s) возвращаемому таким запросом. Для пере хода к результатам следующего запроса следует вызвать метод Кро ме того, можно добавить в хранимую процедуру оператор SET NOCOUNT ON, подавляющий указанные сообщения;

благодаря этому ADO сразу же переходит к результатам первого запроса, возвращающего записи.

Подобная модель поведения не отличительной чертой только храни мых процедур. Похожая ситуация возникает и при выполнении пакетных запро сов. Рассмотрим пакетный запрос, использующий ADO и Visual Basic:

Visual Basic Dim As rs As Dim strConn As String, As String Dim As Integer strConn = & _ "Initial Set = New strConn strSQL = "INSERT INTO Customers & _ "SELECT Customers WHERE "UPDATE Customers SET CompanyName = WHERE & "SELECT CustomerlD, CompanyName FROM Customers WHERE Set intRecordsAffected, adCmdText) Do Until rs Is Nothing = & & vbTab & "intRecordsAffected = & intRecordsAffected Set = Loop ГЛАВА 4 Выполнение запросов к базе данных Первоначально закрыт, и переменная содер жит значение 1. Такие результаты запросу INSERT, который не воз вращает записей и изменяет одну запись БД. После того как мы вызовем метод NextRecordset, объект Recordset становится открытым и содержит результаты пер вого запроса SELECT. Поскольку запрос SELECT не изменяет каких-либо записей БД, переменная intRecordsAffected вернет -1. При повторном вызове Recordset вернет закрытый объект а значение переменной intRecordsAffec ted соответствует числу записей, затронутых запросом UPDATE. При следующем вызове метод NextRecordset возвращает результаты второго запроса SELECT и дает переменной intRecordsAffected значение -1. При последнем вызове NextRecordset возвращает объект установленный в сообщая, что наборов ре зультатов, ожидающих обработки, больше нет.

обрабатывает этот же пакетный запрос иначе. Команда разработчи ков ADO.NET что данная ситуация Ч одна из главных причин простра ции разработчиков при использовании ADO. Они упростили процесс работы с пакетными запросами так, что теперь объект DataReader автоматически переме щает вас к результатам первого запроса, возвращающего записи. Полагаю, боль шинство разработчиков обрадуются такому изменению модели поведения. К со жалению, при этом пришлось частью функциональности.

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

Visual Basic Dim strConn, As String = & "Initial Dim As New strSQL = "INSERT INTO Customers & _ "SELECT Customers WHERE & _ "UPDATE Customers SET CompanyName = WHERE & "SELECT CustomerlD, CompanyName FROM Customers WHERE..."

Dim As New Dim rdr As = Do = & Do While & & - & Loop Loop While Visual C# string strConn, strSQL;

strConn + 92 Часть II Подключаемся: использование поставщика данных "Initial = = "INSERT INTO Customers + "SELECT FROM Customers WHERE + "UPDATE Customers SET CompanyName = + "SELECT CustomerlD, CompanyName Customers WHERE = new = do { = + while + - + } while Вы, возможно, заметили, что код похож на код ADO. Тем не менее результаты выполнения кода несколько отличаются. Когда мы создаем объект DataReader, вызывая метод объекта Command, готов немедленно результаты первого запроса SELECT. Вызывая мы переходим к результатам второго запроса SELECT. При повторном вызове метод возвращает поскольку запросов, возвращающих записи и ожида ющих больше нет;

как следствие, мы выходим из цикла.

Еще одно значительное изменение по сравнению с ADO Ч поведение свой ства RecordsAffected объекта Предположим, что запросы INSERT и UPDATE изменяют каждый по одной записи БД. Свойство RecordsAffected возвра щает суммарное число записей, затронутых всеми командными запросами, пред шествующими который возвращает записи и результаты которого в дан момент выбирает объект DataReader.

когда метод ExecuteReader возвращает объект DataReader, свойство Records последнего возвращает При последующем вызове метода это свойство возвращает 2.

Помните, что не-DML командные запросы (например, CREATE PROCEDURE и TABLE) не изменяют записей и поэтому возвращают число затронутых за писей как -1.

Если вам нужно при помощи ADO.NET определить число записей, затрагивае мых отдельными запросами, разделите пакет на отдельные запросы и выполняй те их Закрытие объекта DataReader В объектной модели ADO.NET жизненно важно как можно быстрее закрывать объекты DataReader. На момент написания книги объект Connection с открытым объектом DataReader считался заблокированным, Если вы попытаетесь открыть второй объект DataReader, не закрыв система сгенерирует исключение, текст которого сообщает, что операции требуется открытое и доступное соеди ГЛАВА 4 Выполнение запросов к базе данных Это ограничение, возможно, разработчиков, имеющих опыт работы с но не тех. кто использовал RDO. Различные технологии доступа к данным Microsoft по-разному подходили к этой ситуации.

Если вы попытаетесь открыть в БД SQL Server два пожарных курсора с зованием ADO, все будет работать нормально и система не выдаст никакой ошибки.

Это обусловлено спецификой OLE DB: если текущее соединение заблокировано, OLE выполняет действие по новому соединению.

Думаю, разработчикам, имевшим дело с ADO, знакомо сообщение об ошибке is busy with results from another ODBC не выполняет какой либо закулисной работы, чтобы вам разрешить возникшую ситуацию. Если вы попытаетесь воспользоваться занятым соединением, то просто получите со общение об ошибке.

Какой из этих подходов ошибки или выполнение нужного действия по новому соединению) лучше? Разработчики как из Microsoft, так и из других компаний не могут прийти к единому мнению. Фактически, каждая новая технология доступа к данным Microsoft предлагает свой способ разрешения этой ситуации: генерирует ошибку, DAO/Jet открывает новое соединение, RDO генерирует ошибку, ADO открывает новое соединение и генерирует ошибку. Как говорят в Новой Англии: погода вам не нравится, немного по Примечание По-моему, появилась DAO/Jet. но радиоуглеродный анализ в этом случае выполнить весьма трудно, и его результаты не бу дут на 100% точными.

Назначение объекта DataReader Ч обеспечить высокую производительность.

Независимо от что открытый DataReader блокирует объект Connection, вам следует как можно быстрее получать результаты выполняемого запроса. Если не обходимо перемещаться взад-вперед между результатами разных исполь зуйте объект или попробуйте хранить результаты запросов в каком-ни будь бизнес-объекте.

Выполнение запроса, возвращающего одно значение Предположим, вы хотите выполнить запрос и получить одну (одну запись, одно поле) данных. Вот два примера запросов, возвращающих одно значение:

SELECT FROM Customers SELECT Customers = Использовать в этом случае объекты DataReader или DataSet Ч то же что стрелять из пушки по воробьям. У объекта Command есть метод предназначенный специально для таких запросов. Он возвращает значение, при меняя универсальный тип данных Object, который можно преобразовать в любой нужный вам тип:

94 Часть II использование поставщика данных Visual Basic Dim As String = & "Initial Dim As New Dim As = = "SELECT COUNT(*> FROM Customers" Dim As Integer = = "SELECT FROM Customers & "WHERE = Dim As String = Visual C# string strConn = + "Initial = new OleDbCommand cmd = = "SELECT FROM Customers";

int intCustomers = = "SELECT Customers = string strCompanyName = Метод Ч хороший пример функции, предоставляющей отличную замену коду, который вы, возможно, даже и не считали неэффективным.

Выполнение параметризованного запроса Предположим, вы разрабатываете приложение, позволяющее просматривать список заказов, размещенных вашими клиентами. Если у вас 20 клиентов, не пишите отдельных функций, возвращающих заказы каждого конкретного а со здайте параметризованную функцию, которая принимает сведения о клиенте и возвращает список его заказов. Точно так же можно создавать запросы, принима ющие параметры. Рассмотрим запрос, который разрешается применять в парамет ризованной функции для возврата списка заказов клиента:

SELECT OrderDate FROM Orders WHERE CustomerlD = ?

Знак вопроса представляет собой маркер параметра Ч стандартный способ обо значения параметра в запросе.

Примечание Поставщик данных SQL Server не поддерживает универсаль ный маркер параметра ? и требует применения именованных парамет ры с префиксом @. В следующем запросе ID является имено ванным параметром:

ГЛАВА 4 Выполнение запросов к базе данных SELECT OrderDate Orders WHERE CustomerlD = Подробнее о выполнении параметризованных запросов с использо ванием поставщика данных SQL Server Ч в приложении А.

Изменяя значение параметра, мы средствами этого запроса получим список за казов любого клиента. Для хранения информации параметра необходим объект Parameter модели Кроме того, у объекта имеется набор Para meters. Следующий фрагмент кода создает объект Command, добавляет в его на бор Parameters объект задает значение последнего и выполняет запрос, возвращающий список заказов конкретного клиента:

Basic Dim strSQL As String = & "Initial Dim As New strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, OrderDate "FROM Orders WHERE CustomerlD = Dim As New 5) = Dim rdr As OleDbDataReader = Visual C# string strConn, strSQL;

StrConn = + "Initial = new strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, OrderDate + Orders WHERE CustomerlD = cmd = new cmd. 5);

= "ALFKI";

OleDbDataReader = Выполнив запрос с помощью метода вы получите его ты, применив объект точно так же, как и в случае с обычными запро сами, не использующими параметров.

запросы значительно упрощают программирование.

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

SELECT EmployeelD, FirstName FROM Employees WHERE LastName = 96 Часть II Подключаемся: использование поставщика данных Поскольку искомое литеральное значение необходимо заключить в кавычки, вам потребуется заменить одинарные кавычки в этом значении двумя последова тельными одинарными кавычками. Это могут подтвердить и вставить в одну из строк своего кода двойные кавычки.

Вызов хранимой процедуры Предположим, у нас есть хранимая процедура, возвращающая ряд данных. Вот пример такой процедуры SQL Server:

PROCEDURE AS SELECT CompanyName, FROM WHERE CustomerlD = RETURN Примечание Некоторые БД, например Oracle, не могут таким способом воз вращать набор результатов из вызова хранимой процедуры. Подроб о выборке результатов из хранимых процедур Oracle при помощи ADO.NET Ч в Базе знаний Microsoft.

Как же вызвать эту процедуру из объекта Один из способов Ч вос пользоваться свойством CommandType данного объекта. Ему можно задать любое значение из перечисления Text, или StoredProcedure, Значение свойства CommandType по умолчанию Ч Text. свойству Command Type значение вы сообщаете объекту что вызываете хранимую процедуру Объект Command совместит значение свойства CommandType с информацией набора Parameters и сгенерирует синтаксис вызова хранимой процедуры:

Visual Basic As Dim cmd As = With = "GetCustomer" = 5) End With Dim rdr As OleDbDataReader = If Then Else customer found") End If ГЛАВА 4 Выполнение запросов к базе данных Visual C# = new = = = cmd.

= = if else customer Стандартный способ вызвать хранимую процедуру Ч воспользоваться ющим синтаксисом:

{? = CALL ?, ?)} Первый маркер параметра представляет значение, возвращаемое вызовом цедуры, и, если вы это значение использовать не собираетесь, данный маркер можно опустить. При этом синтаксис нашего запроса станет таким:

Я предпочитаю использовать именно такой синтаксис, а не полагаться на ство При запросе к таблице, представлению или хранимой проце дуре, в имени которой имеются нестандартные символы, например объекта заключить в кавычки. Если вы используете объект OleDbCommand и задали свойству CommandType значение или в занном случае имя объекта не заключается автоматически в кавычки Ч их при дется добавлять самостоятельно. Разные объекты Command ведут себя в такой ситуации по-разному. Например, объект корректно разделяет параметров. Конечно, если в ваших таблиц и хранимых процедур пробелов, разделение имен параметров не станет для вас проблемой Рекомендую вам перестать полагаться на свойство CommandType и использо вать в качестве значения свойства CommandText корректный синтаксис вашего запроса:

Basic Dim cmd As New = = 98 Часть II Подключаемся: поставщика данных Visual C# cmd = new = = Разработчики, имеющие опыт работы с SQL Server, возможно, привыкли исполь зовать синтаксис EXEC для вызова хранимых процедур в утилитах типа Query Analyzer. Этот синтаксис допустим и в объектах Command, но помните, что он поддерживается не БД. Для выполнения запроса к БД другого типа вам, ве роятно, потребуется слегка изменить синтаксис запроса.

Получение данных при помощи параметров вывода Не все хранимые процедуры возвращают информацию при помощи набора ре зультатов. Многие из них возвращают данные посредством параметров вывода.

Скажем, наша хранимая процедура SQL Server выглядит так CREATE PROCEDURE GetCustomer nchar{5), nvarchar(40) OUTPUT, OUTPUT) AS SELECT = = ContactName, = ContactTitle Customers WHERE = IF = RETURN ELSE RETURN - Как при помощи объекта Command получить данные из параметров вывода? У объекта Parameter есть свойство Direction, принимающее значения из перечисле ния и Output. Значение свойства Direction по умолчанию Ч Input. Чтобы получить от хранимой процедуры GetCus tomer следует задать значение свойства Direction для параметров, которых не ограничивается только вводом.

Хранимая процедура сообщает об успешности выполнения при помощи воз вращаемого параметра. Таким образом, наш фрагмент кода должен просмотреть значение возвращаемого параметра и определить, успешно ли найден в нам клиент:

Visual Dim New Dim cmd As = With cmd = = CALL ?, ?, ГЛАВА 4 Выполнение запросов к базе данных 5) 40) 30) 30) = = = = =, If = 0 Then Value) Else not found") End If End With Visual C# = = = = CALL ?, ?, cmd. Pa 30);

= = = "ALFKI";

= cmd, ExecuteNonQueryO;

if == 0) not Выполнение запроса в транзакции У объекта Command есть свойство Transaction, значение которого следует задать, если необходимо, чтобы команда выполнялась в транзакции. Из предыдущей главы вы знаете, как создать объект Transaction с помощью метода 100 Часть II Подключаемся: использование поставщика данных Command. Следующий фрагмент кода показывает, как выполнить команду в этой транзакции:

Visual Basic Dim txn As = Dim As String = "INSERT INTO Customers (...) VALUES Dim As New txn) As Integer = If intRecordsAffected = 1 Then Else что значение intRecordsAffected = О End If Visual C# QleDbTransaction txn = string StrSQL = "INSERT INTO Customers (...) VALUES cmd = new int = cmd.ExecuteNonQueryO;

if (intRecordsAffected == 1) i txn.CommitO;

} else { что значение = О i He забудьте вызвать метод Commit или объекта Transaction (в мости от того, требуется ли сохранить или отменить выполненные в ходе транзакции).

Создание объектов Command в Visual Studio Visual Studio экономит вам время, усилия и избавляет от головной по зволяя легко и просто создавать и конфигурировать объекты Command. Давайте рассмотрим возможности Visual Studio периода разработки, предназначен ные для работы с объектами Command.

ГЛАВА 4 Выполнение запросов к базе данных Перетаскивание с панели инструментов Главная отправная точка создания объектов Command в Visual Studio Ч нель инструментов. На вкладке Data имеются объекты всех щиков данных Чтобы создать объект OleDbCommand, просто перетащите элемент с панели инструментов область проектирования или панель компонентов этой области и отпустите его. В панели компонентов обла сти проектирования появится новый объект Command (рис. 4-1).

-N| f Рис. 4-1. Перетаскивание элемента панели инструментов для создания нового объекта Command Как уже говорилось, для создания полезного объекта Command следует ввести строки подключения и запроса. Visual Studio поможет вам задать значения соответствующих свойств (Connection и нового объекта непосредственно в период разработки.

Задание значения свойства Connection После того как вы добавите объект Command в панель компонентов, задать значение его свойства Connection. Выбрав свойство Connection в окне Pro perties, вы откроете список его значений. Можно использовать имеющееся (Existing) соединение, создать новое (New) соединение или свойство Connection пустым. На рис. 4-2 показана Windows-форма с элементами и OleDbCommand в панели компонентов. Выбран элемент OleDbCommand, и в окне Properties показано, как задать свойству Connection существующий объект OleDb Connection.

Если выбрать из списка значение New, откроется диалоговое окно Data Link Properties, позволяющее создать новую строку подключения. Studio Часть II использование поставщика данных сгенерирует новый объект используя созданную вами подклю чения, и задаст этот объект Connection вашего объекта Command, 3 Рис. 4-2. Задание значения свойства Connection Использование Query Builder Visual Studio упрощает создание строки запроса при помощи Query Builder.

Выберите в панели компонентов конструктора объект и затем в окне Properties свойство этого объекта. Появится кнопка, показывающая, что у данного свойства есть собственное окно свойств. Щелкните ее, и откроется диалоговое окно Query Builder (рис. 4-3).

Query Builder позволяет создавать запросы посредством простого графичес кого пользовательского интерфейса. При запуске Query Builder предлагает выбрать таблицы, представления и функции, к которым вы будете обращаться в запросе (рис. 4-3). Выбираемые объекты добавляются в область проектирования, распо ложенную за диалоговым окном Add Table.

Когда отберете таблицы для запроса. Query Buiider поможет вам графичес ки выбрать нужные поля, применить фильтры, порядок сортировки и т.д. На рис. 4- мы указали таблицу Customers и ее поля, которые должен выбрать запрос. Заметьте:

мы также определили параметр для поля В окне Query Builder имеется три панели проектирования. Верх няя панель позволяет легко и просто выбирать поля. Средняя упрощает добавле ние фильтров и порядка сортировки в набор результатов. Нижняя содержит текст созданного вами запроса. Изменения, выполненные в одной панели, отражаются в двух других. Для проверки корректности созданного запроса достаточно щелк нуть панель с текстом запроса правой кнопкой и выбрать Verify.

ГЛАВА 4 Выполнение запросов к базе данных Рис, 4-3. Выбор таблиц для запроса V V an Рис. 4-4. Выбор полей и задание критериев запроса средствами Query Builder Еще одна удобная команда контекстного меню, Run, запускает созданный прос и выводит его результаты в нижней панели. Если вы написали параметризо ванный запрос, Query Builder откроет диалоговое окно для ввода значений пара метров. Кроме того, логика Query Builder позволяет редактировать данные в па нели результатов для изменения содержимого БД.

Часть II Подключаемся: использование поставщика данных Использование нового объекта Command в коде Мы успешно создали и сконфигурировали объект Command на основе следующего запроса:

SELECT CompanyName, FROM Customers WHERE CustomerlD LIKE ?

Прежде чем добавить код, который выполнит наш объект получит его результаты, перетащите с панели инструментов на форму элемент управле ния список*. Измените его размер так, чтобы список занимал большую часть формы. Элемент управления отображает содержимое поля CompanyName для получаемых нами рядов.

Дважды щелкните форму, чтобы перейти к коду ее события Load. Теперь нам требуется выполнить объект получить результаты с помощью объекта и вывести содержимое поля CompanyName в списке. Несмотря на то, что для поля указан параметр, достаточно передать шаблонный сим вол чтобы запрос вернул записи всех клиентов.

Разработчики на Visual Basic могут воспользоваться кодом с рис. 4-5.

2 S Рис. 4-5. Использование объекта Command в период выполнения в Visual Basic Программистам на Visual С* предлагается такой код:

соединение в качестве параметра шаблонный получить записи всех клиентов Value = запрос и выводим все поля CompanyName в ГЛАВА 4 Выполнение запросов к данных rdr = while DataReader и Connection Программисты на Visual С* внимание! Не забудьте добавить в блок using в верхней части кода формы выражение using Запустив проект, вы увидите, что список содержит названия компаний из таб лицы Customers.

Перетаскивание из Server Explorer Если ваш объект Command основан на вызове хранимой процедуры, для его со здания и конфигурирования достаточно перетащить хранимую процедуру из Server Explorer в область проектирования. При перетаскивании хранимой процедуры SQL Server создается объект а при перетаскивании хранимых процедур других источников данных Ч объект На рис. 4-6 я перетащил на процедуру из БД SQL Server wind. В окне Properties видно, что свойствам и Connection заданы такие значения, чтобы эту хранимую процеду ру было удобно в коде. Х X И, И В by Yew E Most Рис. 4-6. Создание объекта Command на основе хранимой процедуры Примечание Visual Studio добавляет в имя хранимой процедуры симво лы-разделители, предотвращая проблемы при вызове процедур, имена которых содержат нестандартные символы, например пробелы.

Часть И Подключаемся: использование поставщика данных Visual Studio также заполнила набор Parameters нового объекта Чтобы открыть окно свойств этого укажите набор Parameters в окне Pro perties и щелкните кнопку, расположенную справа (рис. 4-7).

Input. precision к ' i Рис. Просмотр набора Parameters объекта Command, основанного на хранимой процедуре Чтобы заполнить набор Parameters объекта средства работы с дан ными Visual Studio запрашивают БД на предмет информации схемы о хра нимой процедуре. Тем не менее многие БД, например SQL Server, не видят разли чия между параметрами ввода-вывода и параметрами, применяемыми исключи тельно для вывода. Б результате для вызова хранимой процедуры, использующей параметры вывода, иногда приходится вручную задавать тип параметров в окне Properties.

Примечание При перетаскивании из Server Explorer таблицы или представления создается объект а не Command. Подробнее о DataAdapter Ч в следующей главе.

Особенности объектов DataReader и Parameter Теперь, когда вы узнали об основных возможностях объектов Command, DataReader и Parameter, я познакомлю вас с их свойствами и методами.

Свойства объекта В табл. перечислены наиболее часто используемые свойства объекта Command.

ГЛАВА 4 Выполнение запросов к базе данных Таблица Наиболее часто используемые свойства объекта Свойство Тип данных Описание String Текст запроса, выполнить CommandTimeout Время (в секундах), в течение адаптер ожидает выполнения (по Ч 30 секунд) CommandType Указывает выполняемого запроса (по умолчанию Ч Соединение с хранилищем к кото рому объект запрос Набор параметров Transaction n Указывает транзакцию, для как результаты запроса влия ют на текущий объект ект Command активирован мето да объекта (no чанию Ч Подробнее об использо вании этого свойства при передаче ожи дающих изменений в БД Ч в главе Свойство CommandTimeout Свойство задает интервал (в в течение ко торого объект Command ожидает результаты запроса. Значение по умолчанию Ч 30 секунд. Если за интервал времени, заданный свойством прос не успевает объект Command генерирует Помните: как только запрос начал возвращать интервал ожидания перестает Предположим, вы хотите с объекта DataAdapter вставить содержимое таблицы в объект а таблица настолько велика, что выборка из нее занимает более 30 превышает по умолчанию свойства CommandTimeout объекта Command. Поскольку объект mand, используемый объектом DataAdapter, вернул первую запись менее чем за 30 секунд, время ожидания запроса отключается от того, сколько займет получение содержимого таблицы Ч минуту, день или год.

Свойство Свойство CommandType упрощает написание текста запроса и может принимать константы (табл. 4-2) из перечисления CommandType (доступно в пространстве имен Значение свойства CommandType по умолчанию Ч Text. При этом объект выполнения запроса использует точное, указанное вами свой ства CommandText. Я что значение свойства CommandText по изменять следует, и вот почему, Если задать свойству CommandType значение объект Command при выполнении запроса дополнит значение свойства префиксом SELECT " FROM. Это означает, что при успешном выполнении запроса объект Command выберет все записи и столбцы таблицы.

1 08 Часть II Подключаемся: использование поставщика данных Таблица Элементы перечисления Константа Значение Описание Text 1 Объект Command не изменяет содержимое свойства CommandText StoredProcedure 4 Объект Command создаст запрос для хранимой процедуры, используя в имени последней зна чение свойства 512 Объект Command дополнит значение свойства CommandText префиксом ' FROM* Если вы выполняете запрос к таблице, имя которой содержит пробел, напри мер к таблице Order Details БД и не заключено в символы-разделите ли, понятные БД, произойдет ошибка. Я стараюсь использовать квадратные скоб а не вставлять в код двойные кавычки. Если свойству CommandType задано значение TableDirect, система не ограничит автоматически имя таблицы симво лами-разделителями Ч вам придется сделать это самостоятельно.

таких проблем можно исключив пробелы из имен столбцов, представлений и хранимых процедур. В самом деле, часто ли вы слы шали, чтобы разработчик говорил: Слава Богу, я могу использовать пробелы в имени этого объекта?

Примечание Константе TableDirect имя дано не совсем правильно, в результа те программисты, ADO, могут решить, что она со ответствует константе из перечисления модели ADO. Такой вывод полностью логичен, но на самом деле константа соответствует константе Несмотря на заключен ный имени константы смысл, если свойству CommandType задать зна чение TableDirect, объект Command не будет выбирать содержимое таб лицы низкоуровневые интерфейсы, поддерживаемые OLE ставщиками Jet и SQL Server.

Константа StoredProcedure упрощает процесс вызова хранимой процедуры:

Visual Basic Dim As = = = = Visual C# cmd = new OleDbCommandO;

= = "MyStoredProc";

= = 4 Выполнение запросов к базе данных Здесь показан стандартный синтаксис вызова хранимых процедур Ч Кроме того, SQL Server поддерживает синтаксис EXEC ос.

Фактически OLE SQL Server может преобразовывать вызовы, зующие синтаксис CALL, и при непосредственном взаимодействии с БД на самом деле применять синтаксис EXEC. Таким вы воспользуетесь синтаксисом EXEC и чуть-чуть повысите производительность кода. Я практически не прибегаю к этому синтаксису, поскольку мне часто приходится создавать код, не от сервера БД. Чуть позже я расскажу о вызове параметризованных хранимых про цедур, несколько усложняющем синтаксис.

Как и в случае с константой если свойству CommandType задать значение система не заключит автоматически имя таблицы в В связи с этим я обычно не изменяю значение свойства CommandType по умолчанию и использую в свойстве синтаксис Свойство Parameters Свойство Parameters возвращает набор содержащий объекты Подробнее о свойствах и методах объекта ter Ч далее в этой главе.

Свойство Transaction Свойство Transaction позволяет выполнять объект Command в транзакции. Если вы начали транзакцию с использованием имеющегося соединения и пытаетесь выполнить объект не связав его с этой транзакцией при помощи свой ства Transaction, объект Command генерирует исключение.

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

Подробнее об использовании свойства UpdatedRowSource Ч в главе Таблица 4-3. Элементы Константа Значение Описание Both 3 Объект Command получит новые данные для ис пользуя первую возвращенную запись и параметры вы вода 2 Объект Command получит новые данные для ряда, ис пользуя первую запись None 0 При выполнении объект не будет получать но вые данные для ряда 1 Объект Command получит данные для ряда, ис пользуя вывода Часть II Подключаемся: использование поставщика данных Методы объекта Таблица 4-4. Наиболее часто используемые методы объекта Метод Описание Cancel Отменяет выполнение запроса Создает для запроса новый параметр Предназначен для выполнения запросов, не возвращающих записей Выполняет запрос и вставляет его результаты в объект OleDbDataReader Выполняет и получает первое первой записи, Предназначен для единичных запросов типа "SELECT HyTable Prepare Создает в хранилище данных подготовленную версию запроса Задает свойству его значение по умолчанию Ч 30 секунд Метод Cancel Метод Cancel позволяет отменить выполнение запроса. Если вызвать метод Cancel объекта не выполняющего в данный момент запрос, ничего не про изойдет.

Кроме того, при вызове метода Cancel объект Command отбрасывает все запи си объекта которые он не успел считать. Следующий фрагмент кода получает результаты простого запроса. Код выводит результаты, а после них Ч число полученных записей. В коде имеется закомментированный вызов метода Cancel.

Удалив символ комментария и выполнив запрос, вы что метод Cancel отбрасывает его результаты.

Visual Basic Dim strConn As String = Data & "Initial Dim As String = "SELECT Customers" Dim As New Dim As New Dim As OleDbDataReader = Dim As Integer Do While intRowsRetrieved += Loop & retrieved") ГЛАВА 4 Выполнение запросов к базе данных Visual C# string = Data "Initial string = "SELECT Customers";

= new = new rdr = int = 0;

while { + Метод Метод ExecuteNonQuery выполняет запрос, не объект DataReader для при ема его результатов. Используйте этот метод, если вам нужно выполнить команд ный запрос или не требуется просматривать запросом записи.

Значения параметров и параметров вывода становятся ми по завершении вызова метода Метод ExecuteNonQuery возвращает целое число, соответствующее количеству записей, затронутых вашим запросом. Подробнее о возвращаемом значении ме тода и пакетных запросах Ч в начале главы.

Метод Метод ExecuteReader объекта Command помещает ряды, возвращаемые в объект DataReader. Ранее мы уже обсуждали основы использования это го метода, но он предоставляет еще и интересные возможности, о которых я расскажу сейчас.

Метод ExecuteReader объекта Command и может принимать зна чения из перечисления (табл. 4-5).

Таблица 4-5. Элементы перечисления Константа Значение Описание 32 При закрытии объекта DataReader закрывается и соеди нение 4 Объект DataReader получает сведения первичного ключа для входящих набор 2 Объект DataReader содержит только о столбцах;

запрос фактически не выполняется см. стр, Часть II Подключаемся: использование поставщика данных Таблица 4-5.

Константа Значение Описание SequentialAccess 16 Значения доступны только в последовательном порядке. Например, просмотрев содержимое третьего просмотреть содержимое первого и второго столбцов вы уже не сможете 1 Объект DataReader содержит результаты только первого запроса, возвращающего записи 8 Объект DataReader содержит только первую запись, воз вращенную запросом Если при вызове метода передать ему константу то при метода Close объекта DataReader последний вызовет метод Close свя занного с ним объекта Connection.

Эта возможность очень если вы создаете бизнес-объекты и передае те между ними данные. Иногда необходимо, чтобы бизнес-объект вернул вызы вающему объекту объект DataReader, а не DataTable или какую-нибудь другую структуру данных. При этом требуется, чтобы вызывающий объект закрыть объект Connection, когда закончит считывать результаты запроса из объекта Data Reader.

Но что, если вы не доверяете вызывающему объекту? В таком случае не следу ет предоставлять ему прямое соединение с БД. Используя константу CloseConnection, вы с легкостью данную ситуацию, не подвергая риску безопасность и архитектуру своего приложения.

и В последующих разделах в числе прочих методов объекта я также расскажу о методе Он возвращает метаданные о столбцах объек та DataReader Ч имена, типы данных и т.д. Такая информация полезна при созда нии средств автоматической генерации кода. Если вы собираетесь использовать метод объекта DataReader, обратите внимание на константы Keylnfo и SchemaOnty, принимаемые методом ExecuteReader объекта Command.

Вызвав ExecuteReader и указав в параметре Options константу вы фактически получите информацию схемы о столбцах, не выполняя запроса.

Если указать в параметре Options константу Keylnfo, объект DataReader выбе рет из источника данных дополнительную информацию схемы, чтобы показать, являются ли столбцы набора результатов ключевыми столбцами таблиц источника данных.

При использовании константы ScbemaOnfy дополнительно указывать константу Keylnfo не требуется. ADO.NET автоматически укажет в схеме сведения о ключах.

Если при вызове метода ExecuteReader передать ему константу CloseConnection, столбцы данных в объекте DataReader будут доступны только в последовательном порядке. Так. просмотрев содержимое второго столбца, просмотреть содержимое первого столбца вы уже не сможете.

ГЛАВА 4 Выполнение запросов к базе данных В зависимости от используемого источника данных, константа несколько объекта DataReader.

и Если нужно просмотреть только первую запись или первый набор результатов, возвращаемый запросом, передайте при вызове метода константу SingieRow или SingleResult соответственно.

При указании константы SingieRow создается объект DataReader, содержащий не более одной записи данных, Если ваш запрос 10 записей и при вызове ExecuteReader вы указали SingieRow, объект DataReader будет содержать только первую запись данных. Все прочие записи отбрасываются. При использо вании SingleResult аналогичным образом отбрасываются наборы результатов.

Метод Метод ExecuteScalar аналогичен методу ExecuteReader за исключением того, что возвращает первое поле первой записи набора задавая его значению универсальный тип данных Object. Если запрос возвращает больше одной ячейки вторая и последующие ячейки отбрасываются.

Если ваш запрос по аналогии с приведенным далее возвращает одну ячейку для повышения производительности кода можно воспользоваться дом ExecuteScalar.

SELECT FROM HyTable Метод Prepare Одно из основных преимуществ хранимых процедур Ч то, что по сравнению с динамическими запросами они обычно выполняются быстрее. Это обусловлено тем, что СУБД способны заранее подготовить план выполнения процедуры. Такое отличие сравнимо с разницей между кодом сценария и скомпилированным ко дом. Код сценария зачастую более гибок, поскольку его разрешается генериро вать в период выполнения, но зато скомпилированный код выполняется быстрее.

Большинство СУБД поддерживают понятие подготовленного запроса;

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

Чтобы подготовить объект Command, достаточно вызвать его метод Prepare.

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

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

114 Часть Подключаемся: использование поставщика данных Тем не если соединения постоянно заимствуются из пула, а не закры БД не сможет удалить все временные хранимые созданные для ваших подготовленных запросов. И хотя решения данной проблемы в после дних SQL Server изменен порядок хранения временных процедур, я все же не рекомендую вам использовать подготовленные запросы при создании мно гоуровневых процедур.

Метод Метод задает свойству его значение по умолчанию Ч 30 секунд. Если вы спросите: зачем мне это знайте, та кой вопрос до вас задавали многие.

Свойства объекта В табл. 4-6 перечислены наиболее часто используемые свойства объекта OleDb DataReader.

Таблица 4-6. Наиболее часто используемые свойства объекта OleDbDataReader Свойство Тип данных Описание Указывает степень текущего ряда. Доступно только для чтения Возвращает число полей в объекте DataReader. Доступно только для чтения Boolean Указывает, ли объект DataReader. Доступно толь ко для чтения Item Object Возвращает содержимое указанного поля текущей запи си. только для чтения Указывает число затронутых выполненными запросами. Доступна только для чтения Свойство Depth и метод GetData Свойство Depth и метод GetData зарезервированы для запросов, возвращающих данные, и в текущей версии не поддерживаются.

Свойство Свойство возвращает целое число, соответствующее числу полей дан ных в наборе результатов.

Свойство IsClosed Свойство возвращает логическое значение, указывающее, закрыт ли объект Свойство Item Свойство Item объекта по своей сути и функциональности аналогич но свойству Item объекта Можно указать имя поля в виде строки или порядковый номер поля, и свойство вернет значение этого поля с универсальным типом данных Object.

ГЛАВА 4 Выполнение запросов к базе данных Если вам известен тип данных поля, для повышения сле дует вызвать метод (например, или Свойство Свойство позволяет определить число записей, измененных вашими командными запросами. Если вы выполняете один командный запрос и хотите узнать, сколько записей он изменит, воспользуйтесь методом объекта Ч он вернет соответствующее значение, Чтобы узнать, сколько записей изменит выполняемый пакетный запрос, обра титесь к разделу, посвященному пакетным запросам, ранее в этой главе.

Методы объекта OleDbDataReader В табл. 4-7 перечислены наиболее часто используемые методы объекта OleDbData Reader.

Таблица 4-7. Наиболее часто используемые методы объекта Метод Описание Close Закрывает объект Возвращает содержимое указанного поля текущего ряда по поряд ковому номеру поля, задавая содержимому указанный тип данных Получает из указанного поля текущей записи массив байт Получает из указанного поля текущей записи массив символов Возвращает новый объект DataReader на основе указанного поля Возвращает имя типа данных поля по порядковому номеру поля Возвращает тип данных поля по порядковому номеру поля Возвращает имя поля по его порядковому номеру Возвращает номер поля по его имени Возвращает информацию схемы (имена и типы данных полей) об объекте DataReader в виде объекта Возвращает значение поля по его порядковому номеру Принимает с помощью которого объект DataReader воз вращает содержимое текущего столбца. При вызове возвращает значение типа соответствующее числу записей в массиве Указывает, содержит ли поле значение NULL Осуществляет переход к следующему результату Read Осуществляет переход к следующей записи Метод Read Метод Read переход к следующей записи. Помните: пока вы не вы зовете метод Read, первая запись набора результатов, находящегося в объекте недоступна. При первом вызове метода Read объект DataReader пе реходит к первой записи набора результатов, а при последующих вызовах Ч к следующей записи.

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

Метод Метод аналогичен свойству Item. Он принимает целое число и возвра щает содержимое соответствующего поля с универсальным типом данных Object.

Метод GetValue и различные методы принимают только целочис ленные значения, соответствующие порядковому номеру поля, и не осуществля ют построчный поиск, как свойство Item.

Назначение объекта DataReader Ч обеспечить высокую производительность, и, учитывая элемент набора быстрее отыскать по порядковому номеру, а не по имени.

Методы Объект DataReader также предоставляет которые возвращают значения с конкретным типом данных. Если известно, что поле содержит данные с типом string, можно вызвать метод GetValue объекта DataReader и затем привести данные к типу string или же просто воспользоваться методом Visual Basic As String Dim As OleDbDataReader strCompanyName = strCompanyName = Visual C# string OleDbDataReader rdr;

strCompanyName = = У объекта DataReader предусмотрены методы для всех типов данных Framework Ч и т.д.

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

Объект при помощи объекта DataReader выбирает данные из БД и помещает их в объекты DataTable. Чтобы обеспечить максимальную произво дительность, объекты DataAdapter поставщиков данных из состава Visual Studio метод GetValues объекта DataReader.

ГЛАВА 4 Выполнение запросов к базе данных Visual Basic Dim As a = - 1) As Object End While Visual C# OleDbDataReader = aData = new while { Примечание Visual Basic и C* по-разному создают вы, что учтено в приведенном выше фрагменте кода. Так, оператор Dim As Object создает в Visual Basic и Basic массив из пяти элементов (0 Ч 4), а оператор aData = new со здает в Visual С*. NET массив из четырех элементов (0 Ч 3).

Метод Если вы имеете дело с пакетными возвращающими несколько ров результатов, для перехода к следующему набору следует использовать метод NextResult. Как и метод Read, NextResult возвращает логическое значение, сообща ющее, есть ли еще наборы результатов.

Фрагмент кода в разделе Вопросы, которые стоит задавать почаще* показы вает, как с помощью объекта просмотреть результаты пакетного зап роса и как использовать метод NextResult в цикле.

Метод Close При работе с объектами DataReader важно в цикле просмотреть результаты и как можно быстрее закрыть DataReader. Объект Connection невозможно использовать для каких-либо операций, если имеется активный пожарный курсор, открытый с его помощью. Если вы попытаетесь воспользоваться объектом Connection, с ко торым связан открытый объект DataReader. система выдаст исключение, гласящее, что операции требуется открытое и доступное соединение.

Примечание Некоторые БД допускают на одном соединении несколько за просов с ожидающими результатами. В начальной версии ADO.NET объект Connection с открытым объектом DataReader считается заблокированным;

для выполнения каких-либо операций объект DataReader рекомендует ся закрыть независимо от того, допускает ли БД наличие на одном со Часть И Подключаемся: поставщика данных единении запросов с ожидающими результатами.

в будущей версии эта модель поведения будет изменена.

Это ограничение, удивит разработчиков, имеющих опыт работы с ADO, но не тех, кто имел дело с Различные технологии доступа к данным Microsoft по-разному эту ситуацию.

Если вы попытаетесь открыть в БД SQL Server два пожарных курсора с зованием ADO, все будет работать нормально и система не выдаст никакой ошибки.

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

разработчикам, с ADO, знакомо сообщение об ошибке is busy with results from another ODBC не какой либо закулисной работы, чтобы помочь вам разрешить ситуацию. Если вы попытаетесь воспользоваться занятым соединением, то просто получите об ошибке.

Какой из этих подходов ошибки или выполнение нужного по новому соединению) лучше? Разработчики как из Microsoft, так и из других компаний, не могут прийти к единому мнению. Фактически каждая новая технология доступа к данным подходила к этой ситуации чем ее предшественница: генерирует ошибку, открывает новое соедине ние, RDO генерирует ошибку. ADO открывает новое соединение и ADO.NET гене рирует ошибку.

Методы и Методы объекта DataReadcr позволяют больше узнать о результатах, возвращае мых запросом. Так, с помощью метода можно узнать имя конкретного поля. Если вам известно имя нужного поля и вы хотите выяснить его порядковый номер, передайте имя поля методу Метод GetDataTypeName принима ет целое число, соответствующее порядковому номеру поля, и возвращает в стро ковом представлении тип данных этого поля.

Метод Метод GetSchemaTable объекта аналогичен методу объекта Оба создают объект DataTable со вложенными объектами которые соответствуют возвращенным запросом. Метод GetSchemaTable не принимает каких-либо параметров и возвращает новый объект DataTable. Этот объект объекты DataColumn, соответствующие столбцам, запросом;

не менее его набор пуст. Метод GetSchemaTable заполняет новый объект DataTable только сведениями схемы.

вам сначала будет трудно понять данные, возвращаемые методом GetSchemaTable. Этот метод объект с струк турой. Вложенные объекты возвращенного методом объекта DataTable соответствуют набора результатов запроса, а объекты DataColumn свойствам или атрибутам этих столбцов, Следующий фрагмент кода выводит для каждого возвращенного запросом стол бца его имя и тип БД.

ГЛАВА 4 Выполнение запросов к базе данных Visual Basic Dim strConn, As String & "Initial strSQL = "SELECT FROM Orders" Dim As New Dim As New Dim rdr As = Dim As DataTable = Dim As DataRow For Each row In & - 4 _ Next row Visual C# string strConn, strSQL;

strConn = + "Initial StrSQL = "SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders";

= cmd = new OleDbDataReader rdr = DataTable tbl = (DataRow row in + - + Примечание Эти фрагменты кода преобразуют значение типа integer, храня щееся в поле в соответствующую константу перечисления OleDbType.

Различные поставщики данных используют в объекте DataTable, щаемом методом различные схемы таблиц. Так, метод объекта DataReader поставщика SQL Server. NET возвращает объект DataTable.

содержащий столбцы, недоступные при использовании поставщика OLE DB Метод GetData и свойство Depth Свойство Depth и метод GetData зарезервированы для запросов, возвращающих данные, и в текущей версии ADO.NET не поддерживаются.

Создание объектов Parameter У объекта Parameter имеются шесть конструкторов. Набор предоставляет шесть перегруженных методов Add, с помощью которых удается создавать объекты Parameter и добавлять их в набор. Для создания объекта Para Часть II Подключаемся: использование поставщика данных meter предназначен метод объекта Command. Вот такое разнообразие!

Какой же способ выбрать вам? Это зависит от того, какие объекта Parameter требуется задать. Например, один из конструкторов объекта OleDbPa позволяет указать значения свойств ParameterName, Direc Precision, Scale, и Value. Определите, значения каких свойств вы хотите задать, и затем воспользуйтесь конструктором, предоставляющим необходимую функциональность.

Свойства объекта OleDbParameter В табл. 4-8 перечислены наиболее часто используемые свойства объекта OleDb Parameter.

Таблица Наиболее часто используемые свойства объекта OleDbParameter Свойство Тип данных Описание Туре Указывает тип данных параметра OleDbType Указывает тип данных БД параметра Direction Указывает тип параметра Ч параметр (Input), параметр вывода (Output), параметр вво да/вывода возвращаемый параметр (Return) Boolean Указывает, может ли параметр принимать значе ние Null OleDbType OleDbType Указывает тип данных OLE DB параметра ParameterName ParameterName Указывает имя параметра Precision Byte Указывает параметра Scale Byte Указывает числовую шкалу параметра Size Int32 Указывает размер параметра SourceColumn String Указывает имя столбца в объекте DataSet, на ко торый ссылается данный параметр. Подробнее о связывании параметров запроса с объектами Ч в главе SourceVersion Указывает версию столбца (текущая или ориги нальная) в объекте DataSet, на который ссылается данный параметр. Подробнее о связывании пара метров запроса с объектами DataSet Ч в главе Value Object Указывает значение параметра Свойство ParameterName В целом, свойство ParameterName объекта Parameter предназначено исключительно для того, чтобы упростить вам поиск этого объекта в наборе Parameters объекта Command. Например, если вы вызываете хранимую процедуру с использованием поставщика OLE DB вам не требуется соответствие значений свойств Parame terName ваших объектов Parameter именам хранимой процедуры. Но если вы зададите значения свойств ParameterName, окажется, что код удобнее читать.

ГЛАВА 4 Выполнение запросов данных Примечание Поставщик данных SQL Server сопоставляет ваши объекты Parameter маркерам параметров в запросе, используя значения объектов Parameter. если выполняете запрос SELECT Orders WHERE CustomerlD = задайте свойству ParameterName вашего объекта Parameter значение Свойство Direction Если вы вызываете хранимую процедуру и собираетесь использовать возвращае мые параметры или параметры задайте свойству Direction вашего объек та Parameter одно из значений, перечисленных в табл. 4-9.

Таблица 4-9. Элементы перечисления Константа Значение Описание Input 1 Значение по умолчанию. Параметр используется только для ввода 2 Параметр используется только для Параметр используется только ввода-вывода Return б Параметр содержит хранимой процедуры Поскольку значение свойства Direction по умолчанию Ч input, явно его требуется только для объектов Parameter, не являющихся параметрами ввода.

Большинство средств автоматической генерации кода запрашивают БД на предмет информации о параметрах, включая их тип. Даже если вы имеете дело с надежным средством генерации например с утилитами из состава Visual Studio в некоторых случаях вам все равно требуется изменять значение свойства Direction ваших объектов Parameter, Ч спросите вы. Многие БД поддерживают параметры вывода, ввода-вывода и возвращаемые параметры хранимых процедур, но не во всех ЗЗД есть языковые конструкции, позволяющие явно указать тип параметров вашей хранимой процедуры. Так, SQL Server допускает в определении хранимой проце дуры ключевое слово что параметр способен возвращать значение. Тем не менее определение собственно параметра в хранимой процеду ре одинаково и не зависит от того, является ли он параметром вывода или пара метром ввода-вывода. Как следствие, средство генерации кода не может лить, используется ли параметр для ввода-вывода или только для вывода. Утили ты из состава Visual Studio предполагают, что параметр применяется для ввода вывода. Если вам нужен параметр только для вывода, явно задайте в коде свой ству Direction соответствующее значение, Свойство Value Свойство Value позволяет просмотреть и задать значение параметра. Тип этого свойства Ч Object. Следовательно, чтобы задать значение параметра, в не 122 Часть II Подключаемся: поставщика данных которых случаях придется значение свойства Value в другой тип данных, например string или integer.

Свойства и Свойства и SourceVersion определяют, как объект Parameter выбира ет данные из объекта когда вы передаете ожидающие изменения в БД посредством вызова метода объекта Подробнее об этом Ч в главе 10.

Свойства DbType и OfeDbType Класс Parameter Ч единственный параметр объектной модели ADO.NET, требую щий использования внутренних типов данных БД.

когда вы с помощью объекта DataAdapter выбираете поле из таблицы Customers в объект вам не нужно знать, имеет ли это поле фиксированную или переменную длину и допускает ли оно данные в кодировке Unicode. Тип данных объекта Ч просто string, Свойство объекта DataColumn определяет тип который ADO.NET потребуется для хранения содержимого поля, и принимает тип возвращае мый функциями или зависимости от выбранного вами языка). Этот тип данных слабо связан с типом данных, используемым БД для хранения соот ветствующих данных. Строковые типы данных БД (например, char и со поставлены типу данных String модели нецелочисленные типы (money, numeric) Ч типу данных Decimal и т.д.

Тип данных объекта Parameter должен быть более точным. В предыдущем фрагменте кода мы выполняли запрос SELECT CustomerlD, FROM Orders WHERE CustomerlD = ?

с параметром, тип данных которого Ч расшифровывается как (широкий), указывая, что строка содержит двухбайтные символы Unicode, а не однобайтные символы ANSI), а длина Ч 5. Если использовать для параметра не подходящий тип данных, БД обработает значение параметра не так, как мы этого ожидаем.

Все объекты Parameter предоставляют свойство DbType и специфичное для поставщика свойство, определяющее тип данных. Например, у объекта есть свойство а у параметра Ч свойство Свойства DbType и тесно связаны, и их значения влияют друг на друга.

Так, если задать свойству DbType объекта значение свойству OleDbType будет неявно задано значение Точно так же, если для свойства OleDbType указать значение свойству DbType будет неявно задано значение Свойства Precision, Scale и Size При определении структуры БД некоторые типы данных требуют, чтобы перед их именем указывалась дополнительная Зачастую для полей с дво ичными и символьными данными определен максимальный размер. Если вы ис пользуете объект Parameter при работе с такими данными, задайте свойству Size ГЛАВА 4 Выполнение запросов к базе данных нужный размер. Числовые типы данных обычно позволяют задать шкалу (коли цифр) и точность цифр справа от десятичной точки).

Вопросы, которые стоит задавать почаще Вопрос. Я вызвал хранимую возвращающую набор записей. Все вро де бы работает, но возвращаемые параметры и параметры вывода пусты. Что здесь не так?

Ответ. Хранимую процедуру можно рассматривать как одну из функций вашего кода. Функция не вернет значение, пока не будет выполнен весь ее код. Если хра нимая процедура возвращает результаты и вы не завершили их обработку, про цедура на самом деле не закончила выполняться. Результаты, возвращаемые про цедурой, присваиваются параметрам вывода и возвращаемым параметрам ваше го объекта Command только тогда, когда вы закроете объект Предположим, у нас есть такая хранимая процедура:

CREATE PROCEDURE OUTPUT) AS SELECT FROM SELECT Phone Customers и мы вызываем ее с помощью следующего кода:

Visual Basic Dim strConn, As String strConn = & _ "Initial Dim As New StrSQL = Dim As New Dim param As = = Output Dim As = execution - & Do While Loop reading rows - & Do While Loop reading all results - & closing DataReader - " & 124 Часть II Подключаемся: использование поставщика данных Visual C# string = + "Initial = new string = = new param = = Output;

= execution - + while {} reading rows - (string) while {} reading all results - + (string) closing - + Несмотря на то, что хранимая процедура задает значение параметра вывода до того, как выполнить запрос, возвращающий записи из таблицы Customers, это станет доступно лишь после закрытия объекта DataReader.

Вопрос. В документации MSDN мне встретился фрагмент кода, задающий зна параметров без задания свойства или какого-либо другого свойства, специфичного для поставщика данных и определяющего тип данных. Как работает такой код? Безопасно ли его применение?

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

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

Вернемся к упомянутой в вопросе возможности. Если не инициализировать определяющие тип данных объекта OleDbParameter, и задать значение Value, параметр автоматически выберет подходящий тип данных. Сле дующий код успешно возвращает список всех клиентов, идентификатор (Custo которых начинается с английской буквы А.

ГЛАВА 4 Выполнение запросов к базе данных Visual Dim As String strConn = & "Initial Dim As New strSQL = "SELECT Customers & "WHERE CustomerlD LIKE As New Dim As OleDbDataReader = Do While Loop Visual C# string strConn, strSQL;

strConn = + "Initial new StrSQL = "SELECT CustomerlD FROM Customers WHERE LIKE cmd = new OleDbDataReader rdr = while cn.CloseO;

Этот запрос вернет нужные записи, но не что свойствам или Size объекта будут заданы ожидаемые вами ния. OleDbParameter определяет подходящие значения этих свойств, когда вы выполняете запрос, и задает их лишь внутренне. Воспользовавшись утилитой SQL Profiler в SQL Server 2000, вы что объект Command выполнил такой запрос:

exec sp_executesql CustomerlD Customers WHERE CustomerlD LIKE Объект OleDbParameter предположил, что параметр окажется строкой перемен ного размера в кодировке Unicode с длиной, равной 2 символам. Я успешно вы полнял параметризованные запросы к БД SQL Server, Oracle и Access с использо ванием строковых и числовых параметров (например, с типом данных никаких проблем при этом не возникало.

Меня сильно впечатлило, что эта возможность работает насколько хорошо. Тем не менее мой опыт работы в службе технической поддержки по-прежнему за Часть II Подключаемся: использование поставщика данных меня чувствовать себя неуютно, когда приходится иметь дело с подоб ными функциями. Не могу сказать, что я не уверен в корректности их работы, но мне все же хочется, чтобы программисты явно задавали свойства, определяющие тип данных и размер.

Вопрос. Почему нельзя вызвать хранимую процедуру и успешно получить зна чение возвращаемого параметра? С параметрами вывода проблем нет, а вот воз вращаемые параметры вызывают затруднение, Ответ. Во-первых, если вы создали объект с помощью объекта Com прежде чем просмотреть значение возвращаемого параметра, убедитесь, что вы закрыли DataReader (подробнее о закрытии этого объекта Ч в первом вопро се данного раздела).

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

CREATE PROCEDURE AS SELECT OrderDate WHERE = RETURN А код на Visual Basic Ч так:

As New = 5} = = Для краткости скажу, что он расположил параметры в неверном порядке. Та кую простую ошибку может допустить каждый. То, что он назначил свойству значение затруднило выявление и именно по этой причине я не слишком люблю свойство CommandType моделей и ADO. Команда разработчиков ADO.NET старалась сделать как лучше, реализо вав данную возможность, но, полагаю, программисты получат гораздо больше, научившись создавать реальные запросы.

Корректное значение свойства в данном случае таково:

{? = CALL Синтаксис аналогичен вызову функции на Visual Basic или С*. Вам нужен па раметр, в котором хранилось бы значение, возвращенное хранимой процедурой.

Вот полный код для вызова хранимой процедуры:

Visual Dim strConn, As String = & "Initial ГЛАВА 4 Выполнение запросов к базе данных Dim As New strSQL = = CALL Dim As New = 5) Value = Dim As = Do While = & Loop & orders") Visual C# string strSQL;

strConn = + "Initial GleDbConnection = new cn.OpenO;

strSQL = = CALL GleDbCommand cmd = new = 5);

Value = "ALFKI";

OleDbDataReader = while = + + Вопрос. Я пытаюсь преобразовать код на основе ADO для использования ADO.NET.

В моей старой программе применялся метод Refresh набора Parameters модели ADO, Если такой метод ли у объекта модели ADO.NET?

Ответ. Метода у объекта ParameterCollection нет, по крайней мере, пока.

В текущей версии объектной модели ADO.NET предоставить волшебным спосо бом настраиваемому параметризованному запросу сведения о параметрах Тем не менее решение для вызова хранимых процедур имеется. У объекта (подробнее о нем Ч в главе 10) есть метод ко торый заполняет набор Parameters объекта Command, если этот объект вызывает хранимую процедуру, Следующий фрагмент кода демонстрирует, как использо вать данную функциональность.

Часть II использование поставщика данных Visual Basic Dim As String = & _ Data & _ "Initial & _ Dim As New cmd As New = Dim As For Each param In & param.

& Next param Visual C# strConn = Data + "Initial OleDbConnection = new cmd = new = param in { + + } Вопрос. Объектная модель ADO поддерживает запросы. Как выпол нить запрос асинхронно в модели ADO.NET?

Ответ. В текущей версии ADO.NET подобная функциональность отсутствует.

Объект Command модели ADO позволяет выполнять запросы асинхронно, но основная причина, по которой эта возможность была то, что на писать на классическом Visual Basic код, работающий с большинство программистов не смогли.

Framework значительно упрощает процесс работы с потоками, однако сказать, что создавать соответствующий код стало легко, нельзя. Написать устой чивый многопоточный код по-прежнему сложно, поскольку при этом возникает целый ряд новых проблем. Создать код, проблемы которого проявятся не скоро после развертывания нетрудно. Если вы хотите создать многопоточ ГЛАВА 4 запросов к базе данных код на Visual Basic или прочтите книгу, в которой подробно рается весь процесс его написания.

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

Чтобы воспользоваться данным кодом, создайте консольное приложение на удобном вам языке. Затем добавьте в проект новый класс с именем Вставьте приведенный код в файлы как указано в комментариях. Код метод класса, который запускает новый поток и выполняет в нем запрос. Запрос продолжает выполняться в фоновом потоке, и метод немед ленно возвращает управление сразу после начала выполнения запроса. Когда запрос завершится, его результаты отобразятся в окне консоли. Код ожидает 2, и затем вызывает метод чтобы отменить выполнение запроса.

Visual Basic данный код в файл Dim As New * данный код в Public>

Throw New End If End Sub Public Sub If SyncLock Me Try = True Catch ex As Exception End Try End SyncLock Else Dim As String strMessage = "Can't execute method." & vbCrLf & vbTab & "I'm not currently running a query."

Throw New End If End Sub Private Sub Dim strConn As String strConn = & "Initial Dim As String = DELAY & _ "SELECT FROM Customers" = New OleDbConnection(strConn) cmd = New Dim As Integer Try = Catch ex As Exception If blnCancelledQuery = True Then Else End If End Try = False blnCancelledQuery = False End Sub End>

th;

public void if lock (this) { blnRunningQuery = ts = new th = new else string strMessage;

strMessage = "Can't execute + "I'm still waiting on the results + "of a previous throw new I public void if (blnRunningQuery) lock(this) blnCancelledQuery = 132 Часть II Подключаемся: использование поставщика данных } catch (Exception ex) < else string strMessage = "Can't execute + not currently running a throw ;

private void { string strConn;

+ "Initial string strSQL = DELAY + "SELECT FROM Customers";

= new = new int intNumCustomers = catch (Exception ex) :

if (blnCancelledQuery) else = false;

blnCancelledQuery = false;

Получение данных с помощью объектов уже говорилось в главе 4, объекты Command и DataReader позволяют выпол нять запросы и просматривать их результаты. Но что, если вам требуется помес тить результаты запроса в объект Можно написать код, кото рый заполнял бы DataSet новыми записями, перемещаясь по данным объекта DataReader.

Visual Basic Dim As New Dim tbl As DataTable = объект DataTable Dim As New объект Command Dim rdr As = Dim row As Do While row = = данные из Loop Часть II Подключаемся: поставщика данных Visual C# DataSet = new DataTable = объект DataTable = new объект rdr = while = данные других столбцов i Ну и ну! Запись результатов запроса в объект DataSet должна осуществляться просто. А этот код определенно сложен уж конечно, не быстрой разработки приложений. Кто же станет его писать?

К счастью, ничего писать не надо. Объектная модель ADO.NET вам изящное решение Ч объект DataAdapter. Здесь рассказывается об использовании этого объекта для записи результатов запросов в объекты DataSet и DataTable.

Что представляет собой объект DataAdapter Класс DataAdapter Ч своеобразный мост между соединенным и отсоединенным уровнями объектной модели ADO.NET. С помощью объекта DataAdapter удается поместить данные из БД в объект DataSet. Кроме того, объект может принимать хранящиеся в объекте DataSet. и переда вать их в БД. Подробнее об БД с помощью объектов DataAdapter Ч в главе Здесь же я расскажу о выборке данных из БД с помощью объектов Data Adapter.

Чем объект DataAdapter отличается от других объектов, основанных на запросах Когда я рассказываю об объекте DataAdapter программистам БД, большинство из них кивают головой и говорят, что он похож на объекты Command ADO, и позволяющие передавать запросы БД и помещать их ре зультаты в отдельные объекты.

Тем не менее между DataAdapter и его предшественниками есть несколько су щественных отличий, о которых и пойдет речь далее.

Объект DataAdapter предназначен для работы с отсоединенными данными В объектных моделях ADO, RDO и DAO предусмотрена поддержка отсоединенных данных. Каждая из этих моделей позволяет хранить результаты запроса в отсое диненной структуре, например с помощью объекта Command модели ADO поме ГЛАВА 5 Получение данных с помощью объектов данные объект Recordset, отсоединенный от объекта Connection. Тем не менее в начальной версии каждой из этих моделей поддержка работы с отсоединенны ми данными отсутствовала. Как следствие, их объекты, основанные на никогда не были по-настоящему рассчитаны на данные.

Объект предназначен именно для работы с ними. Возможно, наи лучшее подтверждение такой его Ч метод Fill. Для вызова этого метода вам даже не требуется живое подключение к БД. Когда вы вызываете метод Fill объекта не имеющего открытого подключения к БД, объект откры вает соединение, выполняет запрос к БД, выбирает и заносит результаты запроса в объект DataSet и затем закрывает соединение с БД.

Между объектами DataAdapter и DataSet нет прямой связи Чтобы заполнить объект в объекте DataSet, последний передают в ка честве параметра методу Fill объекта DataAdapter.

Visual Basic Visual C# Когда данный вызов завершится, между объектами не будет какого-либо соеди нения. Объект DataSet не поддерживает внешне или внутренне ссылку на объект а объект DataAdapter Ч ссылку на объект DataSet. Кроме того, не содержит информации о происхождении данных Ч строки подключения, имени таблицы или имен столбцов. Таким объекты DataSet можно передавать с промежуточного уровня клиентским приложениям, не разглашая информации о расположении или структуре вашей БД.

Объект содержит логику обновления для передачи в БД изменений, хранящихся в объекте DataSet Объект DataAdapter похож на улицу с двусторонним движением. Он позволяет выполнять запросы к БД и помещать их результаты в объекты DataSet, а также передавать в БД отложенные изменения. Это главное отличие от предыдущих моделей доступа к данным.

Например, чтобы в ADO поместить результаты запроса в объект Recordset, вы явно или неявно используете объект Command. Для обновления БД применяется метод Update объекта Recordset. Объект Command в процессе обновления не уча ствует.

Чтобы в ADO.NET передать Б БД изменения, хранящиеся в объекте DataSet, используется метод Update объекта DataAdapter. Он принимает объект DataSet в качестве параметра. Объект DataSet может кэшировать изменения, но логика об новления находится именно в объекте DataAdapter.

Логикой обновления в объекте DataAdapter можно управлять Это утверждение стоит повторить: логикой обновления в объекте DataAdapter можно управлять. По-моему, это первостепенная причина для перехода с RDO 136 Часть II Подключаемся: использование поставщика данных или ADO на Вам доступно использование собственных запросов INSERT, UPDATE и DELETE, а также передача изменений посредством хранимых процедур.

Когда я изучал и впервые внимание на данную возможность, у меня сразу возникло три мысли: Жду Ч не дождусь уви деть реакцию разработчиков на эту и Почему мы не думали об этом Предыдущие модели доступа к данным не позволяли до такой степени управ лять логикой обновления, и поэтому многие разработчики не использовали пре доставляемые этими моделями возможности быстрой разработки приложений (rapid application development, RAD). Многие администраторы разрешают пользо вателям изменять данные БД только посредством хранимых процедур. Однако у тех нет разрешений на выполнение запросов UPDATE, INSERT INTO или DELETE, хотя именно такие запросы генерируются моделями DAO, RDO и ADO для преоб разования изменений, сделанных в объектах Recordset и в измене ния в БД. Это что разработчикам приложений для обращения к БД не удастся задействовать возможности объекта Recordset по передаче изменений в БД, В период тестирования бета-версии Visual Studio я разговаривал с одним программистом, скептически относящимся к переходу на ADO.NET. Пытаясь от вергнуть новую объектную модель, он спросил: Могу ли я обновлять БД с помо щью хранимых Когда я улыбнулся и ответил: он был просто шо кирован. Я практически как пару секунд вертелись шарики в его мозгу, прежде чем он спросил: Как это? Отчасти разработчик был ошеломлен потому, что администраторы БД созда ют отдельные хранимые процедуры для обновления, вставки и удаления записей.

Таким образом, чтобы в объектной модели доступа к данным появились можности по передаче изменений с хранимых процедур, модель должна позволять создавать отдельные хранимые процедуры для обновления, вставки и удаления данных.

Именно это и делает объект DataAdapter. Его четыре свойства содержат объекты Command: одно свойство для запроса на выборку данных, второе Ч для передачи отложенных изменений, третье Ч для передачи отложенных вставок и четвертое Ч для передачи отложенных удалений. Для любого из этих объектов Command можно указать собственные командные запросы и хранимые процедуры, а также пара метры, которые будут перемещать данные из объекта DataSet в хранимую проце и обратно.

Разработчики иногда ведут себя, как стадо упрямцев (мне, из этой среды, позволительны такие высказывания). Нам нравится контроль и произво дительность, но не менее Ч и простота использования. Объект DataAdapter пре доставляет все это. Вы можете написать собственную логику обновления или по просить ADO.NET сгенерировать командные запросы, аналогичные запросам, скры то генерируемым моделями ADO и DAO. Вы можете даже воспользоваться неко торыми средствами Visual Studio и сгенерировать логику обновления в пе риод разработки Ч такой вариант сочетает в себе удобство использования, кон троль и производительность.

Примеры кода обновления, а обсуждение реального механизма обнов ления БД при объектов DataAdapter приводятся в главе 10. Здесь же ос ГЛАВА 5 Получение данных с помощью объектов новное внимание уделено структуре объекта DataAdapter и тому, как с его помо щью выбирать запросов.

Анатомия объекта DataAdapter Теперь, когда вы получили некоторое представление о функциональности объекта я расскажу о его Ч это позволит вам понять принципы его работы.

Назначение объекта DataAdapter Ч упростить запись результатов запроса в объекты DataSet и Как говорилось в главе 4, объект Command позволя ет просматривать результаты запроса через объект Объект DataAdapter состоит из группы объектов Command и набора сопоставляющих свойств, опре деляющего порядок взаимодействия объекта DataAdapter с объектом DataSet. На рис. 5-1 показана структура объекта DataAdapter.

DataSet Таблицы Свойства Свойства Свойства Свойства Рис. 5-1. Структура объекта DataAdapter Дочерние команды Когда вы с помощью объекта DataAdapter записываете результаты запроса в объект DataSet, объект взаимодействует с БД при помощи объекта Command.

Внутренне DataAdapter с помощью объекта DataReader выбирает результаты и затем копирует полученные данные в новые записи объекта DataSet. Грубо говоря, этот процесс иллюстрируется фрагментом кода, приведенным в начале главы.

Объект Command, при помощи которого DataAdapter выбирает результаты запроса, указан в свойстве объекта DataAdapter.

Часть II Подключаемся: использование поставщика данных Кроме того, у объекта есть другие содержащие объекты Command: и исполь зует эти объекты Command для передачи в БД изменений, хранящихся в объекте Подробнее об этом Ч в главе 10.

Набор По умолчанию объект предполагает, что столбцы объекта соответствуют столбцам объекта DataSet. Тем не менее иногда требуется, чтобы схема объекта DataSet отличалась от схемы БД. Возможно, в объекте DataSet вы захотите дать столбцу БД другое имя. традиционно переименовы вают столбцы в запросах при помощи псевдонимов. Например, если в таблице Employees есть столбцы LNamc и FName, можно воспользоваться в запро се псевдонимами и изменить в наборе результатов имена этих столбцов на и соответственно;

SELECT AS AS LastName, AS FirstName Объект предоставляет механизм, позволяющий сопоставить резуль таты запроса со структурой объекта DataSet Ч набор TabieMappings.

В приведенном выше запросе используется таблица с именами столбцов типа EmpID. LName и FName. Продолжим этот пример: предположим, что таблице БД дано еще более невразумительное имя типа Нам нужно сопоставить данные этой таблицы с таблицей Employees нашего объекта имена стол бцов которой гораздо более понятны Ч EmployeelD, LastName и FirstName. Набор TabieMappings объекта DataAdapter позволяет организовать такое сопоставление между БД и объектом DataSet.

Свойство TabieMappings возвращает объект содер жащий набор объектов Каждый объект позво ляет сопоставить таблицу (представление, хранимую процедуру) вашей БД и соот ветствующее имя таблицы в объекте DataSet. Кроме того, у объекта DataTableMapping есть свойство которое возвращает объект содержащий набор объектов DataColumnMapping. Каждый из объектов DataColumnMapping сопоставляет столбец БД со столбцом объекта DataSet.

Примечание Имя класса Ч самое длинное, какое мне довелось видеть. Благодаря технологии и автодопол при работе в Visual Studio мне не приходится набирать это имя полностью.

На рис. 5-2 показано, как набор TabieMappings объекта DataAdapter сопостав ляет таблицу нашей БД, содержащую сведения о сотрудниках, с соответствующей таблицей объекта DataSet.

На рисунке мы сопоставляем таблицу Table 123 БД с таблицей Employees объекта DataSet, но из информации о сопоставлении что сопоставлены таблица Table и Employees. Это вызвано тем, что у объекта DataAdapter нет данных о том, с какой же таблицей БД он взаимодействует на самом деле. DataAdapter способен посредством объекта DataReader получить из результатов запроса имена столб цов, но никаким способом ему не удастся определить имя таблицы. В результате ГЛАВА 5 Получение данных с помощью объектов DataAdapter он что имя таблицы Ч Table, и запись сопос тавляет таблицы Table и Employees.

ЬД Набор объекта DataSet с Employees сопоставляется с EmployeelD LName LName с LastName с Рис. 5-2. Набор TableMappings объекта DataAdapter Следующий фрагмент кода заполняет набор TableMappings объекта для обсуждаемого нами Visual Basic Dim da As объект DataAdapter Dim TblHap As Dim As DataColumnMapping = "Employees") ColMap = "EmployeelD") ColMap "LastName") ColHap = "FirstName") Visual C# OleDbDataAdapter da;

объект DataAdapter ТЫМар;

ColMap;

ТЫМар = ColMap = ColHap = ColHap = Создание и использование объектов DataAdapter Вы уже знаете, что представляет собой и что может объект DataAdapter, так что речь пойдет о том, как его создать и работать с ним, Создание объекта DataAdapter При создании объекта DataAdapter обычно следует задать его свойству Select Command допустимый объект Command. Следующий фрагмент кода задает зна нового объекта DataAdapter.

140 Часть II Подключаемся: использование поставщика данных Visual Basic Dim strConn As String = Data & _ "Initial Dim As New Dim As String = "SELECT CompanyName Customers" Dim cmd As New Dim da As New = cmd Visual C# string strConn = + "Initial = new string strSQL = "SELECT CustomerlD, CompanyName FROM Customers";

cmd = new OleDbDataAdapter da = = cmd;

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

Visual Basic Dim strConn As String = & _ "Initial Dim strSQL As String = "SELECT CustomerlD, CompanyName Customers" Dim da As New strConn) Visual C# string strConn = + "Initial string strSQL = "SELECT CustomerlD, CompanyName FROM Customers";

OleDbDataAdapter da = new strConn);

В этом подходе есть потенциальный недостаток. Предположим, вы собирае тесь использовать в приложении группу объектов DataAdapter. Если вы сгенери руете их показанным ранее способом, для каждого объекта будет создан новый объект Connection. Чтобы гарантировать, что объекты DataAdapter исполь зуют один объект Connection, достаточно применить конструктор DataAdapter, принимающий строку подключения и объект Connection. фрагмент кода создает два объекта использующих один объект Visual Basic Dim strConn As String = & "Initial Dim As New Dim As OleDbDataAdapter ГЛАВА 5 Получение данных с помощью объектов DataAdapter = New... FROM Customers", = New FROM Orders", Visual C# string = Data + "Initial = new daCustomers, daCustomers = new FROM Customers", daOrders = new... FROM Orders", Третий конструктор принимает объект Command. Если вы уже создали такой объект и хотите, чтобы обращающийся к нему объект DataAdapter заполнил объект воспользуйтесь следующим Visual Basic Dim strConn As String Data & "Initial Dim As New Dim As String = FROM Customers" Dim As New Dim da As New Visual C# string strConn = Data "Initial OleDbConnection new OleDbConnection(strConn);

string strSOL = "SELECT CustomerlD, CompanyName FROM Customers" cmd = new 01eDbCommand(strSQL, OleDbDataAdapter da = new OleDbDataAdapter(cmd);

Получение результатов запроса Рассмотрев различные способы создания объекта DataAdapter в коде, поговорим о том, как с его помощью записать результаты запроса в объект DataSet. Для нача ла создадим простой DataAdapter, выбирающий данные из таблицы mers БД Использование метода Fill При вызове метода Fill объекта DataAdapter выполняется запрос, хранящийся в свойстве объекта и его результаты помещаются в объект DataSet. Следующий код вызывает метод Fill.

Visual Basic Dim strConn, strSQL As String strConn = & _ "Initial strSQL Х= "SELECT CustomerlD, CompanyName, Phone & _ 142 Часть И использование поставщика данных "FROM Customers" Dim da As New Dim ds As New Visual C# string strConn, strConn = + "Initial strSQL = "SELECT CompanyName, Phone + Customers";

da = new strConn);

DataSet = new В данном фрагменте кода при вызове метода Fill в объекте DataSet создается новый объект DataTable. Он содержит столбцы, соответствующие столбцам, ко торые возвращаются при помощи запроса, Ч CompanyName, Contact Name и Phone.

Создание объектов DataTable и при помощи метода Fill В предыдущем фрагменте кода при вызове метода Fill в объекте DataSet создается новый объект DataTable. Он содержит столбцы CustomerlD, CompanyName, Contact Name и Phone, однако имя не a Table.

Мы уже касались этой модели поведения при обсуждении набора объекта DataAdapter. Мы можем добавить элемент в данный набор и указать объекту что нам требуется сопоставить результаты запроса с объектом Data под названием Customer:

Visual Basic те же параметры инициализации строк и что и в Dim da As New strConn) Dim ds As New DataSetO Visual C# те же параметры инициализации строк и запроса, что и в предыдущем фрагменте OleDbDataAdapter da = new strConn);

DataSet ds = new DataSetO;

Подробнее о наборе TableMappings Ч чуть ниже.

ГЛАВА 5 Получение данных с помощью DataAdapter Использование перегруженных методов Есть несколько способов заполнить объект при помощи метода Fill объекта которые мы и рассмотрим в последующих Указание объекта У объекта DataAdapter имеется два метода Fill, предоставляющих расширенные возможности управления используемым объектом DataTable.

Вместо того чтобы добавлять элемент в набор объекта DataAdapter.

достаточно имя таблицы в вызове метода Fill.

Я часто заполняю таблицы объекта DataSet с помощью метода Fill, не исполь зуя набор Кроме того, вместо объекта DataSet можно указать объект DataTable:

Этот вариант полезен, когда у вас есть уже и ожидающий заполне ния объект DataTable.

Разбиение на страницы при помощи метода Fill объекта DataAdapter Вы наверняка просматривали каталоги, в которых на странице отображается несколько товаров, например, 20 из сотни наименований. DataAdapter предоставляет метод Fill, позволяющий выбрать только часть результатов запроса:

intStartRecord, "ИмяТаблицы") Помните, что параметр для первой записи Ч 0. Так, следующий фрагмент кода выбирает первые записей;

20, "Products") Важно также помнить, что при использовании данного метода Fill только записи объекта DataSet. Предположим, вы выполняете запрос к таблице с 1000 записями и выбираете данные порциями по 20 записей на стра ницу. Следующий фрагмент кода 980, 20, "Products") помещает последние 20 записей, возвращенных запросом, в объект DataSet. На самом деле запрос возвращает записей. Объект DataAdapter просто отбра сывает первые 49 страниц данных.

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

Заполнение объекта DataSet содержимым объекта при помощи объекта DataAdapter Поставщик данных OLE DB предоставляет два метода Fill, позволяющих ко пировать данные из объекта Recordset ADO объект DataSet 144 Часть II использование поставщика данных AdoRecordset) Эти методы полезны, если вы собираетесь использовать в приложении уже написанный код или компоненты, возвращающие объекты Recordset ADO.

Открытие и закрытие соединений Возможно, предыдущие фрагменты демонстрировавшие использование ме тода Fill, позволили вам понять основное отличие того, как объекты DataAdapter и Command объекты Connection. В главе 4 перед вызовом одного из методов Execute объекта Command мы открывали связанный с этим объектом объект Connection. В противном случае объект Command генерировал исключе ние. Объект DataAdapter не предъявляет таких требований.

Если при вызове метода Fill объекта объект указанный в свойстве будет закрыт, DataAdapter откроет соединение, выпол нит запрос, выберет результаты и затем закроет объект Connection. Можно сказать, что DataAdapter Ч весьма аккуратный объект. Он всегда возвращает объект Connec tion, указанный в свойстве SelectCommand, в его исходное состояние. Если вы от кроете объект Connection до вызова метода Fill, по завершении вызова соедине ние останется открытым.

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

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

Basic strConn As = Data & "Initial Dim As New Dim daCustomers, As OleDbDataAdapter daCustomers = New... FROM daOrders = New... Orders", Dim ds As New Visual string strConn = + "Initial new OleDbConnection(strConn);

OleDbDataAdapter daCustomers, daOrders;

daCustomers =... FROM Customers", daOrders = new OleDbDataAdapterC'SELECT... Orders", ds = new DataSetO;

ГЛАВА 5 данных с помощью объектов DataAdapter На самом деле при каждом вызове метода вы дважды открыва ете и закрываете объект Connection. Чтобы избежать этого, перед вызовом метода Fill объектов вызовите метод Open объекта Connection. Если по завер шении всех операций вставки соединение необходимо закрыть, вызовите метод Call:

Многократный вызов метода Fill Что, если вам требуется обновить данные объекта DataSet? Допустим, при запуске приложения ваш объект DataAdapter выбирает содержимое таблицы и вы хотите реализовать просмотра точных данных на текущий момент време ни. Самое простое решение Ч очистить объект DataSet (или и снова вызвать метод Fill объекта DataAdapter.

А вдруг вы не понимаете своего счастья и повторно вызываете метод Fill объекта Visual Basic Dim strConn, As String = & _ "Initial strSQL = "SELECT CompanyName, ContactName, Phone & _ "FROM Customers" Dim da As New strConn) Dim ds As New "Customers") Visual C# string strConn, strSQL;

strConn = "Initial strSQL "SELECT CustomerlD, CompanyName, ContactName, Phone Customers";

OleDbDataAdapter da = new strConn);

Pages:     | 1 | 2 | 3 | 4 |   ...   | 8 |    Книги, научные публикации