David Sceppa
Microsoft'
ADO.NET
Microsoft Press
Дэвид Сеппа
Microsoft
ADO.NET
Москва 2003
И. Рг Vг ГI ГL IfII H А Л Р FL U Д А КИ IЦI И11 (л
УДК 004.45 ББК 32.973.26-018.2 С28
Сеппа Д. С28
Microsoft ADO.NET/Пер. с англ. — М.: Издательско-торговый дом «Русская Редакция», 2003- — 640 стр.: ил. ISBN 5-7502-0223-2 Эта книга представляет собой достаточно полный справочник по библиотекам ADO.NET, поставляемым с Microsoft .NET Framework. Вы узнаете об основных объектах модели ADO.NET и научитесь сохранять, искать, фильтровать и сортировать данные с использованием этих объектов. Структура книги позволяет последовательно изучить ADO.NET «с нуля* или, если вы опытный разработчик, быстро найти ответы на интересующие вас вопросы. Здесь рассматриваются как подсоединенные, так и отсоединенные объекты, в том числе DataAdapter.DataSet, Connection, Command, Transaction, DataReader и др. Отдельный раздел книги посвящен созданию эффективных Windows- и Web-приложений для доступа к БД с использованием ADO.NET Книга адресована прежде всего разработчикам приложений с поддержкой Web для доступа к БД при помощи Microsoft Visual Studio .NET и ADO.NET, а также всем, кто интересуется новой объектной модели ADO.NET Книга состоит из 14 глав, приложений и предметного указателя. Прилагаемый компакт-диск содержит все примеры программ книги с исходными текстами и электронную версию книги с интегрированной системой поиска. УДК 004.45 ББК 32.973-26-018.2 Подготовлено к изданию по лицензионному договору с Microsoft Corporation, Редмонд. Вашингтон, США. Microsoft, Microsoft Press, MSDN, Visual Basic, Visual C*. Visual InterDev, Visual Studio, Windows и Windows NT являются товарными знаками или охраняемыми товарными знаками корпорации Microsoft в США и/или других странах. Все друтие товарные знаки являются собственностью соответствующих фирм. Все названия компаний, организаций и продуктов, а также имена лиц, используемые в примерах, вымышлены и не имеют никакого отношения к реальным компаниям, организациям, продуктам и лицам.
© Оригинальное издание на английском языке, Microsoft Corporation, 2002 © Перевод на русский язык, Microsoft Corporation, 2003 BN 0-7356-1423-7 (англ.) ISBN 5-7502-0223-2
© Оформление и подготовка к изданию, издательско-торговый дом «Русская Редакция», 2003
Оглавление Благодарности
XXI
Введение
XXII
Кому предназначена эта книга Структура книги Примеры кода, утилиты и прочие забавные штуки Системные требования Техническая поддержка Ч А С Т Ь
XXII XXII XXIII ХХШ XXIV
1
ОСНОВЫ РАБОТЫ С MICROSOFT ADO.NET
i
Глава 1 Обзор ADO.NET
2
Зачем нужна новая объектная модель Объектная модель ADO.NET Поставщики данных .NET Зачем необходимы отдельные классы и библиотеки Повышенная производительность Замечательные возможности расширения Пролиферация Обсуждение поставщиков данных .NET в этой книге Подсоединенные объекты Объект Connection Объект Command Объекты DataReader Объект Transaction Объект Parameter Объект DataAdapter Отсоединенные объекты Объект DataTable Объект DataColumn Объект Constraint Объект DataRow Объект DataSet Объект DataRelation Объект Data View Метаданные Объекты DataSet со строгим контролем типов Вопросы, которые стоит задавать почаще
3 4 6 7 7 7 8 8 8 9 9 9 10 10 10 Н 12 12 1-4 14 15 16 17 18 19 20
Глава 2 Создание ADO.NET-приложений с помощью мастера DataForm Wizard
21
Все без ума от примеров Создание связанной с данными формы средствами мастера Data Form Wizard Выбор подключения Выбор таблиц БД Создание отношений в объекте DataSet
21 22 24 27 27
VI
Оглавление
Выбор отображаемых столбцов Выбор стиля отображения Использование новой связанной с данными формы Отображение данных в связанной форме Изучаем сгенерированный мастером код Реализация каскадных изменений с помощью объекта DataRelation Передача изменений в базу данных Вызываем метод Update объекта DataAdapter Изоляция измененных записей Реинтеграция изменений Панель компонентов Мастер Data Form Wizard — отправная точка создания приложений для работы с БД Вопросы, которые стоит задавать почаще Ч А С Т Ь
28 29 30 31 31 32 33 34 35 35 36 38 38
2
ПОДКЛЮЧАЕМСЯ: ИСПОЛЬЗОВАНИЕ ПОСТАВЩИКА ДАННЫХ .NET
39
Глава 3
40
Подключение к базе данных
Использование объектов Connection Создание объектов Connection Конструкторы Строки подключения Поставщик OLE DB для БД SQL Server Поставщик OLE DB для БД Oracle Поставщик OLE DB для БД Access Поставщик OLE DB для драйверов ODBC Создание строк подключения в коде при помощи диалогового окна Data Links Создание строк подключения вручную при помощи диалогового окна Data Links Использование .udl-файлов в строке подключения Открытие и закрытие соединений Использование пула соединений Что такое пул соединений Как включить пул соединений Что делать, если не надо помещать соединения в пул Как узнать, действительно ли закрыто соединение или оно просто помещено в пул Разрыв соединений Создание других объектов с помощью объектов Connection Создание объектов Command Выполнение транзакций Получение информации схемы БД Функции среды Visual Studio .NET, доступные в период разработки Работа с объектами Connection в окне Server Explorer Добавление соединений в Server Explorer Поставщики данных OLE DB, ODBC и .NET Сохранение пароля Интегрированная проверка подлинности Добавление соединений в приложение
41 44 44 4б 46 47 47 47 48 49 49 50 50 51 52 53 53 53 54 54 55 56 58 58 60 60 61 62 62
Оглавление
VII
Панель компонентов Использование нового соединения Создание объектов Connection с помощью панели инструментов Использование нового соединения в период выполнения Где же код? Особенности объекта OleDbConnection Свойства объекта OleDbConnection Свойство Connections tring Свойство ConnectionTimeout Свойства Database и DataSource Свойство Provider Свойство ServerVersion Свойство State Методы объекта OteDbConnection Метод BeginTransaction Метод ChangeDatabase Метод Close Метод CreateCommand Метод GetOkDbSchemaTable Метод Open Метод ReleaseObjectPool События объекта OleDbConnection Событие InfoMessage Событие StateChange Вопросы, которые стоит задавать почаще
62 63 63 64 65 66 67 67 67 68 68 68 69 70 70 71 72 72 72 74 75 75 76 78 79
Глава 4 Выполнение запросов к базе данных
81
Использование объектов Command в коде Создание объекта Command Выполнение запроса, не возвращающего записей Просмотр результатов запроса с помощью объекта DataReader Выборка результатов Ускоренная выборка Выборка нескольких наборов результатов Выполнение пакетов командных запросов Закрытие объекта DataReader Выполнение запроса, возвращающего одно значение Выполнение параметризованного запроса Вызов хранимой процедуры Получение данных при помощи параметров вывода Выполнение запроса в транзакции Создание объектов Command в Visual Studio .NET Перетаскивание с панели инструментов Задание значения свойства Connection Использование Query Builder Использование нового объекта Command в коде Перетаскивание из Server Explorer Особенности объектов Command DataReader и Parameter Свойства объекта OleDbCommand Свойство CommandTimeout Свойство Command Type Свойство Parameters ..
81 81 82 85 86 86 89 90 92 93 94 96 98 99 100 101 101 102 104 105 106 106 107 107 109
VIII
Оглавление
Свойство Transaction Свойство UpdatedRowSource Методы объекта OleDbCommand Метод Cancel МетодExecuteNonQuery Метод ExecuteReader Метод ExecuteScalar Метод Prepare Метод ResetCommandTirneoui Свойства объекта OleDbDataReader Свойство Depth и метод GetData Свойство FieldCount Свойство IsClosed Свойство Item Свойство RecordsAffected Методы объекта OleDbDataReader Метод Read Метод GetValue Методы СеКТипДанных> Метод GetValues Метод NextResult Метод Close Методы GetName. GetOrdinal и GetDataTypeName Метод GetSchemaTable Метод GetData и свойство Depth Создание объектов Parameter Свойства объекта OleDbParameter Свойство ParameterName Свойство Direction Свойство Value Свойства SourceColumn и SourceVersion Свойства DbType и OleDb Type Свойства Precision, Scale и Size Вопросы, которые стоит задавать почаще
109 109 110 110 Ill Ill 113 113 П4 114 114 114 114 114 115 115 115 116 116 116 117 117 118 118 119 119 120 120 121 121 122 122 122 123
Глава 5 Получение данных с помощью объектов DataAdapter
133
Что представляет собой объект DataAdapter Чем объект DataAdapter отличается от других объектов, основанных на запросах Объект DataAdapter предназначен для работы с отсоединенными данными Между объектами DataAdapter и DataSet нет прямой связи Объект DataAdapter содержит логику обновления для передачи в БД изменений, хранящихся в объекте DataSet Логикой обновления в объекте DataAdapter можно управлять Анатомия объекта DataAdapter Дочерние команды Набор TableMappings Создание и использование объектов DataAdapter Создание объекта DataAdapter Конструкторы DataAdapter
134 134 134 135 135 135 137 137 138 139 139 140
Оглавление
IX
Получение результатов запроса 141 Использование метода Fill 141 Создание объектов DataTable и DataColumn при помощи метода Fill ... 142 Использование перегруженных методов Fill 143 Открытие и закрытие соединений 144 Многократный вызов метода Fill 145 Сопоставление результатов запроса объекту DataSet 14б Набор TableMappings объекта DataAdapter 14б Свойство MissingMappingAction 148 Работа с пакетными запросами 148 Получение результатов выполнения хранимых процедур 149 Хранимые процедуры Oracle 150 Выборка информации схемы 150 СвойствоMissingSchemaAction ISO Метод FillScherna 151 Создание объектов DataAdapter в Visual Studio .NET 151 Перетаскивание объекта DataAdapter с панели инструметов 151 Использование мастера Data Adapter Configuration Wizard 151 Перетаскивание из Server Explorer 155 Предварительный просмотр результатов, возвращаемых объектом DataAdapter 157 Просмотр кода, генерируемого мастером 157 Особенности объекта DataAdapter 158 Свойства объекта DataAdapter 158 Свойства SelectCommand, UpdateCommand, InsertCommand и DeleteCommand 159 Свойство TableMappings 159 Свойства MissingMappingAction и MissingSchemaAction 161 Свойство AcceptChangesDuringFill 162 Свойство ContinueUpdateOnError 162 Методы объекта DataAdapter 163 Метод Fill 163 Метод FillSchema 166 Метод GetFillParameters 1б7 Метод Update 1б7 События объекта DataAdapter 168 Событие FiltError 168 События RowUpdacing и RowUpdated 1б9 Вопросы, которые стоит задавать почаще 171
Ч А С Т Ь
3
АВТОНОМНАЯ РАБОТА С ДАННЫМИ: ОБЪЕКТ DATASET МОДЕЛИ ADO.NET
173
Глава 6
174
Работа с объектами DataSet
Возможности объекта DataSet Работа с отсоединенными данными Прокрутка, сортировка, поиск и фильтрация Работа с иерархически организованными данными Кэширование изменений Интеграция с XML Универсальная функциональность
174 175 175 175 176 176 176
Оглавление
Использование объектов DataSet Создание объекта DataSet Просмотр структуры, создаваемой при вызове метода DataAdapter.Fill Объект DataTable Объект DataColumn Просмотр данных, возвращаемых объектом DataAdapter Объект DataRow Просмотр содержимого объекта DataRow Просмотр объектов DataRow в объекте DataTable Проверка данных в объекте DataSet Свойства объекта DataColumn, используемые для проверки данных ... Набор Constraints объекта DataTable Получение информации схемы с помощью метода DataAdapter.FillSchema Создание объектов DataTable в коде Создание объекта DataTable Добавление объекта DataTable в набор Tables объекта DataSet Добавление столбцов в объект DataTable Указание типа данных объекта DataColumn Добавление первичного ключа Добавление других ограничений Использование столбцов с автоинкрементом Добавление столбца, основанного на выражении Создание объектов DataTable, соответствующих таблицам Customers, Orders и Order Details Изменение содержимого объекта DataTable Добавление нового объекта DataRow Редактирование существующей записи Работа со значениями Null в объекте DataRow Удаление объекта DataRow Исключение объекта DataRow Использование свойства DataRow.RowState Просмотр отложенных изменений объекта DataRow Работа с объектами DataSet в среде Visual Studio .NET Генерирование объекта DataSet на основе объектов DataAdapter Создание нового объекта DataSet «с нуля» Создание объекта DataSet без контроля типов Особенности объектов DataSet, DataTable, DataColumn, DataRow, UniqueConstraint и ForeignKeyConstraint Свойства объекта DataSet Свойство CaseSensitive Свойство DataSetName Свойство DesignMode Свойство EnforceConstraints Свойство ExtendedProperties Свойство HasErrors Свойство Locale Свойства Namespace и Prefix Свойство Relations Свойство Tables ..
17" 177 177 178 178 179 180 181 181 182 183 184 185 186 186 186 187 188 188 190 192 196 197 200 200 202 204 205 206 206 208 211 211 214 217 221 221 221 222 222 222 222 223 223 224 224 224
Оглавление
XI
Методы объекта DataSet Методы AcceptChanges и RejectChanges Методы Beginmit и Endlnit Метод Clear Методы Clone и Сору Метод GetChanges Методы GetXmJ и GetXmlSchema Метод HasChanges Метод Merge Методы ReadXtnl и Wri teXml Методы ReadXmlSchema, WriteXmlSchema и InferXrnlSchema Метод Reset События объекта DataSet Событие MergeFailed Свойства объекта DataTable Свойство CaseSensitive Свойства ChildReiations и ParentRelations Свойство Columns Свойство Constraints Свойство DataSet Свойство DefaultView Свойство DesignMode Свойство ExtendedProperties Свойство HasErrors Свойство Locale Свойство MinimumCapacity Свойства Namespace и Prefix Свойство PrimaryKey Свойство Rows Свойство TableName Методы объекта DataTable Методы AcceptChanges и RejectChanges Методы Beginlnit и Endlnit Методы BeginLoadData и EndLoadData ! Метод Clear Методы Clone и Сору Метод Compute Метод GetChanges Метод GetErrors Методы ImportRow, LoadDataRow и NewRow Метод Reset Метод Select События объекта DataTable : События ColumnChanged и ColumnChanging События RowChanged и RowChanging События RowDeleted и RowDeJeting Свойства объекта DataColumn Свойство AllowDBNull Свойства Autolncrement, AutoIncrementSeed и AutoIncrementStep
224 225 226 226 226 226 227 227 227 227 2.27 227 228 228 228 229 229 229 229 230 230 230 230 230 231 231 231 231 232 232 232 233 233 233 233 234 234 235 235 235 236 236 236 236 237 237 237 238 238
XII
Оглавление Свойство Caption Свойство ColumnMapping Свойство ColumnName Свойство DataType Свойство DefaultValue Свойство Expression Свойство ExtendedProperties Свойство MaxLength Свойства Namespace и Prefix Свойство Ordinal Свойство Readonly Свойство Table Свойство Unique Свойства объекта DataRow Свойство HasErrors Свойство Item Свойство ItemArray Свойство RowError Свойство RowState Свойство Table Методы объекта DataRow Методы AcccptChanges и RejectChanges Методы BeginEdit, CancelEdit и EndEdit Метод ClearErrors Метод Delete Метод GetChildRows Методы GetColumnError и SetColumnError Метод GetColumnsInErrar Методы GetParentRow, GetParentRows и SetParentRow Метод HasVersion
238 239 239 239 240 240 241 241 242 242 242 242 242 243 243 243 243 244 244 244 244 245 245 247 247 247 247 248 248 249
Метод IsNull Свойства объекта UniqueConstraint Свойство Columns Свойство ConstraintName Свойство ExtendedProperties Свойство IsPrimaryKey Свойство Table Свойства объекта ForeignKeyConstraint Свойства AcceptRejectRule, DeleteRule и UpdateRuie Свойства Columns и RelatedColumns Свойство ConstraintName Свойство ExtendedProperties Свойства RelatedTable и Table Вопросы, которые стоит задавать почаще
Глава 7 Работа с реляционными данными Особенности доступа к реляционным данным Соединяющие запросы Отдельные запросы Иерархичные объекты Recordset модели ADO
249 :
250 250 250 251 251 251 251 251 252 252 252 252 252
255 256 256 257 258
Оглавление
XIII
Объекты DataReiation модели ADO.NET Работа с объектами DataReJation в коде Создание объектов DataReiation Поиск связанных данных Метод GetChildRows объекта DataRow Метод GetParentRow объекта DataRow Метод GetParentRows объекта DataRow Выбор версии данных для просмотра Проверка данных средствами объектов DataReiation Создание ограничений Использование имеющихся ограничений Смотри-ка! Нет ограничений! Объекты DataReiation, ссылающиеся на себя Отношения «многие ко многим» Использование объектов DataReiation в объектах DataColumn. основанных на выражениях Каскадирование изменений Свойства DeleteRule и UpdateRule объекта ForeignKeyConstraint Постепенный отказ от соединяющих запросов Создание объектов DataReiation в Visual Studio .NET Добавление объекта DataReiation в объект DataSet со строгим контролем типов Добавление объекта DataReiation в объект DataSet без контроля типов Особенности объекта DataReiation Свойства объекта DataReiation Свойство ChildColumns Свойство ChildKeyConstrain! Свойство ChildTable Свойство DataSet Свойство ExtendedProperties Свойство Nested Свойство ParentColumns Свойство ParentKeyCorestraint Свойство ParentTable Свойство RelationName Вопросы, которые стоит задавать почаще
259 260 260 262 263 263 264 2(55 266 266 267 268 269 2~ 1
Глава 8
289
Сортировка, поиск, фильтрация
274 277 277 278 279 279 280 281 281 281 282 282 282 282 282 284 284 284 284 284
Возможности поиска и фильтрации объекта DataTable Поиск записи по значениям первичного ключа Динамичный поиск Поиск по шаблону Использование символов-разделителей Использование дополнительных методов Select Указание порядка сортировки Указание нужного состояния искомых записей Что представляет собой объект Data View Объекты Data View возвращают данные из объектов DataTable Объекты Data View не являются SQL-запросами Использование объектов Data View в коде Создание объектов Data View Использование свойства RowStateFilter .. ..
289 289 292 293 293 295 295 296 297 297 298 298 299 300
XIV
Оглавление
Использование объекта DataRowView Просмотр всех записей данных, доступных через объект DataView Поиск данных в объекте DataView Метод Find Метод FindRows Модифицирование объектов DataRowView Создание объектов DataView в Visual Studio .NET Добавление нового объекта DataView в конструктор Задание значений свойств объекта DataView Особенности объекта DataView Свойства объекта DataView Свойства AllowDelete, AUowEdit и AllowNew Свойство ApplyDefaultSort Свойства Count и Item Свойство DataViewManager Свойство RowFilter Свойство RowStateFilter Свойство Sort Свойство Table Методы объекта DataView Методы AddNew и Delete Методы Beginlnit и Endlnit МетодСоруТо Методы Find и FindRows Метод GetEnumerator Событие ListChanged объекта DataView Свойства объекта DataRowView Свойство DataView^ Свойства IsEdit и IsNew Свойство Item Свойство Row Свойство RowVersion Методы объекта DataRowView Методы BeginEdit, CancelEdit и EndEdit Метод CreateCMdView Метод Delete Вопросы, которые стоит задавать почаще
300 301 303 303 304 305 306 306 306 307 307 307 308 308 309 309 309 310 ЗЮ 310 310 3И ЗП 312 313 314 315 315 315 315 315 316 316 316 316 317 317
Глава 9 Работа с объектами DataSet со строгим контролем типов
323
Создание объектов DataSet со строгим контролем типов Сложный способ Использование метода WriteXmlSchema объекта DataSet Использование утилиты XML Schema Definition Tool Простой способ Где же находится файл класса? Использование объектов DataSet со строгим контролем типов Добавление записи Поискзаписи Редактирование записи
324 324 324 325 326 328 329 329 331 .. 332
Оглавление
Работа со значениями NULL Работа с иерархичными данными Прочие возможности объектов DaraSet, DataTable и DataRow Когда стоит использовать объекты DataSet со строгим контролем типов Программные компоненты и швейцарские армейские ножи Преимущества периода разработки Преимущества периода выполнения Что еще следует учесть Осуществление структурных изменений Преобразование объектов DataSet Возможность генерирования данных без контроля типов Выбор способа разработки Вопросы, которые стоит задавать почаще
Глава 10
Передача обновлений в базу данных
Урок истории Преимущества передачи обновлений с использованием объектов Recordset ADO Недостатки передачи обновлений с использованием объектов Recordset ADO Передача обновлений с помощью объектов Command модели ADO.NET Передача обновлений с использованием объектов DataAdapter ADO.NET Конфигурирование объектов DataAdapter вручную Связанные параметры Передача обновлений с использованием хранимых процедур Использование собственной логики обновления Преимущества Недостатки Создание логики обновления с помощью объекта CommandBuilder Как объект CommandBuilder генерирует логику обновления Преимущества и недостатки использования объекта CommandBuilder Создание логики обновления средствами мастера Data Adapter Configuration Wizard Просмотр структуры объекта DataAdapter Параметры генерации логики обноачения Передача обновлений с помощью хранимых процедур Преимущества и недостатки использования мастера Прочие проблемы обновления Способы оптимистичного управления параллелизмом Использование только полей первичного ключа Использование всех полей в разделе WHERE Использование полей первичного ключа и полей типа timestamp Использование полей первичного ключа и измененных полей Работа со значениями NULL Передача обновлений в транзакциях Использование набора TableMappings Лучший способ обновления Особенности объекта OleDbCommandBuilder Свойства объекта OleDbCommandBuilder Свойство DataAdapter .' Свойства QuotePrefix и QuoteSuffix Методы объекта OleDbCommandBuilder
XV
333 334 335 336 336 336 338 340 340 341 342 343 344
346 349 350 350 352 362 362 363 367 373 373 374 374 375 37(5 377 378 379 379 382 382 383 383 383 385 385 386 388 391 394 395 395 395 395 396
XVI
Оглавление
Метод DeriveParameters Методы GetDeleteCommand, GetlnsertCommand и GetUpdateCommanct Метод RefreshSchema Вопросы, которые стоит задавать почаще
396 397 397 398
Глава 11 Сложные случаи обновления данных
404
Обновление отображаемого содержимого записи после передачи изменений Получение новых значений поля timestamp после передачи обновления Выборка данных при помощи пакетных запросов после передачи обновления Свойство UpdatedDataSource объекта Command Выборка новых данных с помощью параметров вывода Выборка данных с помощью события RowUpdated объекта DataAdapter после передачи обновления Приложение Timestamp Выборка новых значений автоинкремента Работа с SQL Server Функция @@IDENTITY и ключевое слово SCOPEJDENTITY Работа с Access 2000 Работа с последовательностями Oracle Генерирование фиктивных значений для объектов DataColumn последовательности Приложение, выбирающее значения автоинкремента Использование ключевого слова NOCOUNT SQLServer Передача иерархичных изменений Передача записей, ожидающих вставки и удаления Передача иерархичных изменений с помощью метода Select объекта DacaTable Передача иерархичных изменений с помощью метода GetChanges . . , . Работа со значениями автоинкремента и реляционными данными Изоляция и повторная интеграция изменений Экономное использование полосы пропускания при помощи метода GetChanges Метод Merge объекта DataSet Метод Merge и свойство RowState Метод Merge и значения автринкремента Изящная обработка неудачных попыток обновления Опережающее планирование KOI 1фликтов Свойство ContinueUpdateOnError объекта DataAdapter Информирование пользователя об ошибках Выборка текущего содержимого конфликтующих записей Если сначала не получилось Импорт «новых оригинальных* значений с помощью метода DataSet.Merge Приложение Conflicts Работа с распределенными транзакциями Координаторы транзакций и диспетчеры ресурсов Двухфазная фиксация Распределенные транзакции в .NET Framework Поддержка распределенных транзакций в БД Создание собственных компонентов
405 406 406 407 407 408 410 410 411 413 414 414 416 417 418 420 420 421 423 423 425 426 428 431 431 438 438 438 439 441 442 442 443 444 445 446 446 447 447
Оглавление
Атрибут TransactionOption Регистрация ADO.NET-соединения в транзакции Подтверждение или отмена результатов работы Упрощенная работа с распределенными транзакциями Приложение DistributedTransaction Прочие преимущества использования служб Component Services В сложных случаях обновления данных всегда используйте ADO.NET Вопросы, которые стоит задавать почаще
Глава 12 Работа с XML-данными Мост через пропасть между XML и доступом к данным Запись и считывание XML-данных Методы объекта DataSet для работы с XML-данными Метод GetXml Методы WriteXml и ReadXml Методы WriteXmlSchema, ReadXmlSchema и InferXmlSchema Формирование схем Свойства ADO.NET. влияющие на схему XML-документа Имена элементов и атрибутов Элементы или атрибуты: что выбрать? Вложенные реляционные данные Пространства имен и префиксы Кэширование изменений и XML-документы XML-документы формата diffgram DataSet + XmlDocurnent = XmlDataDocument Использование объекта XmlDataDocument Работа с объектом DataSet как с XML-документом Кэширование обновлений XML-документа Получение XML-данных из БД SQL Server 2000 Использование запросов SELECT...FOR XML , Выполнение запроса SELECT...FOR XML в SQL Server Query Analyxer Загрузка результатов запроса в объект DataSet Загрузка результатов запроса в объект XmlDocurnent Поставщик данных SQL XML .NET Data Provider Загрузка данных в объект XmlDocurnent с помощью объекта SqlXmlCommand Загрузка данных в объект DataSet с помощью объекта SqlXmLVdapter Использование шаблонов запросов Выполнение шаблонных запросов с использованием объекта SqlXmlCommand Параметризованные шаблонные запросы Работа с запросами XPath Добавление информации схемы XSLT-трансформация Передача обновлений Логика обновления, используемая объектом SqlXmlCommand Простой пример с использованием ADO.NET и XML Два пути к одному конечному пункту ADO.NET и XML счастливая пара Вопросы, которые стоит задавать почаще
XVII
448 449 449 450 451 452 452 452
454 454 4~>5 455 455 456 458 459 460 460 461 461 461 461 462 463 464 46-1 465 467 467 4б7 468 469 470 471 472 473 474 475 476 476 478 479 481 482 484 484 484
XVIII
Оглавление
Ч А С Т Ь
4
СОЗДАНИЕ ЭФФЕКТИВНЫХ ПРИЛОЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ ADO.NET
487
Глава 13 Создание эффективных Windows-приложений
488
Быстрое создание пользовательского интерфейса при помощи связывания с данными Этап 1. Создание объектов DataAdapter и DataSet Этап 2. Добавление связанных с данными элементов управления TextBox Этап 3. Получение данных Этап 4- Добавление кнопок для перемещения по содержимому объекта DataSee Этап 5. Добавление кнопок Add и Delete Этап 6. Передача изменений в БД Этап 7. Добавление кнопок Edit. Update и Cancel Этап 8. Просмотр дочерних данных Получение только списка заказанных клиентом товаров Добавление объекта DataTable Order Details в класс DataSet со строгим контролем типов Добавление элемента управления DataGrid, отображающего дочерние данные Передача в БД изменений из обеих таблиц Этап 9- Связывание второй формы с тем же источником данных Этап 10. Совершенствование пользовательского интерфейса Добавление функции поиска с использованием элемента управления ComboBox Управление форматом связанных данных Этап 11. Если хочешь сделать что-то хорошо Резюме: связывание с данными Проблемы разработки приложений Выборка только необходимых данных Стратегии обновления Мгновенные и кэшированные обновления Повторная выборка перед разрешением изменений ADO.NET и пессимистическое управление блокировками Стратегии подключения Подсоединение и отсоединение Пул соединений Работа с данными больших двоичных объектов Отложенная выборка ВЮВ-данных Обработка ВЮВ-данных с помощью объектов DataSet Обработка ВЮВ-данных с помощью объектов DataReader Двоичные BLOB-данные в БД Northwind Пример приложения для работы с ВЮВ-данными Пользовательские интерфейсы, созданные с применением тяжелой артиллерии ADO.NET Вопросы, которые стоит задавать почаще
Глава 14
488 490 491 493 494 496 498 499 501 502 502 503 504 505 508 508 509 512 513 513 513 513 514 514 515 517 517 518 519 519 519 520 522 523 524 524
Создание эффективных Web-приложений
Краткий обзор Web-приложений ASP.NRTупрощает разработку Web-приложений
528 :
528 528
Оглавление
XIX
Преимущества и недостатки способа без хранения сведений о состоянии . . . . 529 Забывчивый сервер, молчаливый клиент 529 Подключение к БД 530 Использование доверенных соединений 530 Подмена пользователей 531 Работа с БД Access 532 Вывод данных на Web-странице 532 Использование метода DataBinder.Eval 533 Связывание элемента управления TextBox с объектом DataSet 5 34 Связывание элемента управления TextBox с объектом DataReader 535 Связывание элементов управления DataGrid с результатами запросов 535 Связывание элемента управления DataGrid с объектом DataSet 536 Связывание элемента управления DataGrid с объектом DataReader 5 36 Кэширование данных между обращениями к БД 537 Способ без хранения сведений о состоянии 538 Преимущества 538 Недостатки 538 Кэширование данных на стороне клиента 538 Файлы cookie 538 Скрытые поля 539 Свойство ViewState 540 Хранение сведений о состоянии на стороне Web-сервера 541 Свойство Session 541 Объект Application 542 Объект Cache 542 Кэширование вывода 543 Хранение сведений о состоянии в БД 543 Преимущества 544 Недостатки 544 Рекомендации по хранению сведений о состоянии 54 :> Хранение данных в объектах ViewState 545 Хранение данных в объекте Application 545 Хранение данных в объекте Session 545 Хранение данных в БД 545 Кэширование вывода 545 Постраничная разбивка информации 546 Средства постраничного предстаачения информации, предоставляемые Web-элементом управления DataGrid 546 Свойство AllowPaging 547 Свойства AllowCustomPaging и VirtualltemCount 547 Средства постраничного представления информации, предоставляемые методом РШ объекта DataAdapter 549 Создание запросов, возвращающих страницу данных 550 Приложение PagingOptions 551 Редактирование данных на Web-странице 551 Упрощение редактирования данных при помощи элемента управления DataGrid 551 Обработка событий, связанных с редактированием содержимого DataGrid , . . 553 Передача изменений в БД 554 Внесение изменений в объект DataSet 554 Создание собственных запросов UPDATE 555 Приложение ShoppingCart 555 Вопросы, которые стоит задавать почаще 555
XX
Оглавление
ПРИЛОЖЕНИЯ Приложение А
559 Прочие поставщики данных .NET
Поставщик данных SQL Client .NET Data Provider Именованные параметры и маркеры параметров Подключение к БД SQL Server с помощью объекта SqlConnection Получение результатов запроса с помощью объекта SqlDataAdapter Использование объектов SqlCommand и SqlDataReader Методы GetSqKTMn£aHHbix> и пространство имен SqlTypes Вызов хранимых процедур Получение информации схемы БД Поставщик данных ODBC .NET Data Provider Подключение к БД при помощи объекта OdbcConnection Использование параметризованных запросов Получение результатов запроса с помощью объекта OdbcDataAdapter Просмотр результатов запроса при помощи объекта OdbcDataReader Вызов хранимой процедуры Получение информации схемы БД Поставщик данных Oracle Client .NET Data Provider Подключение к БД при помощи объекта OracleConnection Использование параметризованных запросов Получение результатов запроса с помощью объекта OracleDataAdapter Просмотр результатов запроса при помощи объекта OracleDataReader Специфичные для Oracle типы данных Вызов хранимой процедуры Выборка данных из курсоров REF Получение информации схемы БД Проблемы, общие для поставщиков данных .NET Создание кода, независимого от поставщика Выбор подходящего типа данных поставщика .NET
Приложение Б Утилиты Утилита ADO.NET Ad Hoc Query Tool Подключение к БД Добавление поставщиков данных .NET Выполнение запросов Просмотр информации схемы Передача обновлений Параметры приложения Утилита ADO.NET DataAdapter Builder Определение логики обновления Элемент управления ADO.NET Navigation Control Добавление элемента управления ADO.NET Navigation Control на панель инструментов Visual Studio .NET Конфигурирование свойств ADO.NET Navigation Control, обеспечивающих взаимодействие с данными Конфигурирование прочих свойств ADO.NET Navigation Control
Предметный указатель Об авторе
560 560 5бО 5б1 562 5бЗ 564 566 567 568 569 569 570 570 571 573 573 573 574 574 575 576 577 578 580 580 580 584
587 587 588 588 589 589 592 592 592 593 595 595 595 596"
597 607
Благодарности
П,
.режде всего, благодарю своих мать и отца за терпение и поддержку, оказанную мне при работе как над этой книгой, так и вообще в жизни. Спасибо Джеки Ричарде (Jackie Richards), которая воодушевляла и работала в тесном контакте со мной, а также провела подробное исследование возможностей объектной модели ADO.NET. Благодарю Сэма Карпентера (Sam Carpenter), который вытаскивал меня выпить пива, веселил и раскрыл мне некоторые перспективы моего проекта, и Брэда Родеса (Brad Rhodes), написавшего предисловие к этой книге. Хочу сказать спасибо Стиву ДюМошу (Steve DuMosch), который останавливал мои напыщенные речи, случавшиеся время от времени, и убедил снова надеть бутсы для мини-футбола. Благодарю Стива Эллиса (Steve Ellis), направлявшего меня при работе над этим проектом, докторов Джонатана и Стефани Брэмен (Dr. Jonathan and Stephanie Braman), вместе с которыми я в начале года совершил путешествие на Мег, и которые приютили мою приставку Х-Вох на несколько месяцев. Благодарю и поздравляю команды Microsoft WebData и Visual Studio .NET, которые создали и в срок выпустили самый мощный набор технологий доступа ЕС данным. А также спасибо коллективу Microsoft Press, сыгравшему значительную роль в том, что сей труд появился на полках книжных магазинов.
Введение
Модель Microsoft ADO.NET — большой шаг вперед в области технологий доступа к данным Microsoft. Она предоставляет разработчику беспрецедентные возможности для управления взаимодействием кода и данных — приятное усовершенствование для тех программистов, которые мечтали об этом, используя предыдущие технологии на основе «черного ящика*, такие, как ядро курсоров ADO, среда Microsoft Visual Studio 6 Data Environment и поставщик MSDataShape OLE DB Provider. ADO.NET — не только самая мощная и надежная технология доступа к данным, предлагаемая сегодня разработчикам компанией Microsoft. Она характеризуется невероятно крутой кривой обучения: изучать ее трудно, но те. кто эти трудности преодолели, в один голос говорят о значительном повышении производительности труда. Разработчики, познакомившиеся с основами объектной модели, по-прежнему задают вопросы, касающиеся нюансов ADO.NET, например: «Как управлять именами таблиц, при помощи которых объект DataAdapter сопоставляет результаты моего пакетного запроса с моим объектом DataSefi* или «Почему, когда я использую созданный мной объект DafaSet, при повторном заполнении в нем появляются дублирующие записи, а в случае с объектом DataSet, созданным средой Visual Studio .NET, этого не происходит?*.
Кому предназначена эта книга Эта книга — исчерпывающее руководство по ADO.NET, предназначенное всем разработчикам, даже не имеющим опыта работы с данной технологией. Я не предполагал, что вы знакомы с объектом DataReader из класса DataSet. Структура книги позволяет последовательно изучать ADO.NET «с нуля» или, если вы опытный разработчик, быстро найти ответы на интересующие вас вопросы.
Структура книги Все главы, посвященные определенному объекту или их группе, начинаются с описания соответствующего объекта/объектов, далее приводятся инструкции по использованию их основных функций. Почти во всех главах я рассказываю, как сэкономить время на разработку, создавая объекты средствами Visual Studio .NET. Каждая глава содержит справочную информацию по обсуждаемому объекту и раздел «Вопросы, которые стоит задавать почаще», где я рассматриваю вопросы, важность которых разработчики зачастую недооценивают. Иногда справочная информация частично повторяет сведения предыдущих глав. Полагаю, это необходимо, т. к. позволяет избежать «прыганья* взад-вперед по главам. Книга делится на четыре части. В первой приводится обзор объектной модели ADO.NET и инструкции по работе с мастером Data Form Wizard. Вторая часть
Введение
XXIII
посвящена использованию различных объектов, предоставляемых поставщиками данных .NET — Connection, Command, DataReader и DataAdapter. В третьей части обсуждаются «отсоединенные* объекты модели ADO.NET — DataSet, DataTable, DataColumn, DataRotv, DataRelation и DataView. Кроме того, рассматриваются простые и сложные случаи передачи изменений в базу данных при помощи объекта DataAdapter, а также функции ADO.NET для работы с XML-данными. Четвертая часть посвящена способам эффективной разработки Windows- и Web -приложений с использованием ADO.NET.
Примеры кода, утилиты и прочие забавные штуки В большинстве приведенных в книге фрагментов кода используется поставщик OLE DB .NET Data Provider. Я выбрал его потому что из двух поставщиков данных .NET, входящих в состав Microsoft .NET Framework, он более «универсальный». Фрагменты кода рассчитаны на взаимодействие с Microsoft Desktop Engine (MSDE) и Microsoft SQL Server, но их можно изменить для работы с любой БД, для которой имеется поставщик OLE DB. В приложении А приведены фрагменты кода, использующие поставщиков SQL Client .NET Data Provider, ODBC .NET Data Provider и Oracle Client .NET Data Provider. Приложение Б посвящено трем утилитам, записанным при прилагаемом компакт-диске: Ad Hoc Query (утилита для выполнения произвольных запросов к БД), DataAdapter Builder (утилита, генерирующая логику- обновления для объекта DataAdapter^ и элемент для перемещения по содержимому объекта DataSet, предназначенный для Windows-форм. Все утилиты могут работать с любым поставщиком данных .NET, а не только с поставщиками SQL Client .NET Data Provider и ODBC .NETData Provider. На компакт-диске, прилагаемом к книге, также записан исходный код каждой утилиты. Он рассчитан на разработчиков, которые захотят изменить функциональность утилит; тем не менее распространять этот код в какойлибо форме или каким-либо образом не рекомендуется. Я не занимаюсь технической поддержкой данных утилит, и они не предназначены для распространения. Эти утилиты нельзя считать готовым продуктом, и в мои планы входит расширить их функциональность, устранить неполадки в работе и выложить копии утилит на сайт http://ivunv.ininety.cont, где они будут доступны всем, кто приобрел издание этой книги. На прилагаемом к книге компакт-диске записаны фрагменты кода, три вышеупомянутые утилиты, а также электронная оригинальная версия книги с интегрированной системой поиска. Приведенные в книге фрагменты кода также доступны по адресу http://ivww.microsoft.com/mspress/books/5354-asp. Чтобы открыть страницу со ссылками на файлы с фрагментами кода, щелкните ссылку «Companion Content» в меню, в правой части экрана. Надеюсь, эта информация вам пригодится.
Системные требования Для работы с файлами примеров и программами, записанными на прилагаемом компакт-диске, ваш компьютер должен соответствовать приведенной ниже минимальной конфигурации:
XXIV
Введение
• пакет инструментальных средств разработчика Microsoft .NET Framework SDK (доступен по адресу bttp://msdn.microsoft.com/ne£); • среда разработки Visual Studio .NET (необязательна, но рекомендуется); • бразуер Microsoft Internet Explorer версии 5.01 или более поздней.
Техническая поддержка Издательский коллектив приложил все усилия, чтобы обеспечить точность информации книги и содержимого прилагаемого компакт-диска. Microsoft Press принимает поправки к книге по адресу http://mspress.microsoft.com/support. Напрямую подключиться к Базе знаний Microsoft и задать вопрос по интересующей вас проблеме можно по адресу http://www.microsoft.com/mspress/support/search.asp. Если у вас есть комментарии, вопросы или идеи, связанные с этой книгой, присылайте их в Microsoft Press одним из следующих способов. Обычной почтой: Microsoft Press Microsoft ADOMET (Core Reference) Editor One Microsoft Way Redmond, WA 98052-6399 Электронной почтой:
[email protected] Учтите, что поддержка продукта не предоставляется по указанным адресам. Подробнее о поддержке ADO.NET, Visual Basic .NET, Visual C* .NET, Visual Studio .NET и .NET Framework — на Web-узле Microsoft Press Support по адресу http://support.microsoft.com.
Ч А С Т Ь
1
ОСНОВЫ РАБОТЫ С MICROSOFT ADO.NET
Г Л А В А
1 Обзор ADO.NET
/\DO.NET — это набор библиотек, поставляемый с Microsoft .NET Framework и предназначенный для взаимодействия с различными хранилищами данных из .NETприложений. Библиотеки ADO.NET включают классы для подсоединения к источнику данных, выполнения запросов и обработки их результатов. Кроме того, ADO.NET можно использовать в качестве надежного, иерархически организованного, отсоединенного кэша данных для автономной работы с данными. Главный отсоединенный объект, DataSet, позволяет сортировать, осуществлять поиск, фильтровать, сохранять отложенные изменения и перемещаться по иерархичным данным. Кроме того, объект DataSet включает ряд функций, сокращающих разрыв между традиционным доступом к данным и программированием с использованием XML. Теперь разработчики получили возможность работать с XML-данными через обычные интерфейсы доступа к данным и наоборот. Если вкратце, при создании приложений для работы с данными нужно использовать ADO.NET В Microsoft Visual Studio .NET есть ряд функций, предназначенных для создания приложений для доступа к данным. Одни из них экономят время на разработку, генерируя большие объемы скучного кода. Другие повышают производительность ваших приложений, добавляя метаданные и логику обновления в код вместо того, чтобы выбирать эту информацию в период выполнения. Хотите верьте, хотите — нет, но большинство функций доступа к данным, предоставляемых Visual Studio .NET, выполняют сразу обе этих задачи. Говоря о ADO.NET, мы также рассмотрим в этой книге и средства Visual Studio .NET, экономящие время и усилия на разработку.
ГЛАВА 1 Обзор ADO.NET
Зачем нужна новая объектная модель Разработчики, имеющие опыт работы с предыдущей технологией доступа к данным Microsoft, ActiveX Data Objects (ADO), могут спросить; «Разве ADO нужна не для того же? Зачем Microsoft создала новую технологию доступа к данным?» Похоже, что в каждой последующей версии Visual Basic реализуется новая модель доступа к данным. В Visual Basic 3-0 появилась технология Data Access Objects (DAO). В Visual Basic 4.0 - Remote Data Objects (RDO). В Visual Basic 5 и Visual Studio 97 OBDCDirect. В Visual Basic 6 и Visual Studio 6 — ADO. Некоторые разработчики рассматривают эти непрерывные изменения как тонко завуалированную попытку продавать техническую литературу. На самом же деле Microsoft разрабатывала новые технологии доступа к данным для удовлетворения постоянно растущих потребностей разработчиков. Изначально DAO предназначалась для взаимодействия с БД на основе локальных файлов. Вскоре разработчикам захотелось взаимодействовать с серверными БД типа Microsoft SQL Server и Oracle. DAO позволяла взаимодействовать с ними, однако программистам требовались более широкие возможности управления и большая производительность. Тогда группа разработчиков Visual Basic создала RDO — быстрый и простой уровень доступа к данным, предназначенный для взаимодействия с серверными БД. Конечно, мощь RDO впечатляла, но не хотелось в то же время отказываться и от и простоты DAO. Microsoft создала технологию ODBCDirect, попытавшись объединить в ней лучшие стороны RDO и DAO. Появился Интернет, и понадобилась модель доступа к данным, простая для работы в серверных сценариях, которая требовала бы меньше кода и позволяла бы клиент)-- и серверу обмениваться структурами данных. В ответ на эту потребность появилась ADO. За прошедшие несколько лет ADO верой и правдой послужила многим разработчикам, однако ей не хватает некоторых ключевых функций, необходимых для создания мощных приложений. Например, все больше и больше разработчиков хотят работать с XML-данными. И хотя в последних версиях ADO появились соответствующие функции, сама технология изначально не рассчитана на взаимодействие с такими данными. Так, ADO не позволяет отделить информацию схемы от фактических данных. Возможно, Microsoft добавит в будущие версии ADO дополнительные XML-функции, однако ADO никогда не будет обрабатывать XMLданные так же эффективно, как это делает ADO.NET. Это обусловлено тем, что ADO.NET создавалась с учетом, a ADO — без учета XML. Ядро курсоров ADO позволяет передавать отсоединенные объекты Recordset между различными уровнями приложения, но объединить содержимое нескольких таких объектов нельзя. В ADO разрешено передавать каптированные изменения в БД, однако нет средств управления логикой обновления. Кроме того, ядро курсоров ADO, например, не позволяет передавать отложенные изменения в БД при помощи хранимых процедур. Поскольку многие администраторы БД позволяют пользователям изменять содержимое БД только средствами хранимых процедур, большинство лишено возможности передавать изменения через объект Recordset ADO. Microsoft разработала ADO.NET для решениях этих, а также некоторых других проблем, подробно о которых я расскажу в этой книге.
Часть 1 Основы работы с Microsoft ADO.NET Как и ее предшественницы, ADO предназначалась для разработки приложений на основе технологии СОМ. С появлением общеязыковой среды выполнения (common language runtime) и .NET Framework Microsoft полностью пересмотрела среду Visual Studio. Теперь мы имеем пакеты Windows Forms и Web Forms, рассчитанные на работу в общеязыковой среде выполнения. Кроме того, Microsoft разработала для .NET Framework новую объектную модель доступа к данным — ADO.NET. ADO.NET объединяет в себе вес лучшие стороны своих предшественниц и реализует функции, наиболее часто требуемые разработчикам: расширенную поддержку XML, упрощенный доступ к отсоединенным данным, расширенные возможности управления обновлениями и повышенную гибкость обновлений.
Объектная модель ADO.NET Мы уже достаточно сказали о назначении ADO.NET и ее месте в архитектуре Visual Studio .NET, теперь самое время познакомить вас с этой технологией поближе. Здесь дается краткий обзор объектной модели ADO.NET и ее отличий от предыдущих технологий доступа к данным Microsoft. Назначение ADO.NET — помогать разработке эффективных многоуровневых приложений для работы с БД в интрасетях и Интернете, для чего она и предоставляет все необходимые средства. На рис. 1-1 показаны классы, составляющие объектную модель ADO.NET. Пунктирная линия делит модель на две части. Объекты в левой части называются подсоединенными (connected). Для управления соединением, транзакциями, выборки данных и передачи изменений они взаимодействуют непосредственно с БД. Объекты в правой части называются отсоединенными (disconnected), они позволяют работать с данными автономно. Подсоединенные объекты
OataAdapter -
Рис. 1-1.
Иерархия объектов ADO.NET
Отсоединенные объекты
ГЛАВА 1
Обзор ADO.NET
Пространства имен Пространство имен — это логическое объединение объектов. Размеры .NET Framework велики, и чтобы упростить разработку приложений на ее основе, Microsoft разделила объекты на пространства имен. На рис. 1-2 показана часть иерархия пространств имен в ,NET Framework,
Рис. 1-2. Пространства имен в ,NET Framework Самая частая причина использования пространств имен — профилактика коллизий имен в сборках. Благодаря наличию разных пространств имен, программисты, работающие над разными компонентами, составляющими одно решение, могут использовать одинаковые имена для разных элементов. Поскольку эти имена разделены, в период компиляции они не мешают друг другу. Более практичное применение пространств имен — то, что группировка упрощает поиск объектов. Иногда я не помню точное имя нужного мне объекта. Если бы в ,NET Framework не было предусмотрено деление объектов на небольшие пространства имен, мне пришлось бы искать требуемый объект в общем алфавитном списке. К счастью, я обычно знаю пространство имен необходимого мне объекта. Найти объект в нем гораздо проще, поскольку требуется просмотреть меньше записей, Подробнее об использовании пространств имен в Microsoft .NET Framework и Visual Studio .NET — в документации AfSDN. Объекты, составляющие отсоединенную часть модели ADO.NET. не взаимодействуют напрямую с подсоединенными объектами. Это — значительное отличие от предыдущих объектных моделей доступа к данным Microsoft. В ADO объект Recordset хранит результаты запросов. Можно вызвать его метод Open, чтобы выбрать результаты запроса, и затем с помощью метода Update (или UpdateBatcfo} передать изменения из Recordset в БД. Объект DataSet ADO.NET (подробнее о нем — далее) по функциональности сравним с объектом Recordset ADO. Тем не менее DataSet не взаимодействует с БД. Для выборки данных из БД в объект DataSet последний передают методу Fill подсоединенного объекта ADO.NET — DataAdapter. Аналогичным образом для пере-
Часть I Основы работы с Microsoft ADO.NET
дачи отложенных изменений из DataSet в БД объект DataSet нужно передать методу DataAdapter-.Update.
Поставщики данных .NET Поставщик данных .NET — это набор классов, предназначенных для взаимодействия с хранилищем данных определенного типа. .NET Framework включает два поставщика — SQL Client .NET Data Provider и OLE DB .NET Data Provider. Поставщик OLE DB .NET Data Provider позволяет взаимодействовать с различными хранилищами данных посредством поставщика OLE DB. Поставщик SQL Client .NET Data Provider рассчитан исключительно на взаимодействие с БД SQL Server версии 7 или более поздней. Каждый поставщик данных .NET реализует одинаковые базовые классы — Connection, Command, DataProvider, Parameter и Transaction, конкретное имя которых зависит от поставщика. Так, у поставщика SQL Client .NET Data Provider есть объект SqlConnection, а у поставщика OLE DB .NET Data Provider — объект QleDbConnection. Независимо от типа поставщика, объект Connection реализует одинаковые базовые функции посредством одних и тех же базовых интерфейсов. Чтобы открыть соединение с хранилищем данных, создайте экземпляр объекта Connection поставщика, задайте значение свойства ConnectionString и затем вызовите метод Сопnection.Open. У каждого поставщика данных .NET — собственное пространство имен. Оба поставщика из состава .NET Framework относятся к пространству имен SystemData, где находятся отсоединенные объекты. Поставщик OLE DB .NET Data Provider находится в пространстве имен SystemData.QleDb, а поставщик SQL Client .NET Data Provider — в пространстве имен SystemDataSqlCUent. Поставщики данных .NET реализуют одинаковые базовые функции, и поэтому создаваемый вами код выглядит примерно одинаково вне зависимости от поставщика. Как видно из следующих фрагментов кода, все, что требуется для перехода от поставщика OLE DB .NET Data Provider к поставщику SQL Client .NET Data Provider, — заменить класс, экземпляр которого создается, и привести строку подключения в соответствие требованиям поставщика, Visual Basic .NET 'Открываем и закрываем соединение 'с использованием поставщика OLE DB .NET Data Provider Dim cnQleOb As New OleDbConnection cnOleDb.ConnectionString = "Provider=SQLOLEDB; "Data Source=(local);InitialCatalog=Northwind;..." cndleDb.OpenO cnOleDb.CloseQ 'Открываем и закрываем соединение 'с использованием поставщика SQL Client .NET Data Provider Dim cnSql As New SqlConnection cnSql.ConnectiorStrinQ = "Data Source=(local);" & "Initial Catalog=Northwind;..."
ГЛАВА 1 Обзор ADO.NET cnSql.OpenO cnSql.Close()
Visual C# //Открываем и закрываем соединение //с использованием поставщика OLE ВО .NET Data Provider OleDbConnection cnOleDb = new OleDbConnectionO; enOleDb.ConnectionString = "Provider=Provider=SQLOLEDB; "Data Source=(local);InitialCatalog=Northwlnd;..."; enOleDb.OpenO;
cnOleDb.Closef); //Открываем и закрываем соединение //с использованием поставщика SQL Client .NET Data Provider SqlConnection cnSql = new SqlConnectionC); cnSql,ConnectionString = "Data Source=(local);" + "Initial Catalog=Northwind;..."; cnSql.Open(); cnSql.Closet);
Зачем необходимы отдельные классы и библиотеки В предыдущих технологиях доступа к данным Microsoft нет отдельных библиотек и классов для разных хранилищ данных. Многие разработчики спрашивают, почему Microsoft решилась на такое значительное изменение. Основных причин три: производительность, возможность расширения и пролиферация. Повышенная производительность Как переход на использование поставщиков данных .NET повышает производительность? Создавая ADO-код, вы, по сути, используете интерфейсы ADO в качестве «посредника» при взаимодействии с БД. Вы указываете ADO, какой поставщик следует использовать, и ADO передает ему все ваши вызовы. Поставщик выполняет необходимые действия и возвращает вам результат через библиотеку ADO. У поставщиков данных .NET промежуточный уровень отсутствует. Вы обращаетесь непосредственно к поставщику, и тот взаимодействует с хранилищем данных при помощи низкоуровневых интерфейсов программирования последнего. Взаимодействие с SQL Server в ADO.NET при помощи поставщика SQL Client .NET Provider происходит быстрее, чем в ADO при помощи OLE DB-поставщика SQL Server, поскольку в ADO.NET задействовано на один уровень меньше.
Замечательные возможности расширения Когда в SQL Server 2000 появились функции для работы с XML-данными, команде разработчиков ADO пришлось решать интересную проблему. Для реализации в ADO функций, позволяющих выбирать XML-данные из БД SQL Server, требовалось добавить новые интерфейсы в OLE DB API и поставщик SQL Server OLE DB Provider.
Часть I Основы работы с Microsoft ADO.NET Расширить функциональность поставщиков данных .NET гораздо проще. Им требуется поддерживать лишь единые базовые интерфейсы и при необходимости предоставлять дополнительные, специфические для отдельных поставщиков функции. Объект Command (SqlCommancf) поставщика SQL Client .NET Data Provider предоставляет такие же методы и свойства, как и его аналог в поставщике OLE DB .NET Data Provider, а также реализует метод для выборки содержимого запроса в виде XML-данных, Пролиферация В состав пакета Microsoft Data Access Components (MDAC) версии 2.0, появившегося в июле 1998 г., входили OLE DB-поставщики для SQL Server, Microsoft Access и Oracle. Microsoft и другие команды разработчиков создали родные OLE DB-noставщики для взаимодействия с другими хранилищами данных, однако такие поставщики существуют далеко не для всех хранилищ. Если вы работаете с ADO и не используете OLE DB-поставщик от Microsoft, высока вероятность того, что вы применяете ODBC-драйвер (технология, предшествовавшая OLE DB), коих великое множество хотя бы из-за того, что их легко создавать. Многие разработчики сочли создание собственных OLE DB-поставщиков слишком сложным делом. По сравнению с OLE DB-поставщиками и ODBC-драйверами написать поставщик данных .NET достаточно просто. Требуется реализовать меньше интерфейсов. Microsoft упростила процесс создания поставщиков для ADO.NET, чтобы облегчить разработчикам проектировку поставщиков данных .NET. Чем больше поставщиков данных .NET, тем к большему числу различных источников данных можно обращаться средствами ADO.NET.
Обсуждение поставщиков данных .NET в этой книге Каждый поставщик данных .NET реализует одни и те же базовые интерфейсы, и поэтому рассматривать использование интерфейсов отдельных поставщиков данных .NET не требуется, В этой книге мы обсудим поставщик OLE DB .NET Data Provider. Я выбрал именно его потому, что он входит в состав .NET Framework, предоставляет базовые, независимые от поставщика функции и очень гибок в использовании — с его помощью удается взаимодействовать с любой БД, у которой есть родной OLE DB-поставщик. Рассматриваемые функции поставщика OLE DB .NET Data Provider также предоставляются и другими поставщиками, причем за исключением специально оговоренных случаев они работают аналогичным образом. Подробнее о возможностях поставщиков данных .NET — в приложении Б. В главе 12 демонстрируются некоторые XML-функции ADO.NET, доступные при помощи поставщиков SQL Client .NET Data Provider и SQL XML .NET Data Provider. Далее по тексту я буду называть объекты, общие для всех управляемых поставщиков, по имени, независимому от поставщика, например DataAdapter, а не OleDbDataAdapter или SqlDataAdapter.
Подсоединенные объекты Объектная модель ADO.NET включает классы, предназначенные для непосредственного взаимодействия с источником данных. Такие объекты, показанные на рис. 1-1
ГЛАВА 1 Обзор ADO.NET
слева от пунктирной линии, я буду называть подсоединенными. Большинство из них представляют базовые концепции доступа к данным, например физическое соединение с БД, запрос и результаты запроса. Объект Connection Объект Connection представляет соединение с источником данных. С помощью свойств этого объекта можно задать тип источника, его расположение и другие атрибуты. Объект Connection примерно эквивалентен объект)-' Connection ADO и объекту Database DAO, он применяется для соединения и отсоединения от БД. Connection выступает в качестве канала, по которому другие объекты, например DataAdapter и Command, взаимодействуют с БД для передачи изменений и выборки их результатов.
Объект Command Объекты Command по структуре аналогичны объектам Command KDO и QueryDef ОАО. Они могут представлять запрос к БД, вызов хранимой процедуры или прямой запрос на возврат содержимого конкретной таблицы. БД поддерживают множество разных типов запросов. Одни запросы возвращают записи данных, ссылаясь на одну или несколько таблиц или представлений или вызывая хранимую процедуру. Другие запросы изменяют записи данных, а все прочие — управляют структурой БД, создавая и изменяя такие объекты как таблицы, представления и хранимые процедуры. С помощью объекта Command удается выполнить любой из этих запросов к БД. Выполнение запроса к БД с использованием объекта Command осуществляется очень просто. Задайте свойству Connection одноименный объект, соединяющийся с БД, и затем задайте свойству CommandText текст запроса. Можно ввести обычный SQL-запрос, например: SELECT CustomerlD, CompanyName, ContactName,
Phone FROM Customers
Можно также указать имя таблицы, представления или хранимой процедуры и средствами свойства CommandType задать тип выполняемого запроса. Объект Command позволяет выполнять запрос разными способами. Если запрос не возвращает записи, вызовите метод ExecuteNonQuery. Метод ExecuteReader возвращает объект DataReader, позволяющий просматривать возвращенные запросом записи. У объекта SqlCommand есть третий метод выполнения, ExecuteXmlReader, который аналогичен методу ExecuteReader, но предназначен для работы с запросами, возвращающими результаты в формате XML
Объекты DataReader Объект DataReader предназначен для максимально быстрой выборки и просмотра возвращаемых запросом записей. Этот объект позволяет просматривать результаты запроса по одной записи за раз. При переходе к следующей записи содержимое предыдущей записи отбрасывается. Объект DataReader не поддерживает обновление, и возвращаемые им данные доступны только для чтения. Поскольку DataReader реализует лишь ограниченный набор функций, он очень прост и имеет высокую производительность. 2-5958
10
Часть I
Основы работы с Microsoft ADO.NET
Объект Transaction Иногда несколько изменений содержимого БД требуется сгруппировать и обрабатывать как единицу работы. В программировании БД такая единица называется транзакцией (transaction), Предположим, ваша БД содержит банковскую информацию и включает таблицы сберегательных и текущих счетов. Если пользователь захотел перевести денежные средства со сберегательного на текущий счет, в коде следует убедиться, что снятие средств со сберегательного счета и внесение их на текущий счет успешно завершились как одна единица работы или что не было сделано ни одного из изменений. Для этого и предназначена транзакция. У объекта Connection есть метод BeginTransaction, позволяющий создавать объекты Transaction. С помощью объекта Transaction удается подтвердить или отменить все коррективы, сделанные в ходе транзакции. В нашем примере изменения обоих счетов включены в одну транзакцию, и следовательно, эта процедура подтверждается или отмененяется как одна единица работы.
Объект Parameter Скажем, вам требуется выбрать из БД Orders все заказы конкретного клиента. Запрос выглядит так: SELECT CustomerlD, CompanyName, CompanyName, Phone FROM Customers WHERE CustomerlD = 'ALFKI'
Значение поля CustomerlD в разделе WHERE запроса зависит от клиента, заказы которого требуется просмотреть. Однако при использовании такого запроса его текст придется менять каждый раз, когда потребуется просмотреть заказы другого клиента. Чтобы упростить выполнение аналогичных запросов, можно заменить значение поля CustomerlD маркером параметра: SELECT CustomerlD, CompanyName, CompanyName, Phone FROM Customers WHERE CustomerlD = ?
Затем, перед выполнением запроса, следует указать значение параметра. Многие разработчики интенсивно используют параметризованные запросы, поскольку они упрощают программирование и повышают эффективность кода. Для применения параметризованного объекта Command создайте объекты Parameter, соответствующие всем параметрам запроса, и добавьте их в набор Parameters объекта Command. Объект Parameter ADQ.NE1 предоставляет свойства и методы, позволяющие определить тип данных и значение параметров. Для работы с хранимой процедурой, возвращающей данные посредством параметров вывода, задайте свойству Direction объекта Parameter соответствующее значение из перечисления ParameterDirection.
Объект DataAdapter Объект DataAdapter представляет новую концепцию моделей доступа к данным Microsoft; у него нет близкого эквивалента в ADO и ОАО, хотя объекты Command ADO и QueryDefDAO можно считать его двоюродными братьями. DataAdapter — это своеобразный мост между БД и отсоединенными объектами модели ADO.NET. Метод DataAdapter Fill предоставляет эффективный механизм
ГЛАВА 1 Обзор ADO.NET
11
выборки результатов запроса в объект DataSet или DataTable для последующей автономной работы с ними. Кроме того, объекты DataAdapter позволяют передавать отложенные изменения из объектов DataSet в БД. Объект DataAdapter ADO.NET предоставляет несколько свойств, фактически являющихся объектами Command. Так, свойство SelectCommand содержит объект Command, представляющий собой запрос для заполнения объекта DataSet. Кроме того, у DataAdapter есть свойства InsertCommand, UpdateCommand и DeleteCommand. соответствующие объектам Command для передачи новых, измененных и удаленных записей в БД. Эти объекты Command предоставляют функциональность обновления, которая в объектах Recordset ADO и ОАО была автоматической. Например, когда вы выполняете в ADO запрос для создания объекта Recordset, ядро курсоров ADO запрашивает у БД метаданные о запросе, чтобы определить происхождение результатов. Затем ADO на основе метаданных создает логику обновления, чтобы преобразовать изменения объекта Recordset в изменения содержимого БД, Так для чего же объекту DataAdapter ADO.NET требуются отдельные свойства UpdateCommand, InsertCommand и DeleteCommand"! Они позволяют определять собственную логику обновления. Функциональность обновления в ADO и ОАО достаточно ограничена в том плане, что обе объектные модели преобразуют изменения объектов Recordset в командные запросы, непосредственно ссылающиеся на таблицы БД. Для обеспечения безопасности и целостности данных многие администраторы БД ограничивают доступ к таблицам БД, позволяя изменять их содержимое только с помощью хранимых процедур. В отличие от объекта DataAdapter ADO.NET, модели ADO и DAO не умеют передавать изменения средствами хранимых процедур и не позволяют вам определить собственную логику обновления. Свойствам UpdateCommand, InsertCommand и DeleteCommand объекта DataAdapter можно задать вызов хранимой процедуры, изменяющей, добавляющей или удаляющей записи в соответствующей таблице БД. Затем следует вызвать метод DataAdapter.Update, и ADO.NET с помощью созданных вами объектов Command передаст изменения из DataSet в БД, Как уже говорилось, DataAdapter заполняет таблицы в объекте DataSet, а также считывает каптированные изменения и передает их в БД. У DataAdapter имеется несколько свойств, позволяющих вести мониторинг всех этих операций. Набор TableMappings — это свойство, позволяющее отслеживать сопоставление таблиц БД с таблицами DataSet. У каждого сопоставления таблиц имеется аналогичное свойство для сопоставления столбцов — набор ColumnMappings.
Отсоединенные объекты Вы узнали, как с помощью различных объектов поставщика данных .NET подключиться к источнику данных, выполнить запросы и просмотреть их результаты. Тем не менее эти подсоединенные классы позволяют просматривать данные только в виде непозиционируемого, доступного только для чтения потока. Что, если вам требуется сортировать, выполнять поиск, фильтровать или изменять результаты запросов?
12
Часть I Основы работы с Microsoft ADO.NET
Объектная модель ADO.NET включает классы, предоставляющие такую функциональность. Они выступают в качестве автономного кэша данных. Выбрав результаты запроса в объект DataTable (подробнее о нем — чуть далее), можно закрыть соединение с источником данных и продолжить работу с данными. Как уже говорилось, этим объектам не требуется живое соединение с источником данных, и поэтому они называются отсоединенными (disconnected objects). Давайте рассмотрим отсоединенные объекты модели ADO.NET. Объект DataTable Объект DataTable ADO.NET аналогичен объектам Recordset ADO и DAO. Он позволяет просматривать данные в виде наборов записей и столбцов. Чтобы поместить результаты запроса в объект DataTable, применяют метод DataAdapterFill:
Visual Basic .NET Dim strSQL As String = "SELECT CustomerlD, CompanyName FROM Customers" Dim strConn As String = "Provider=SQLQLEDB;Data Source=(local);..." Dim daCustomers As New 01eDbDataAdapter(strSQL, strConn) Dim tblCustomers As New DataTableO daCustomers,Fill(tblCustomers)
Visual C# string strSQL = "SELECT CustomerlD, CompanyName FROM Customers"; string strConn = "Provider=SQLOLEDB;Data Source=(local);..." OleDbDataAdapter daCustomers = new QleDbDataAdapterCstrSQL, strConn); DataTable tblCustomers = new DataTableO; daCustomers.Fill(tblCustomers); Данные, выбранные из БД и помещенные в объект DataTable, считаются отсоединенными от сервера. Содержимое DataTable можно просматривать, не генерируя сетевого трафика между ADO.NET и БД. При автономной работе с данными живое соединение с БД не требуется, но помните: вы также не увидите изменений, внесенных другими пользователями после того, как вы выполнили исходный запрос. Класс DataTable содержит наборы других отсоединенных объектов, которые мы рассмотрим в последующих разделах. Для обращения к содержимому DataTable используют свойство Roivs, которое возвращает набор объектов DataRow. Чтобы просмотреть структуру DataTable, воспользуйтесь свойством Columns и получите набор объектов DataColumn. Класс DataTable также позволяет определять на своем содержимом различные ограничения, например первичный ключ. Обращаться к этим ограничениям можно посредством свойства Constraints объекта DataTable. Объект DataColumn У каждого объекта DataTable есть набор Columns, представляющий собой контейнер объектов DataColumn. Как видно из его названия, объект DataColumn соответствует столбцу таблицы. Тем не менее в действительности DataColumn содержит не данные, хранящиеся в объекте DataTable, а информацию о структуре столбца. Такая разновидность информации называется метаданными (metadata). Напри-
ГЛАВА 1 Обзор ADO.NET
13
мер, объект DataColumn предоставляет свойство Туре, описывающее тип данных (скажем, string или integer) столбца. У DataColumn также есть другие свойства, например Readonly, AHowDBNutt, Unique, Default и Autolncrement, позволяющие управлять порядком обновления данных столбца, ограничить диапазон допустимых значений поля и определить порядок генерации значений для новых записей данных. Класс DataColumn также предоставляет свойство Expression, с помощью которого удается определить порядок вычисления данных в столбце. Распространенная практика — основывать используемый в запросе столбец на выражении, а не на содержимом столбца таблицы БД. Например, в БД Northwind, поставляемой с большинством продуктов для работы с БД Microsoft, все записи таблицы Order Details содержат поля UnitPrice и Quantity. Если в структуру данных требуется добавить общую стоимость заказанных единиц товара, то обычно в запрос следует добавить вычисляемое поле. Следующий SQL-запрос определяет вычисляемое поле с именем ItemTotal. SELECT OrderlD, ProductlD, Quantity, UnitPrice, Quantity * UnitPrice AS ItemTotal FROM [Order Details]
Недостаток такого способа в том, что БД вычисляет значение только при выполнении запроса. Если изменить в объекте DataTable содержимое поля UnitPrice или Quantity, на значении поля ItemTotal это не отразится. Класс ADO.NET определяет свойство Expression, позволяющее изящно разрешить данную ситуацию. Когда вы проверяете значение основанного на выражении объекта DataColumn, ADO.NET вычисляет это выражение и возвращает полученное значение. Таким образом, если обновить значение одного из используемых в выражении столбцов, результат пересчитывается. Вот два фрагмента кода, демонстрирующих использование свойства Expression: Visual Basic .NET Dim col As New DataColumnO With col .ColumnName = "ItemTotal"
.DataType = GetType(Decimal) .Expression = "UnitPrice * Quantity" End With
Visual C# QataColumn col = new DataColumnO;
col,ColumnName = "ItemTotal"; col.DataType = typeof(Decimal); col.Expression = "UnitPrice * Quantity"; Набор Columns и объекты DataColumn можно ориентировочно сопоставить набору Fields и объектам Field ADO и DAO.
14
Часть I Основы работы с Microsoft ADO.NET
Объект Constraint Класс DataTable также позволяет определять на локальном содержимом объектов DataTable различные ограничения. Например, можно создать объект Constraint, гарантирующий, что значений поля или нескольких полей будут уникальны в пределах DataTable. Объекты Constraint хранятся в наборе Constraints объекта DataTable. Объект Dataflow Обратиться к реальным значениям, хранящимся в объекте DataTable, позволяет набор Rows, содержащий объекты DataRow. Чтобы просмотреть содержимое конкретного поля определенной записи, воспользуйтесь свойством Нет соответствующего объекта DataRow и считайте значение нужного поля. Класс DataRow предоставляет несколько перегруженных определений свойства Пет. Выбрать поле для просмотра можно, передав свойству Item объекта DataRow имя, порядковый номер или сопоставленный с полем объект DataColumn. Item — свойство объекта DataRow по умолчанию, и поэтому его разрешается использовать неявно: Visual Basic .NET Dim row As DataRow row = MyTable.Rows(O) Console.WriteLine(row(O)) Console.WriteLine(row("CustomerlD"}) Console.WriteLineC row(MyTable.Columns("Customs rID"))) Visual C# DataRow row; row = MyTable.Rows[0]; Console.WriteLineCrow[0]); Console.WriteLine(row["CustomerlD"]); Console.WriteLineCrow[MyTable.Columns["CustomerlD"]]); Вместо того чтобы вернуть содержимое только текущей записи, DataTable предоставляет через набор Rows содержимое всех записей данных. Это заметно отличается от поведения объектов Recordset ADO и DAO, которые предоставляют одновременно только одну запись данных и требуют перемещаться по их содержимому с помощью методов типа MoveNext. Следующий фрагмент кода в цикле перемещается по содержимому объекта Recordset ADO: «Классический» Visual Basic Dim strConn As String, strSQL As String Dim rs As ADODB.Recordset strConn = "Provider=SQLOLEDB;Data Source=(local);..." strSOL = "SELECT CustomerlD, CompanyName FROM Customers" Set rs = New ADODB.Recordset rs.CursorLocation = adUseClient rs.Open strSQL, strConn, adOpenStatic, adLockReadOnly, adCmdText Do While Not rs.EOF
ГЛАВА 1 Обзор ADO.NET
15
MsgBox rs("CustomerID") rs.MoveNext Loop
Для просмотра содержимого объекта DataTable ADO.NET следует перемещаться по объектам DataRow из набора Rows-.
Visual Basic .NET Dim strSOL, strConn As String Dim da As New 01eDbDataAdapter(str$QL, strConn} Dim tbl As New DataTableO
da.Fill(tbl) Dim row As DataRow For Each row In tbl.Rows Console.WriteLine(row(0}) Next row Visual C# string strSQL, strConn; OleDbDataAdapter da = new OleDbDataAdapterfstrSQL, strConn); DataTable tbl = new DataTableO; da.Fill(tbl); foreach (DataRow row in tbl.Rows) Console.WriteLine(row[0]);
Объект DataRow является также отправной точной обновления. Например, можно вызвать метод DataRowBeginEdit, изменить посредством свойства Item значение одного из полей записи и затем вызвать метод EndEdit, чтобы сохранить сделанное изменение. Метод CancelEdit объекта DataRow отменяет все изменения, сделанные за текущий сеанс редактирования. Кроме того, DataRow предоставляет методы для удаления элементов из набора Rows, Когда вы изменяете содержимое записи, DataRoiv кэширует эти изменения, чтобы позже передать их в БД. Таким образом, при изменении значения поля записи DataRow хранит оригинальное и текущее значения поля, что обеспечивает успешное обновление содержимого БД. При наличии отложенных изменений свойство Item объекта DataRow позволяет просматривать оригинальные значения полей. Объект DataSet Как следует из имени, объект DataSet содержит набор данных. DataSet можно рассматривать в качестве контейнера объектов DataTable (хранящихся в наборе Tables объекта DataSet). Помните: ADO.NET предназначена упростить разработку крупных многоуровневых приложений для работы с БД. Иногда требуется обратиться к компоненту на промежуточном сервере и выбрать содержимое нескольких таблиц. Вместо того чтобы многократно обращаться к серверу и выбирать данные по одной таблице за раз, можно поместить все данные в объект DataSet и вернуть
16
Часть I Основы работы с Microsoft ADO.NET
его за один вызов. Тем не менее объект DataSet — нечто больше, чем просто контейнер объектов DataTable. Данные в объекте DataSet отсоединены от БД. Все изменения данных просто кэшируются в объектах DataRow. Когда придет время передать эти изменения в БД, вероятно, окажется неэффективным пересылать промежуточному серверу весь объект DataSet, Стоит воспользоваться методом GetChanges и извлечь из DataSet лишь измененные данные. В результате снизится объем данных, передаваемых между процессами и серверами. Объект DataSet также предоставляет метод Merge, дополняющий метод GetCbanges. Сервер промежуточного уровня, с помощью которого вы передаете изменения в БД, может, используя возвращенный методом Merge объект DataSet, вернуть DataSet с только что выбранными из БД данными. Метод Merge позволяет объединить содержимое двух DataSet в один объект. Это еще один пример, подтверждающий, что при разработке ADO.NET учитывалась архитектура многоуровневых приложений. В предыдущих моделях доступа к данным Microsoft аналогичной функции нет. Создать объект DataSet и заполнить его набор Tables удается и без взаимодействия с БД. В предыдущих моделях доступа к данным перед локальным добавлением новых записей требовалось сначала выполнить запрос к БД и затем позже передать эти новые записи в БД. В ADO.NET взаимодействие с БД требуется только тогда, когда необходимо передать новые записи. Кроме того, DataSet предоставляет функции чтения и записи в файл и область памяти. Можно сохранить только содержимое объекта DataSet, только его структуру или и то, и другое. ADO.NET хранит эти данные в виде XML-документа. Поскольку ADO.NET и XML тесно связаны, перенос данных из объектов DataSet ADO.NET в XML-документы и обратно осуществляется очень быстро. Это позволяет воспользоваться одной из наиболее мощных функций языка XML — его способностью легко преобразовывать структуру данных. Так, применив XSLT-шаблон, удается преобразовать данные XML-документа в HTML-код.
Объект DataRelation Обычно таблицы БД тем или иным образом связаны между собой. Например, в БД Northwind каждой записи таблицы Orders соответствует запись таблицы Customers, что позволяет определить, кто какие заказы разместил. В приложении вам, скорее всего, потребуется использовать связанные данные из нескольких таблиц. Для решения этой задачи применяют объект DataRelation, предоставляемый объектом DataSet, Класс DataSet определяет свойство Relations, представляющее собой набор объектов DataRelation. Объект DataRelation позволяет определить отношение между объектами DataTable из состава DataSet. Создав объект DataRelation, с помощью аналогичного следующему кода выбирают массив объектов DataRow с записями о заказах, размещенных конкретным клиентом:
Visual Basic .NET Dim dsNorthwind As DataSet Dim rowCustomer, rowOrder As DataRow
ГЛАВА 1 Обзор ADO.NET
17
'Код, создающий объект DataSet dsNorthwind.Relations.Add("CustomersOrders", dsNorthwind.Tables("Customers"). ColumnsC "CustomerlD"), dsNorthwind.Tables( "Orders"), ColumnsC''CustomerlD")) For Each rowCustomer In dsNorthwind.Tablesf"Customers").Rows Console.WriteLineC'Orders for customer " 4 rowCustomer("CompanyName")) For Each rowOrder In rowCustomer.GetChildRows("CustomersOrders") Console.WriteLine(vbTab 4 rowOrder("OrderID"}) Next rowOrder Next rowCustomer
Visual C# DataSet dsNorthwind; //Создаем и инициализируем объект DataSet dsNorthwind,Relations.Add("CustomersQrders", dsNorthwind. Tables["Customers"].Coluinns["CustomerlD"], dsNorthwind. Та t)les["Orders"].ColtJmns["CustomerID"]); foreach (DataRow rowCustomer in dsNorthwind.Tables["Customers"].Rows) { Console.WriteLine("Orders for customer " •+ rowCustomer["CompanyName"].ToString()); foreach (DataRow rowOrder in rowCustomer.GetChildRows("CustomersOrders")) Console.WriteLine('\f + rowOrder["OrderID"].ToString()); }
Кроме того, объекты DataRelation предоставляют свойства, позволяющие обеспечить ссылочную целостность. Например, DataRelation можно сконфигурировать так, чтобы изменение значения первичного ключа родительской записи автоматически каскадировалось дочерним записям. Объект DataRelation, кроме того, удается настроить таким образом, что при удалении записи в родительском объекте DataTable автоматически удаляются соответствующие записи дочернего объекта DataTable,
Объект DataView Выбрав результаты запроса в объект DataTable, можно посредством объекта DataView просматривать это содержимое разными способами. Если содержимое DataTable требуется упорядочить по определенному полю, задайте имя этого поля свойству Sort объекта DataView. Кроме того, свойство Filter объекта DataView позволит вам выводить только записи, удовлетворяющие заданным критериям. Просматривать содержимое одного объекта DataTable можно одновременно посредством нескольких объектов DataView. Предположим, у вас на форме есть две сетки: одна с упорядоченным по алфавиту полным списком клиентов, а другая — с тем же списком, но упорядоченным по другому полю, например по обла-
Часть I Основы работы с Microsoft ADO.NET era или по городу. Для вывода представлений свяжите обе сетки с разными объектами DataView, ссылающимися на одинаковый объект DataTable. Благодаря этой возможности, исключается необходимость хранить две одинаковые копии данных в разных структурах. Подробнее об этом — в главе 8. Метаданные ADO и ОАО позволяют создавать объект Recordset на основе результатов, возвращенных запросом. Ядро доступа к данным просматривает столбцы данных набора результатов и на основе этой информации заполняет набор Fields, объекта Recordset, задавая имена, типы данных и т.д. ADO.NET предоставляет вам альтернативу. Можно написать пару строк кода и предоставить ADO.NET автоматически определять структуру результатов или написать код большего объема, включающий метаданные о структуре результатов вашего запроса. Почему выбирают способ, при котором требуется писать больший объем кода? В основном из-за расширенной функциональности и повышенной производительности. Но как больший объем кода повышает производительность приложения? Это не очевидно, так ведь? При создании программ, отличных от средств выполнения произвольных запросов, вам обычно известна структура набора результатов вашего запроса. Например, большая часть ADO-кода выглядит так: Dim rs as Recordset 'Объявляем прочие переменные 'Инициализируем переменные и открываем соединение с БД rs.Open strSQL, cnDatabase, adOpenStatic, adLockdptiroistic, adCmdText Do While Not rs.EOF Listl.Addltem rs.Fields("UserName").Value rs.MoveNext Loop
В данном случае программист знает, что запрос содержит столбец UserName, Смысл в том, что вы, как разработчик, обычно имеете представление, какие столбцы и с каким типом данных вернет ваш запрос. Тем не менее ADO заранее неизвестно, как будут выглядеть результаты запроса. А значит, ей приходится обращаться к OLE DB-поставщнку и задавать вопросы типа «Сколько столбцов в наборе результатов запроса?», «Каковы типы данных этих столбцов?», «Происхождение данных?» и «Какие поля первичного ключа таблиц упомянуты в запросе?». Поставщик может ответить на некоторые из этих вопросов, но в большинстве случаев ему приходится обращаться к БД. Для выборки результатов запроса и помещения их в объект DataSet ADO.NET должна знать ответы. Их можно дать самостоятельно или указать ADO.NET обратиться к поставщику. В первом случае производительность кода окажется выше, поскольку ADO.NET не придется запрашивать эти сведения у поставщика в период выполнения. Написание кода для подготовки структуры объекта DataSet может стать очень утомительной задачей, хотя и позволяет повысить производительность приложения.
ГЛАВА 1 Обзор ADO.NET
19
К счастью. Visual Studio .NET включает предоставляемые на этапе разработки средства доступа к данным, объединяющие в себе лучшие стороны обоих способов. Так, можно создать DataSet на основе запроса, имени таблицы или хранимой процедуры, и затем мастер конфигурирования сгенерирует ADO.NET-код, необходимый для выполнения запроса и передачи изменений в БД. Подробнее об этом — в последующих главах книги.
Объекты DataSet со строгим контролем типов Visual Studio .NET упрощает разработку приложений для доступа к данным, генерируя объекты DataSet со строгим контролем типов. Предположим, у нас есть простая таблица Orders со столбцами CustomerlD и CompanyName. Писать код, аналогичный показанному ниже, не требуется. Visual Basic .NET Dim ds As DataSet 'Создаем и заполняем объект DataSet Console. WriteUne(ds.Tables("Customers").Rows(0)( "CustofnerlD"))
Visual C# DataSet ds; //Создаем и заполняем объект DataSet Console. WriteLine(ds.Tables["Customers"].Rows[0]["CustomerlD"]);
Вместо этого можно написать такой код: Visual Basic .NET Dim ds As CustomersDataSet 'Создаем и заполняем объект DataSet Console,WriteLine(ds.Customers(0).CustomerlD)
Visual C# CustomersDataSet ds; //Создаем и заполняем объект DataSet Console. Writel_ine(ds.Customers[0]. CustomerlD);
Объект DataSet со строгим контролем типов — это просто класс, создаваемый Visual Studio .NET и предоставляющий информацию о таблицах и столбцах посредством свойств. Кроме того, объекты DataSet со строгим контролем типо.в предоставляют нестандартные методы для таких действий, как создание новых записей. Таким образом, вместо следующего кода: Visual Basic .NET Dim ds as DataSet 'Код, создающий объект DataSet 'и объект DataTable под названием Customers Dim rowNewCustoroer As Dataflow rowNewCustomer = ds.Tables("Customers").NewRow()
20
Часть I
Основы работы с Microsoft ADO.NET
rowNewCustomer("CiistomerID") = "ALFKI" rowNewCustomer("CompanyName") = "Alfreds Futterkiste" ds,Tables("Customers").Rows.Add(rowNewCustomer}
Visual C# DataSet ds; //Код, создающий объект DataSet //и объект DataTable под названием Customers Dataflow rowNewCustomer; rowNewCustome г = ds.Tablest"Custome rs"].NewRow(); rowNewCustomer["CustomerID"] = "ALFKI"; rowNewCustomerfCompanyName"] = "Alfreds Futterkiste"; ds.Tables["Customers"].Rows.Add(rowNewCustomer);
создать и добавить новую запись в таблицу можно посредством одной строки кода: ds.Customers,AddCustomersRow("ALFKI",
"Alfreds Futterkiste")
Подробнее об объектах DataSet со строгим контролем типов — в главе 9-
Вопросы, которые стоит задавать почаще Несмотря на свое название, модель ADO.NET мало похожа на ADO. И хотя ADO.NET содержит классы, позволяющие подключаться к БД, выполнять запросы и получать их результаты, эта объектная модель сильно отличается от ADO. Вы уже знаете о многих отличиях. Далее я рассмотрю основные объекты иерархии ADO.NET. Однако для начала проясню некоторые вопросы, наиболее часто возникающие у разработчиков, только что перешедших на ADO.NET, Вопрос.
Почему нет ни "слова о курсорах?
Ответ. В первом выпуске ADO.NET серверные курсоры не поддерживаются. Возможно, их поддержка будет реализована в будущих версиях. В данный момент ни один из объектов иерархии ADO.NET не является интерфейсом серверного курсора. Объекты DataSet и DataTable соответствуют скорее клиентскому объекту Recordset ADO. Объект DataReader — серверному объекту Recordset ADO, использующему непозиционируемый, доступный только для чтения курсор. Вопрос. Как задать текущее положение в объекте DataTable средствами ADO.NET? В предыдущих моделях доступа к данным имелись методы типа MoveFirst, MoveNext и т.д. Где же свойства позиционирования и методы перемещения? Ответ. Объект DataTable предостаапяет набор Rows, позволяющий в любое время ссылаться на любую запись таблицы; следовательно, DataTable не поддерживает концепцию текущей записи. Поскольку к любой записи можно обращаться напрямую, потребность в свойствах позиционирования и методах перемещения типа MoveFirst, MoveLast, MoveNext и MovePrevious отпадает. В ADO свойства позиционирования и методы перемещения обычно использовались при выводе данных на форме.
Г Л А В А
2 Создание ADO.NET-приложений с помощью мастера DataForm Wizard
Структура многих книг для разработчиков такова: сначала в нескольких главах рассматриваются отдельные фрагменты кода и лишь затем на их основе создастся несложное работоспособное приложение. Я решил изменить логику изложения и прямо в этой главе покажу, как быстро и просто создать приложение, демонстрирующие часть основных возможностей Microsoft ADO.NET,
Все без ума от примеров Из конференций, в которых мне пришлось участвовать и выступать, я понял, что все разработчики, особенно использующие Microsoft Visual Basic, без ума от примеров, И правда, гораздо проще обсуждать возможности кода, предварительно увидав его в действии. Именно поэтому я начинаю главу с примера. Мастер Data Form Wizard из Visual Studio .NET позволяет легко, буквально в несколько этапов, создать связанную с данными форму. Сейчас я создам форму, позволяющую получать из БД данные, просматривать и изменять их и затем передавать изменения в исходную БД. Далее я кратко разберу генерируемый мастером код —- это будет своего рода анонс следующих глав. Созданное приложение я использую в качестве рабочего примера при описании многих объектов, функций и концепций, о которых пойдет речь в следующих главах книги.
Часть I Основы работы с Microsoft ADO.NET
Примечание Мастера раздражают многих разработчиков. Возможно, «раздражают* — слишком сильное слово, но разработчики, по крайней мере, не доверяют мастерам, особенно использующим технологии «черного ящика*. К счастью, мастер Data Form Wizard из Visual Studio .NET генерирует код, который можно просматривать и изменять. В этой главе я расскажу о том, как создать простое приложение на основе БД Northwind, поставляемой с такими программными продуктами, как Microsoft SQL Server, Microsoft Access и Microsoft Data Engine (MSDE). В зависимости от программы или ее версии, структура БД Northwind различается, но всегда содержит таблицы Customers, Orders, Products и Employees. Чтобы создать приложение, о котором пойдет речь, вам потребуется доступ к БД Northwind.
Создание связанной с данными формы средствами мастера Data Form Wizard Б проект Visual Studio .NET разрешено добавлять различные объекты, например формы, модули классов и модули кода. В проекты Visual Basic .NET и С* можно также добавлять связанную с данными форму. При этом Visual Studio автоматически запускает мастер Data Form Wizard. В последовательно открываемых мастером диалоговых окнах вам предлагается ввести информацию о БД, ее таблицах, полях таблиц и т.д. Далее об этом я расскажу подробно. Во-первых, в меню File среды Visual Studio .NET выберите New\Project или щелкните кнопку Project в панели инструментов. Откроется диалоговое окно New Project. В панели Project Types выберите нужный язык программирования — Visual Basic или Visual С* — и затем щелкните значок Windows Application (рис. 2-1). В текстовом поле под значками наберите имя создаваемого проекта, Chapter2, и щелкните ОК.
Project Types: ь
р| vsuarf Basic Projects &1 Visual C# Projects jj Setup and Deployment Protects £j.'l Other Project! [_J Ve-wl 5tudio Solutions
Class Library
Windows ControlLibrary
Application
ASP MET Vjeb ASP .MET Web №eb Control Application Service Library .A project for creating an application with a Windows user interface Й8те:
j Chapters]
Location;
j D:\Documents and 5ettings\C)ayidScl.My Documents^, -j
Project win be created at 0:\.. .\Сау1с13с\Му Doajmerits\Vi£ijiJ Studio Projacts^Chaptвг2, *More
Рис. 2-1.
I
I
I
;
OK
1
Cancel
j
Создание нового Windows-приложения
ГЛАВА 2 Создание ADO.NET-лриложений с помощью мастера DataForm Wizard
23
Примечание При создании Web-формы используются более сложные концепции, о них пойдет речь в главе 14- Здесь для простоты я предлагаю создать связанную с данными Windows-форму. Теперь воспользуемся возможностями мастера Data Form Wizard. Поскольку-' при работе с мастером в проект добавляется новая форма, мастер запускают из диалогового окна Add New Item. Чтобы открыть это окно, выберите из меню File среды Visual Studio .NET команду New или щелкните кнопку Add New Item в панели инструментов. После этого щелкните значок Data Form Wizard (рис. 2-2) и затем — ОК. AddNew Item - Chapter? Templates:
Windows Form
Class
Component Class
User Control
DataSet
XML Fie
Open
XML Schema
Caned
Рис. 2-2. Запуск мастера Data Form Wizard из диалогового окна Add New Item Откроется первое окно мастера (рис. 2-3).
Рис. 2-3. Начальное окно мастера Data Form Wizard
Часть I Основы работы с Microsoft ADO.NET
24
Щелкните Next. В следующем диалоговом окно появится предложение использовать имеющийся объект DataSet или создать новый. Поскольку объектов DataSet еще нет, создайте новый (рис. 2-4), Choose thE dataset v°U want to use The dataset contains [he tflbles and columns to wQ'k with in уст
Рис. 2-4.
Создание нового объекта DataSet для новой формы
Имя объекта DataSet должно отражать тип его данных. В нашем примере DataSet содержит таблицы Customers и Orders БД Northwind, так что вам следует задать ему имя CustomersAndOrders. Щелкните Next. Примечание Мастер Data Form Wizard создает объекты DataSet с жестким контролем типов и добавляет их в проект в виде новых файлов классов. В связи с этим имя объекта DataSet всегда должно быть допустимым именем класса, т. е. начинаться с буквы и включать только буквы, цифры и символы подчеркивания.
Выбор подключения Теперь пора подключить маетер Data Form Wizard к вашей БД. В окне Choose A Data Connection (рис. 2-5) можно выбрать новое или уже существующее, доступное в окне Server Explorer, подключение. Если вы еще не создавали подключение к БД, щелкните New Connection. Откроется диалоговое окно OLE DB Data Link Properties. На вкладке Connection окна OLE DB Data Link Properties можно задать параметры подключения. По умолчанию создается подключение к Microsoft SQL Server. Чтобы подключиться к БД, укажите имя сервера [или наберите (local) для взаимодействия с локальным экземпляром SQL Server], имя пользователя, пароль и имя БД (рис. 2-6). Чтобы выбрать нужный экземпляр на компьютере с несколькими экземплярами SQL Server, введите имя машины, обратную косую черту и имя экземпляра, например Имя^1оегоСервера\ИмяМоегоЭкземг11яра,
ГЛАВА 2 Создание ADO.NET-приложений с помощью мастера DataForm Wizard
25
Вя»е Гпт» Wizard a data connection
Which tdfine с hon ihcm Id the fvteard иь
Рис. 2-5-
Рис. 2-6.
Диалоговое окно Choose A Data Connection
Вкладка Connection окна OLE DB Data Link Properties
Примечание По умолчанию пароль учетной записи администратора SQL Server — пустой. В целом это не слишком удачная идея, и по соображениям безопасности пароль рекомендуется изменить. Кроме того, при создании приложений для работы с БД SQL Server не рекомендуется регистрировать пользователей под учетной записью администратора. Создайте отдельные учетные записи пользователей или одну общую учетную запись и назначьте им соответствующие разрешения. Следует позаботиться о том, чтобы пользователи случайно или намеренно не внесли в БД необратимые изменения. Заметьте: на рис. 2-7 помечен флажок Allow Saving Password. По умолчанию он снят. Если вы, используя параметры по умолчанию, введете пароль, Visual Studio .NET получит всю строк}' подключения, кроме пароля. В результате при каждом обращении к БД в период разработки вам придется вводить пароль. Я предпочи-
Часть I Основы работы с Microsoft ADO.NET
26
таю помечать этот флажок, удаляя тем самым пароль из строки подключения в коде приложения. Сейчас же пометьте данный флажок. Подробнее об этом — в главе 3. Чтобы подключиться к БД, отличной от SQL Server, перейдите на вкладку Provider и выберите нужный поставщик OLE DB. Здесь перечислены все установленные на вашем компьютере поставщики OLE DB. Подробнее о подключении к другим поставщикам средствами окна Data Link Properties — в следующей главе.
osoT SAM 1 1 OLE OB Piovida EBDttJelDS' CLE DE PfOYidfr osofUet 4 0 OLE OB Provider cionCLEUEPrc.:; aCT- Fa. osoft OLE И F-svdtr foi lndeГО!0. -v fjeiefe - Dria№ (he curent record. •V Cancel- Сй-т?1& chang« to ^hecurreriT'et^u. *•" risi»atm tonlrolj - Mo«s loAVsl, BSVBUS, nejl. or Ийпяогй.
Рис. 2-12.
Выбор стиля отображения содержимого таблицы
30
Часть I
Основы работы с Microsoft ADO.NET
Примечание В случае с Web-приложением мастер создает объекты DataGrid, которые преобразуют содержимое ваших таблиц в HTML-таблицы для отображения на форме. Вот и все. Щелкните Finish, чтобы создать новую связанную с данными форму
Использование новой связанной с данными формы На рис. 2-13 показана связанная с данными форма, созданная мастером Data Form Wizard.
-.ipre,:
Ddtahurml.xbLLkr^ignl |
^fCiisWrper5flndOr6eis ^1 esehiblylnfo.vl: US DotSomlL,* S FO.nl vb
jCaUforml Er*en'.V1nifc:
I ЬсмШсШпс AccsiSibleRulf
i OleDbConneaionl
% CfeDbPataAdapteг I
Рис. 2-13.
Новая, связанная с данными форма
Рис. 2-14.
Диалоговое окно свойств проекта Chapter2
а x
ГЛАВА 2
Создание ADO.NET-приложений с помощью мастера DataForm Wizard
31
Собрав и запустив проект прямо сейчас, вы не увидите новой связанной с данными формы. Если вы не изменяли параметров проекта, отобразится только форма, с самого начала имевшаяся в проекте. Чтобы изменить параметры для отображения новой формы, выберите из меню Project среды Visual Studio .NET команду Properties или щелкните в окне Solution Explorer проект правой кнопкой и выберите Properties. Откроется диалоговое окно Property Pages. В раскрывающемся списке Startup object укажите DataForm 1 (рис. 2-14). Щелкните ОК, чтобы сохранить изменения,
Отображение данных в связанной форме Чтобы запустить проект и увидеть новую связанную с данными форму, нажмите клавишу F5; выберите из меню Debug команду Start или щелкните кнопку Start в панели инструментов, Вы увидите, что на форме имеются метки и текстовые поля для всех полей таблицы Customers, а также связанная с данными сетка для отображения содержимого таблицы Orders. Однако никаких данных нет. Все элементы управления пусты. Форма создала объект DataSet с таблицами и отношением, определенным средствами мастера Data Form Wizard, причем абсолютно пустой. Щелкните кнопку Load Б левом верхнем углу формы, и в элементах управления появятся данные (рис. 2-15).
Рис. 2-15. Просмотр данных в новой связанной форме
Изучаем сгенерированный мастером код Чтобы просмотреть код, выполняемый проектом при щелчке кнопки Load, закройте форму и вернитесь в среду Visual Studio .NET. Дважды щелкнув кнопку Load, вы увидите, что код ее события Click вызывает процедуру LoadDataSet. Прокрутив содержимое окна до определения данной процедуры, вы узнаете, что она вызыва ет процедуру FtilDataSet, которая в свою очередь вызывает метод Fill двух объектов OieDhDataAdapter (рис. 2-16).
Часть I Основы работы с Microsoft ADO.NET
32
j"-*F"0«t«fi«
;i)ataforml
9
pubi
sufc FiUI>aiiiSet(BYVai da^aSet Ля Chapters.cuat
1 tdti ThrOT FlnnllJ
He . oie№i:o End Tcy End 3ub
Cionl. Closed
?эо
Рис. 2-16.
ots
chs
•
-INS
Код, сгенерированный мастером для заполнения объекта DataSet
При вызове метода Fill объекта QleDbDataAdapter выполняется запрос, заданный свойству SelectCommand объекта DataAdapter, и его результаты заносятся в указанные объекты DataSet или DataTable. Мастер Data Form Wizard создает объекты DataAdapter для выборки содержимого нужных вам таблиц. Свойство SelectCommand любого объекта DataAdapter содержит запрос в следующем формате: SELECT Fieldl. Field2,
FieldN FROM HyTable
Большинство кнопок формы описывают сами себя — так, при щелчке кнопки Add добавляется новая запись, а при щелчке кнопки Delete удаляется текущая запись о клиенте. Кнопки со стрелками позволяют перемещаться по записям.
Реализация каскадных изменений с помощью объекта Dafafle/at/on Щелкните кнопку со стрелкой вправо, чтобы перейти к следующему клиенту. В текстовых полях связанной с данными формы отобразится информация о нем. Из-за созданного вами объекта DataRelation (отношение между таблицами) в сет-> кс видны только заказы, размещенные данным конкретным клиентом. Кроме того, это отношение позволяет реализовать на форме и некоторые другие функции. С помощью кнопок перемещения перейдите к первому клиенту', разместившему заказы. Если вы не изменяли содержимое таблиц БД Northwind, то для клиента с идентификатором (CustomerlD} ALFKI указано несколько заказов. В текстовом поле CustomerlD наберите Chap2. He волнуйтесь: это не повлияет на содержимое БД. Затем перейдите к следующей записи и снова вернитесь к измененной вами записи. Просмотрите содержимое сетки: значение поля CustomerlD для всех заказов — Chap2. Созданный вами объект DataRelation указал объекту DataSet каскадно изменять значения поля CustomerlD записи о клиенте на связанные записи о
ГЛАВА 2 Создание ADO.NET-лриложений с помощью мастера DataForm Wizard
33
заказах. Если удалить текущую запись о клиенте, объект DataSet также удалит все связанные с ней записи о заказах.
Передача изменений в базу данных Как уже упоминалось, изменения данных объекта DataSet не влияют на содержимое БД. Чтобы убедиться в этом, закройте форму и повторно запустите проект. Загрузите данные и с помощью кнопок перемещения перейдите к ранее изменявшейся вами записи о клиенте. Вы увидите, что значение поля CustomerlD — гюпрежнему ALFKI. Как упоминалось в главе 1, данные объекта DataSet отсоединены от БД. В ADO.NET имеются функции для передачи изменений в БД, но как ими воспользоваться? Для передачи изменений в БД применяют дополнительную функцию класса DataActatper ADO.NET. Подробнее об этом — далее. Теперь снова измените запись о клиенте. Перейдите к первому клиенту, щелкнув кнопку с двойной стрелкой влево. Добавьте в конец названия компании букву X. Щелкните кнопку со стрелкой вправо, чтобы перейти к следующей записи, а затем — кнопку со стрелкой влево, чтобы вернуться к измененной записи. Вы увидите, что изменение по-прежнему хранится в объекте DataSet. Если закрыть форму и перезапустить проект, изменение будет утеряно. Чтобы передать изменение в БД, щелкните кнопку Update. Курсор мыши изменится со стрелки на часы, показывая, что форма выполняет код, передающий сделанное вами изменение в БД. Когда курсор примет вид стрелки, закройте форму и перезапустите проект. Щелкнув кнопку Load, вы увидите новое название компании. roaili t'lti«in.isfc .МП jdesign] Oet*fomi1.vb
'!,••• DatffanM.vb I
Рис. 2-17. Процедура UpdateDataSet, сгенерированная мастером Data Form Wizard Какой же код выполняет кнопка Update для передачи изменения в БД? Чтобы просмотреть его, закройте форму и вернитесь в среду Visual Studio .NET. Дважды
34
Часть I
Основы работы с Microsoft ADO.NET
щелкните кнопку Update и просмотрите код события Click. Сгенерированный мастером код создает две процедуры для обновления БД — UpdateDataSet и LJpdateDataSource. Событие Click кнопки Update вызывает процедуру UpdateDataSet, которая в свою очередь вызывает процедуру UpdateDataSource. Определения этих процедур показаны на рис. 2-17 и 2-18. Код процедур только на первый взгляд кажется сложным, однако на самом деле он прост. Сейчас мы вернемся к основному процессу, и рассмотрим код лишь поверхностно. Подробнее о передаче обновлений в БД — в главах 10 и 11 этой книги. Помните: мастер сгенерировал этот код для обработки любых изменений данных формы. Код обрабатывает изменение, добавление и удаление записей в обеих таблицах. Для управления обновлением данных используются методы GetChanges, Update и Merge,
Рис. 2-18. Процедура UpdateDataSource, сгенерированная мастером Data Form Wizard
Вызываем метод Update объекта DataAdapter Вместо того чтобы обсуждать эти методы по порядку их появления в коде, прежде я расскажу о методе Update — наиболее важном элементе процесса обновления, Как говорилось в главе 1, объект DataAdapter — это мост между объектом DataSet и БД, Метод Update объекта DataAdapter передает изменения из объекта DataSet в базу данных. Каждый объект DataAdapter соответствует одному объекту DataTable из состава нашего объекта DataSet. Чтобы передать изменения из обоих объектов DataTable, следует вызвать метод Update обоих объектов DataAdapter. При вызове метода Update объекта DataAdapter необходимо указать, какие данные нужно передать в БД Объект DataAdapter очень гибок, и его метод Update может принимать множество различных структур. Код, сгенерированный мастером Data
ГЛАВА 2 Создание ADO.NET-приложений с помощью мастера DalaForm Wizard
35
Form Wizard, использует объект DataSet, однако методу Update можно также передать объект DataTable или массив объектов DataRow, Объект DataAdapter просматривает содержимое структуры данных и определяет, какие записи он способен обработать. Например, DataAdapter, созданный мастером Data Form Wizard на основе таблицы Customers, будет просматривать только соответствующий этой таблице объект DataTable. Объект DataAdatper выбирает нужную таблицу для просмотра, используя набор TableMappings, о котором вкратце я рассказал в главе 1. Обнаружив измененную запись, DataAdapter определяет тип изменения — вста вка, обновление или удаление — и передает его в БД, основываясь на его типе. Если запись изменена, объект DataAdapter выполняет объект Command, заданный свойству UpdateCommand. Точно так же DataAdapter использует свойство InsertCommand для передачи новых и свойство DeleteCommand — для удаления имеющихся записей.
Изоляция измененных записей Код функции UpdateRowSource формы вызывает метод GetCbanges объекта DataSet, Этот метод генерирует новый объект DataSet с именем objDataSetChanges, содержащий только измененные записи. Метод GetCbanges принимает необязательный параметр, позволяющий указать, нужны ли вам все изменения или только какойто конкретный их тип — вставка, обновление или удаление. На самом деле мастеру Data Form Wizard не требовалось создавать с помощью метода GetCbanges новый объект DataSet, содержащий только измененные записи. Если объект DataSet, переданный в качестве параметра при вызове метода Update объекта DataAdapter содержит: неизмененные записи, те игнорируются. Так зачем же нужен метод GetCbanges? Мастер Data Form Wizard генерирует двухуровневые приложения. Клиентское приложение взаимодействует непосредственно с БД. При создании многоуровневого приложения, использующего Web-сервисы или компоненты СОМ+ и выполняющегося на сервере промежуточного уровня, следует ограничить объем данных, передаваемых между компьютерами. Чем меньше данных передается, тем быстрее выполняется приложение. Если в таком многоуровневом приложении у клиента есть объект DataSel с неизмененными записями для передачи в БД, клиент передает данные на промежуточный уровень. Передавать на этот же уровень неизмененные записи не требуется. Таким образом, умелое использование метода GetCbanges позволяет значительно повысить производительность многоуровневых ADO.NET-приложений. Мастер Data Form Wizard создает двухуровневые приложения, однако генерируемый им код годится и для многоуровневых.
Реинтеграция изменений Просмотрев измененную запись и успешно передав ожидающее изменение в БД. объект DataSet помечает эту запись как не содержащую ожидающих изменений. Таким образом, исключается многократная передача одного и того же изменения при повторных вызовах метода Update.
36
Часть I Основы работы с Microsoft ADO.NET
Ранее я рассказал о методе GetCbanges класса DataSet. Код формы использует объект DataSet, возвращенный методом GetCbanges, при вызове метода Update объекта DataAclapter. После внесения обновлений объект DataSet помечает соответствующие записи объекта objDataSetChanges как успешно обновившие БД. Тем не менее объект objDataSetChanges стоит отдельно от основного объекта DataSet формы. Нам нужно как-то объединить изменения, сделанные объектами DataAdapter в объекте objDataSetChanges, и внести их в основной объект DataSet. У класса DataSet имеется метод Merge, позволяющий объединять данные из двух объектов DataSet. Если записи объектов DataSet различаются, ADO.NET просто помещает все записи в тот объект, метод Merge которого вы вызвали. В нашем случае записи объекта objDataSetChanges ссылаются на те же данные, что и основной объект DataSet. Нам нужно, чтобы соответствующие записи основного объекта DataSet были перезаписаны записями объекта objDataSetChanges. ADO.NET сравнивает значения основного ключа, хранящиеся в записях, и определяет, какие же записи ссылаются на один и тот же ряд данных. По умолчанию ADO.NET перезаписывает ряд того объекта DataSet, метод Merge которого вы вызвали, Таким образом, изменения, вносимые объектами DataAdapter в объекты dsDelta DataSet, передаются основному объекту DataSet, и мы можем успешно обрабатывать последующие обновления.
Панель компонентов Прежде чем продолжить, хочу обратить ваше внимание на еще одну особенность среды Visual Studio .NET. Разработчики, работавшие с предыдущими версиями Visual Studio, возможно, удивятся, увидев новую версию конструктора связанных с данными форм. Один из первых вопросов тех, кто работает на Visual Basic 6. «Что это за панель под формой?* На рис. 2-19 под формой находится панель компонентов. Visual Studio .NET позволяет перетаскивать элементы из панели инструментов в конструкторы. Многие разработчики используют панель инструментов для добавления кнопок на формы. Тем не менее не все добавляемые компоненты видны в период выполнения. Например, в Visual Basic 6 на форму можно добавить элементы управления «•таймер* и «диалоговое окно*, которые в период выполнения не видны Это компоненты, свойства которых разрешено задавать в период разработки средствами окна Properties, но которым не соответствует какой-либо видимый элемент пользовательского интерфейса. В Visual Basic 6 при добавлении такого элемента управ-, ления на форме появляется значок, однако в период выполнения данный элемент управления не виден. Visual Studio .NET помещает не отображаемые компоненты в панель, расположенную под формой. Чтобы изменить свойства компонента в окне Properties, щелкните в панели нужный компонент. В панели компонентов нужной формы отображаются значки, соответствующие объектам DataConnection, DataAdapter и DataSet. Компоненты со вкладки Data панели инструментов можно перетаскивать на форму или в панель компонентов. Некоторые компоненты, например DataAdapter, связаны с мастерами настройки. После того как вы перетащите на форму объект
ГЛАВА 2 Создание ADO.NET-приложений с помощью мастера DataForm Wizard
37
OleDbDataAdapter, запустится мастер настройки параметров объекта DataAdapler, используемого для взаимодействия с БД. Мастер генерирует код, основываясь на вводимых вами данных, аналогично мастеру Data Form Wizard. Объектные переменные из панели компонентов инициализируются в скрытой области кода формы. Откройте раздел кода, помеченный как «Windows Form Designer generated code» (рис. 2-20). Здесь находится весь сгенерированный конструктором код. Прокрутив содержимое окна за объявления объектов, вы увидите процедуру InitializeComponent, которая содержит код, сгенерированный мастером DataAdapter Configuration Wizard. Создание компонентов с использованием этих функций быстрой разработки приложений сэкономит вам уйму времени.
Панель комонентов
Рис. 2-19. Панель компонентов среды Visual Studio .NET
OMWo.ml.rt I
Gill
Oil
Рис. 2-20. Раздел со «скрытым» кодом, сгенерированным мастером
38
Часть I
Основы работы с Microsoft ADO.NET
Мастер Data Form Wizard — отправная точка создания приложений для работы с БД Поздравляю! Вы только что создали работающее приложение для взаимодействия с базой данных. Конечно, если бы этим его создание и ограничивалось, эта глава книги стала бы последней. Рассматриваемое здесь приложение максимально упрощено, Его не рекомендуется развертывать в сети с большим числом пользователей. Представьте: каждый пользователь выбирает каждую запись и каждый столбец из вашей БД. Упс! Сетевой трафик выйдет из берегов. Мастер Data Form Wizard можно считать, скорее, отправной точкой разработки, он не рассчитан на создание законченных решений. Однако работа с ним — хороший способ знакомства с ADO.NET, поскольку в результате вы сможете просматривать генерируемый код. Прежде чем перейти к следующей главе и подробно рассмотреть объект DataSet, вернемся на несколько минут к коду, сгенерированному мастером Data Form Wizard для нашего приложения. Не пытайтесь прямо сейчас понять, что делает та или иная строка кода — просто оцените его объем. Хотя мастера Visual Studio .NET и не создают законченных, готовых для развертывания приложений, они значительно сокращают время разработки, генерируя необходимый код. В последующих главах я подробно рассмотрю многие из этих мастеров и генерируемый ими код, и расскажу, в каких случаях он подходит или не подходит для вашего приложения.
Вопросы, которые стоит задавать почаще Вы только что создали простое приложение, использующее ADO.NET, и познакомились с некоторыми возможностями данной технологии. Я показал принципы работы ADO.NET на примере фрагментов кода, сгенерированного мастером Data Form Wizard. Эта глава — не только отправная точка, но и своего рода анонс. Итак, вместо того чтобы, как обычно, привести возможные вопросы и ответы на них, я подкину несколько вопросов, над которыми вам придется подумать самостоятельно. Часть ответов вы найдете в коде, сгенерированном мастером Data Form Wizard, другие — в следующих главах книги. Вопрос. Поработав с формой, созданной мастером Data Form Wizard, я обнаружил, что могу добавлять новые заказы. Причем еще до добавления в БД поле OrderlD записи о новом заказе уже содержит конкретный номер. Как объект DataSet генерирует его? Вопрос. Что произойдет, если другой пользователь изменит содержимое записи в период времени после того, как я получу эту запись, но до того, как я передам сделанные мной изменения в БД? Вопрос. После того как я удалил запись о клиенте и щелкнул кнопку Update, приложение выдало сообщение о том. что оператор DELETE конфликтует с ограничением COLUMN REFERENCE. Что это значит? Как мне изменить код для корректной обработки такой ситуации?
Ч А С Т Ь
2
ПОДКЛЮЧАЕМСЯ: ИСПОЛЬЗОВАНИЕ ПОСТАВЩИКА ДАННЫХ .NET
Г Л А В А
3 Подключение к базе данных
\_/дин из этапов разработки приложения для взаимодействия с БД — установка соединения с источником данных и управление этим соединением. В объектной модели ADO.NET соединение с источником данных представлено объектом Connection, Эта глава — справочник основных функций объекта Connection ADO.NET. Я расскажу, как создавать и использовать объекты Connection в коде и в среде разработки Visual Studio .NET. Основное внимание уделено объекту OleDbConnection — из двух входящих в Microsoft .NET Framework объектов Connection он используется наиболее часто. Если не оговорено иное, функции объекта стандартны и имеются у всех объектов Connection, независимо от их поставщика данных .NET, В фрагментах кода вам следует с помощью соответствующих конструкций ссылаться на пространства имен SystemData, SystemData.OleDb и SystemDataSqlClient, а в начало модулей кода (на Visual Basic .NET и Visual С* .NET соответственно) включить приведенные ниже строки. Подробнее о них — в документации по выбранному вами языку программирования. Итак, в код на Visual Basic .NET добавьте следующие строки: Imports System.Data Imports System.Data.OleDb Imports System.Data.SqlCHent
В код на Visual С* .NET добавьте: using System.Data; using System.Data.OleDb; using System.Data.SqlClient;
ГЛАВА 3 Подключение к базе данных
41
Использование объектов Connection Свойства объекта Connection позволяют задать реквизиты пользователя, а также указать расположение источника данных. Методы этого объекта позволяют управлять соединением с источником данных. Кроме того, объект Connection можно использовать в качестве отправной точки для создания объектов Command и Transaction. Теперь немного о том, как создавать и использовать подключения в приложениях при помощи объекта Connection. SQL Server — в массы! Набор инструментальные средств разработчика (software development kit. SDK) Microsoft .NET Framework включает файлы для установки Microsoft " Desktop Engine 2000 (MSDE). Как и БД Access, MSDE — не требующий дополнительных лицензий, предназначенный для распространения пакет для работы с БД Тем не менее, в отличие от БД Access и ядра БД Jet, MSDE — настоящая клиент-серверная СУБД, почти как SQL Server. В БД MSDE можно создавать таблицы, представления и хранимые процедуры, которые также будут выполняться в БД SQL Server. Кроме того, как и SQL Server, MSDE поддерживает стандартную и интегрированную проверку подлинности. MSDE основана на том же коде, что и SQL Server, и предоставляет многие его функции, В результате приложения, взаимодействующие с БД MSDE, можно с минимальными усилиями преобразовать для работы с SQL Server. Между MSDE и SQL Server имеется несколько важных отличий, о которых следует помнить. В MSDE нет некоторых «серверных* функций SQL Server. Например, ядро MSDE рассчитано на обработку пяти параллельных подключений. При большем числе подключений производительность падает. SQL Server поддерживает гораздо большее число параллельно работающих пользователей. Помимо этого размер БД MSDE ограничен двумя гигабайтами, а в SQL Server можно создавать БД большего размера, Также в MSDE нет средств разработки, поставляемых с SQL Server, например Enterprise Manager, Query Analyzer и SQL Profiler. Подробнее о различиях SQL Server и MSDE — на Web-узлах MSDN и SQLServer. MSDE 2000 работает на операционных системах семейства Windows 9x (Windows .98 и более старших), а также на всех версиях Windows NT 4.0/2000 ДР. Используемые в книге строки подключения и запроса взаимодействуют с локальным экземпляром MSDE, поставляемым с .NET Framework SDK. Это позволяет копировать код из электронной версии книги и выполнять его, не изменяя строку подключения к БД. Для устаноаки MSDE раскройте меню Start, выберите группу программ Microsoft .NET Framework SDK и затем — Samples And QuickStart Tutorials, В браузере откроется страница SDK's QuickStarts, Tutorials, And Samples. Если вы еще не устанавливали с этой страницы MSDE, появится окно, аналогичное приведенному на рис. 3-1см. след. стр. 3-5958
Часть II Подключаемся: использование поставщика данных .NET
Microsoft .NET Framework SDK Quicks tarts, Tutorials and Samples
Nate: The .NET Framework Samples Database seUp program msy requn return ha this page ID complete the Quickstert tutorials installation. Step 2: £EUJILUlB-3u!£kSta!±S Because the QuititStar-s demonstrate a wide
Рис, 3-1. Установка MSDE с помощью Microsoft.NET Framework SDK Щелкнув ссылку Install The .NET Framework Samples Database, вы установите на свой компьютер MSDE, При щелчке ссылки Set Up The QuickStarts устанавливаются примеры БД (стандартные БД pubs и Northwind из состава SQL Server, и БД, используемые примерами кода .NET Framework), а также настраиваются виртуальные каталоги Internet Information Services (IIS) и Web-страниц QuickStart Tutorial. Следующий фрагмент кода подключается к экземпляру MSDE из состава ,NET Framework, используя библиотеку SQL DMO. Для выполнения кода йам потребуется добавить ссылку на эту библиотеку. SQL DMO — это СОМ-, а не , МЕТ-библиотека, поэтому при добавлении ссылки не забудьте перейти на вкладку СОМ диалогового окна Add References. Код выполняет содержимое файла сценария, а также просматривает информацию о полъзоБатедях и структуре ваших БД. Подробнее об использовании объектной библиотеки SQL DMO — в справочном файле (SQLDMO.chm).
Visual Basic .NET Dim dffloServer As New SQLBKO.SQLServerO daioServer.toginSecure * True dfflOServer.Conneat("(local)\№etSDK") Dim filScrlScript As IQ.StreamReader Dim strPathToFile As String Console.WriteLlneC'InstaHing the sample Northwind database") strPathToFile « '•G:\VS.NET\FrameworkSDK\SafHples\Setup\iFistnwnd.sql" lO.File.DpenText(strPathtoPiie)
ГЛАВА 3 Подключение к базе данных
43
dmoServer. ExecutelmfnedlateCfllSqlSerlpt. ReadTofnd) filSqlScript.CloseO Console.WriteLineC"Instailing the sample pubs database") strPathToFiie = "C:\VS. NeAFrameworkSDX\Saffiples\Setup\instpu&s.sql" filSqlScript « IQ.File.OpenText(strPathToFile) dmoServer. Executelmmediate(filSqlSeript.ReatfToEnd) filSqlScript.CloseO Din dmoDatabase As SQLDMO.Database Dim dmoTable As SQLDMO.Table Console.WriteLineC"Databases:"} For Each dmoDatabase In dmoServer,Databases If Not draoDatabase.SystemObject Then Console.WriteLineCvbTab & draoDatabase.Name) For Each dmoTable In dmoDatabase.Tables If Not dmoTable.SystemObject Then Console.WriteLinefvbTab & vbtab & *noTable,Na»e} Eftd If Next dmoTable Console.WriteLineC) End If Next efmoDatabase Dim draoLogin As SQLDMO.Login Console.WriteLineC"Logins:") For Each dmoLogin In dmoServer.Logins Console.WriteLineC vbTab & dstoLogin.Hame) Next dmoLogin Console.WriteLineC) dmoServer.DisConnect{)
Visual C# .NET SQLDMO,SQLServer dioServer = new SQLDHO.SQLServeK);
dmoServer.LoginSecure = true; dmoServer.Connect("Clocal)\\NetSDK", null, null); System.10.Streamfieader filSqlScript; string strPathToFile;
Console. WriteLinef "Installing the saieple Norttiwlnd database"); StrPathToFile - "C:\\VS,HET\\FrameworkSDK\\Samples\\Setup\\itistnwnd.sql' filSqlScript = System,10.File.OpenText{StrPathToFile); dfltoServer, Executelmmediateff ilSqlScrlpt. FteadToEndO, SOLOMO.SQLDMOLEXEC_TVPE.SQLDMOExec_Default, null); filSqlScript.CloseO;
см. след, стр,
44
Часть II Подключаемся: использование поставщика данных .NET
Console, WriteLine( "Installing the sample pubs database"); strPathToFile = "CiXXVS.NrrXXFramew filSqlScrlpt = System. 10. File. GpenText(strPathTaFile); dmaSe rve г . Executelmiaediate ( f USqlSc rl pt . ReadToEnd ( ) , SQLDMO.SQLDMQ_EXEC,TYPE.SQLDMOExec_Q6fault, null); filSsilScript.eiQseO;
Console. WrlteLineC "databases:"); foreach (SQLDHO. Database dmoDatabase in drac-Server. Databases) if (IdmoDatabase.SystemObject) I Console. WrlteLine("\t" + dmoDatabase.Name); foreach (SQLDHO. Table dmoTable in dmoDatabase. Tables) If CldnioTable.SystemObject) Console, WriteLine("\t\t" + insole, Writeline( ) ;
Console. WriteLine( "Logins: " }; foreach (SQLDHO. Login dmoLogln in dmoServer, Logins) Cor*sole.WriteLine("\t" + dmologin. Наяда); Console. WriteLineO; duoServe r, DisCoRfvect( ) ;
Создание объектов Connection Для создания соединения с помощью объекта OleDbConnection в период выполнения имеются два способа. Можно просто создать новый инициализированный объект OleDbConnection, как показано ниже:
Visual Basic .NET Dim en As OleDbConnection en = New 01eDbConnection()
Visual C# .NET OleDbConnection en; en = new OleDbConnectionO;
Или же инициализировать объект OleDbConnection с помощью конструктора класса.
Конструкторы Microsoft .NET Framework поддерживает конструкторы, которые при обычном программировании с использованием объектной модели COM (Component Object Model) недоступны. Конструктор можно рассматривать как метод класса, вызываемый при инициализации объекта. Обычно конструктор принимает парамет-
ГЛАВА 3
Подключение к базе данных
45
ры, соответствующие наиболее часто используемым свойствам класса. Например, класс OleDbConnection определяет конструктор, который принимает значение свойства ConnectionString создаваемого им объекта OleDbConnection. Следующие фрагменты кода аналогичны друг другу. Для каждого языка программирования приводится по два фрагмента, первый из которых создает экземпляр объекта OleDbConnection и инициализирует его, а второй — инициализирует объект OleDbConnection в период его создания, передавая параметр конструктору этого объекта. Visual Basic .NET Dim strConn As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK; " i "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As OleDbConnection en = New OleDbConnection() en.ConnectionString = strConn — или -
Dim strConn As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" A "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As OleDbConnection en = New OleDbConnection(strConn)
Visual C# .NET string strConn; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en; en = new OleDbConnectionO; on.ConnectionString = strConn;
— или — string strConn; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en; en = new OleDbConnection(strConn); Visual Basic .NET и С* позволяют инициализировать переменные при их объявлении. При совместном использовании с конструктором такая возможность позволяет упростить приведенные выше фрагменты кода, а также объявлять, создавать экземпляры и инициализировать объекты одной строкой:
Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & _ "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn)
46
Часть II
Подключаемся: использование поставщика данных .NET
Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn);
Строки подключения В приведенных выше фрагментах кода для объектов OleDbConnection передается строка подключения, которая состоит из пар «параметр — значение*, разделенных точкой с запятой: strConn = "Параметр1=3начение1;Параметр2=3начение2;..."
Параметры и значения зависят от нужного вам источника данных и способа подключения к нему. Поставщик данных OLE DB .NET очень удобен для подключения к БД и предоставляет множество способов создания строки подключения. Давайте вкратце рассмотрим создание строк подключения для трех наиболее часто используемых поставщиков данных OLE DB: для БД Access, БД SQL Server и БД Oracle. Поставщик OLE DB для БД SQL Server При подключении к БД SQL Server можно указать собственного поставщика OLE DB, расположение экземпляра SQL Server, нужную БД, а также имя пользователя и пароль: Provider=SQLOLEDB;Data 5оигсе=Сервер; Initial Catalog=BA; User Ю=ИдентификаторПользователя;Раз5иогй=Пароль;
Начиная с SQL Sewer версии 2000, на одном компьютере можно устанавливать несколько экземпляров SQL Server. Чтобы указать в коде нужный экземпляр, воспользуйтесь следующим синтаксисом атрибута Data Source-. Provider=SQLOLEDB;Data Зоигсе=Сервер\Экземпляр; Initial Catalog=Efl; User Ю=ИдентификаторПопьзователя; Password=napont.;
Если вы подключаетесь к SQL Server при помощи реквизитов для входа в сеть, можно воспользоваться атрибутом Integrated Security и опустить имя пользователя и пароль: Provider=SQLOLEDB;Data 8оигсе=Сервер;Initial Catalog=Bfl; Integrated Security=SSPI;
От некоторых старых привычек трудно избавиться. При подключении к SQL Server посредством старой технологии (ODBC) сетевые реквизиты можно задействовать, воспользовавшись атрибутом Trusted_Connection. Поставщик OLE DB SQL Server принимает этот атрибут как псевдоним Integrated Security. Я продолжаю использовать этот несколько устаревший синтаксис в основном потому, что значение Yes запомнить проще, чем SSPI-. Provider=SQLOLEDB; Data 5оигсв=Сервер; Initial Catalog=Bfl;Trusted_Connection=Yes;
ГЛАВА 3
Подключение к базе данных
47
Подробнее о параметрах данного поставщика — в документации Microsoft Data Access SDK. Поставщик OLE OB для БД Oracle Разработчики, использующие ADO.NET для подключения к БД Oracle, не просто устанавливают ADO.NET и создают строку подключения. И поставщик Microsoft OLE DB Provider for Oracle, и драйвер Microsoft ODBC Driver for Oracle взаимодействуют с клиентскими компонентами Oracle, а не непосредственно с БД Oracle. Чтобы взаимодействовать с Oracle при помощи ADO.NET, вам потребуется установить соответствующую версию клиентских утилит Oracle (SQL'Net) и создать псевдоним БД. Затем можно воспользоваться строкой подключения: Provider=MSDAORA;Data 5оигсе=ПсевдонимБД; User 10=ИдентификаторПользователя; Password=napont>;
Подробнее о параметрах данного поставщика — в документации Microsoft Data Access SDK. Поставщик OLE DB для БД Access Для подключения к БД Access можно использовать соответствующий поставщик OLE DB — Microsoft Jet 4.0 OLE DB Provider. В строке подключения следует указать имя поставщика, его версию, а также расположение БД: Provider=Microsoft.Jet.OLEDB.4.0;Data
8оигсе=С;\Путь\К\БаэеДанных.МОВ;
Если не указан полный путь, ADO будет искать БД в рабочей папке вашего приложения. Разрешается также задавать относительные пути. Например, если БД находится во вложенном каталоге Data рабочей папки приложения, можно указать такую строку подключения: Provider=Microsoft.Jet.OLEDB.4.0; Data Source=Data\MyDatabase.MDB;
Есть и другие способы подключения с помощью поставщика Jet OLE DB. Подробнее — в документации Microsoft Data Access SDK. Сейчас я покажу два наиболее часто используемых способа. Первый — подключение к БД Access с использованием системы безопасности Jet: Provider=Microsoft.Jet.OLEDB,4.0; Data Source=C:\.. ДЗащищеннаяБД.НОВ; Jet OLEDB:System database=C:\.. ДСистемнаяБД.МОМ; User 10=ИмяПользователя;Password=flap(Hifa;
Второй — подключение к БД, защищенной паролем: Provider=Microsoft.Jet.OLEDB.4.0; Data Source=C:\..АЗащищеннаяПаролемБД.МОВ; Jet OLEDB;Database Password=napcyib;
Поставщик OLE DB для драйверов ODBC Если вы работали с ADO, то, возможно, знакомы с поставщиком OLE DB для драйверов ODBC, который обычно называют по его кодовому имени — Kagera. В па-
48
Часть II Подключаемся: использование поставщика данных .NET
кете Microsoft Data Access Components версий младше 2 это единственный компонент с такой функциональностью. Kagera выступает в роли моста между OLE DB и предыдущей технологией доступа к данным. ODBC, преобразуя вызовы АР! OLE DB в вызовы API ODBC. Данный поставщик позволяет разработчикам взаимодействовать с драйверами ODBC при помощи ADO. Взаимодействие с Kagera посредством поставщика OLE DB .NET может показаться сложной задачей, что, в общем-то, правда. Именно поэтому команда разработчиков Microsoft создала поставщик ODBC .NET (подробнее о нем — в приложении А). Для взаимодействия с источником данных через драйвер ODBC следует обратиться к поставщику ODBC .NET. Если вы попытаетесь использовать Kagera совместно с поставщиком OLE DB .NET, система сгенерирует исключение,
Создание строк подключения в коде при помощи диалогового окна Data Links Чтобы создавать строки подключения в коде, в Visual Studio .NET, как и в Visual Studio 6, можно воспользоваться диалоговым окном Data Links. В нем следует выбрать поставщика OLE DB и затем указать расположение источника данных, имя пользователя, пароль и другие атрибуты поставщика. С этим окном вы уже познакомились при создании подключения с помощью мастера Data Form Wizard в главе 2. Чтобы сделать окно Data Links доступным в среде Visual Studio .NET, следует добавить ссылку на библиотеку Data Link. Щелкните свой проект в окне Project Explorer правой кнопкой и выберите Add Reference. Перейдите на вкладку СОМ диалогового окна Add Reference и добавьте ссылки на библиотеки Microsoft ActiveX Data Objects 2.7 Library (обычно ее называют ADO) и Microsoft OLE DB Service Component 1.0 Type Library (рис. 3-2). Примечание Эти библиотеки содержат СОМ-компоненты. Когда вы добавляете ссылку на них, Visual Studio .NET предлагает создать оболочку для библиотек, В нашем примере щелкните Yes. Подробнее о взаимодействии с СОМ — в документации MSDN. Следующий код обращается к окну Data Links и получает возвращаемую им строку подключения, основанную на введенных пользователем данных: Visual Basic .NET Dim objDataLink As New HSDASC.DataLinksO Dim en As New ADODB.ConnectionO objDataLink.PromptEdit(cn)
Console.WriteLineCen.ConnectionString)
Visual C# .NET MSDASC.DataLlnks objDataLink = new MSDASC.DataLinksClassO; ADODB.Connection en = new ADODB.ConnectionClass{); object objCn = (object) en; objDataLink.PromptEdit(ref objCn); Console.WriteLine{cn.ConnectionString);
ГЛАВА 3 Подключение к базе данных
49
Add Reference .N£1
сом (projects)
I Microsoft Office Web Conponents Wizards IMicrosoft OLAP Designer Server Driver Microsoft OLAP Designer Server Driver 8.0 Microsoft OLE DB ActiveX Data Objects I... Microsoft OLE D6 provider for OLAP Servi... I Microsoft OLE DB provider for OLAP Ser/i,.. ; Microsoft OLEjJBPrJvider for OLftP sjervi...
2.1 1,0 1.0 1,0 1.0 1,0 1,0
'Microsoft OLE DE Simple Provider l.BLibr,., 1 5 J Microsoft Outlook 9,0 Object Library 9.0 ! Microsoft PttureCliD Control 6.0 (5P3) 1.1 j WirrncnFf PniunrPriirir Ч П Ohi^rt I ihrsrv ?.f>
E:^0fFice2K\0fries\MSO., D^ogram Fiies\Commo, D:\Prcgram FilesKornmo. D:\Program Fies\Cornmo. D;\Prcgram File;\Commo, C^Program Fites|commo. D;\Ptograoi Fj!es\Comrrra,
E:\Oflice2K\Office|M5O. D:iw[WNT\System3?\PI, F:IOffir4?k-lnfFirplM^PP
Microsoft ActiveX DaLa Objects 2.7 Library Microsoft OLE DB'Sen/ice Component l.DTyjleLbrmY COM
Рис. 3-2. Диалоговое окно Add Reference Если вы, как и я, не способны запомнить все атрибуты строки подключения, воспользуйтесь диалоговым окном Data Links: здесь можно быстро задать параметры и просмотреть получившуюся строку подключения. Это позволяет сконцентрироваться на более серьезных проблемах, например на том, что пишет Питер Гэмонс (Peter Gammons) о моей любимой команде «Red Sox» Б своей колонке Webузла ESPN. Создание строк подключения вручную при помощи диалогового окна Data Links Вам не придется писать код, который проверял бы строки подключения, создаваемые средствами диалогового окна Data Links. Достаточно лишь создать файл с расширением .udl. и он будет связан с окном Data Links. Дважды щелкните этот файл и задайте необходимые параметры на вкладках открывшегося диалогового окна. UDL-файл — это простой текстовый файл, который можно просматривать в текстовом редакторе типа Notepad. Вуаля! Новая строка подключения готова. Использование .udl-файлов в строке подключения Теперь не требуется жестко задавать строку1 подключения в коде приложения или создавать ее динамически, достаточно сослаться на .udl-файл. Это позволяет программе установки (или пользователю, если вы по своей природе доверчивы) сгенерировать нужную строку подключения и поместить ее в .udl-файл. Для ссылки на .udl-файл в строке подключения используется пара «параметр — значение», например: File Name=MyDataLink.udl;
50
Часть II
Подключаемся: использование поставщика данных .NET
Если полный путь к .udl-файлу не указан, поставщик данных OLE DB .NET ищет этот файл в текущем рабочем каталоге приложения. В строке подключения разрешается также задать относительный путь: File Name=SettingsSubOir\HyOataL.ink.ucll;
Открытие и закрытие соединений Получив объект QleDbConnection с действительной строкой подключения, установите соединение для взаимодействия с хранилищем данных. Чтобы открыть соединение, вызовите метод Open объекта OleDbConnection:
Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB; Data Source=(local)\NetSDK;" & _ "Initial Catalog=Nortriwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn) cn.OpenO
Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); cn.OpenO;
Закрыть соединение можно, вызвав метод Close-
Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New 01eDbConnection{strConn) cn.OpenO
cn.Close() Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Tru3ted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); on.OpenO; cn.Close(); Если используется пул соединений, при уничтожении объекта Connection реальное соединение с источником данных не разрывается.
Использование пула соединений Открытие и закрытие соединений с БД — дорогостоящие операции. Использование пула соединений при разработке многоуровневого приложения позволяет значительно повысить его производительность.
ГЛАВА 3 Подключение к базе данных
51
Что такое пул соединений Концепция пула соединений весьма простя. Представьте многоуровневое приложение, аналогичное изображенному на рис. 3-3. С ервер промежуточного уровня Клиентское приложение Клиентское приложение Клиентское приложение Клиентское приложение Клиентское приложение
Бизйее-обшт ч
1
—*
\ \Ч \
Connection
V
\
Бизнес-объект
Connection
— М
4-
я •
/
Бизйве-оёьт
Connection
s
/
/
Ь
= "08") { //Вставьте сюда запрос
Свойство State Возвращает текущее состояние соединения как элемент перечисления ConnectionState из пространства имен SystemData. В табл. 3-2 перечислены константы, значения и описания возможных состояний соединения. Таблица 3-2.
Константы, обозначающие состояние соединения
Константа Broken
Значение 16
Closed Connecting
0 2
Executing
4
Fetching
8
Open
1
Описание Соединение разорвано. Оно считается таковым, если было открыто и затем по какой-то причине (проблемы с сетью, перезагрузка сервера и т.д.) потеряло возможность взаимодействовать с источником данных. В начальной версии ADO.NET эта константа не используется Соединение закрыто Соединение устанавливается. В начальной версии ADO.NET эта константа не используется Соединение выполняет запрос. В начальной версии ADO.NET эта константа не используется Соединение занято выборкой данных. В начальной версии ADO.NET эта константа не используется Соединение открыто
Перечисление ConnectionState содержит ряд значений, не используемых в начальной версии ADO.NET. В текущей версии данной объектной модели свойс то State объекта Connection возвращает только Open или Closed. В следующих вереи-
70
Часть II Подключаемся: использование поставщика данных .NET
ях, возможно, будут поддерживаться комбинации этих значений, указывающие; например, что соединение открыто и выполняет запрос. Чтобы узнать, что значение свойства State изменилось, можно использовать событие StateCbange объекта Connection.
Методы объекта OleDbConnection В таблице 3-3 перечислены методы объекта OleDbConnection. Методы, предоставляемые большинством объектов .NET Framework, например GetType и ToString, опущены. Таблица 3-3. Наиболее часто используемые методы объекта OleDbConnection Метод BegiriTransaction CbangeDatabase Close CreateCommand GetOleDbSchemaTable Open ReleaseQbjectPool
Описание При открытом соединении начинает транзакцию При открытом соединении переключает вас на указанную БД Закрывает соединение Создает объект OleDbCommand для текущего соединения Получает информацию схемы из источника данных Открывает соединение Удаляет соединение из пула соединений OLE DB
Метод Beg/n Transaction Для начала транзакции по открытому соединению (это требуется, например, чтобы задать блокировку на данные или убедиться, что вы сможете подтвердить или откатить серию изменений в хранилище данных) вызовите метод BeginTramaction объекта Connection. Он вернет объект Transaction (подробнее о нем — в разделе главы 10, посвященном обновлению БД), Примечание Разработчики, использовавшие объекты соединения в ADO, RDO и ОАО, вероятно, ожидают, что у объекта Connection имеются методы для подтверждения и отката транзакции. В объектной модели ADO.NET метод BeginTransaction генерирует новый объект Transaction. Чтобы подтвердить или откатить транзакцию, вызовите соответственно методы Commit и Rollback этого объекта. Поскольку BeginTransaction создает новую транзакцию, связывает ее с создавшим ее соединением и инициализирует транзакцию, использование данного метода объекта Connection может значительно упростить ваш код. Следующие фрагменты кода имеют аналогичную функциональность: Visual Basic .NET Dim txn As OleDb.QleDbTransaction = cn.BeginTransaction()
Dim txn As New OleDb.OleDbTransactionO txn.Connection = en txn.BeginO
ГЛАВА 3
Подключение к базе данных
71
Visual C# .NET OleDbTransaction txn = cn.BeginTransaction{); — или —
OleDbTransaction txn = new OleDbTransactionO; txn.Connection = en; txn.Begin(); Метод ChangeDatabase Как уже говорилось ранее, на одном сервере SQL Server может размещаться несколько БД. Чтобы при работе с SQL Server изменить текущую БД, выполните запрос, аналогичный следующему: USE Northwind
ADO.NET также предоставляет более изящный способ смены БД. Метод ChangeDatabase объекта Connection упрощает данный процесс. Следующие фрагменты кода эквивалентны: Visual Basic .NET Dim en As New QleDbConnection(strConn) cn.0pen{) en.ChangeDatabase{"Northwind")
— или Dim en As New OleDbConnection(strConn) cn.OpenQ Dim cmd As OleDbCommand = cn.CreateCommandO cmd.CommandText = "USE Northwind" cmd.ExecuteNonQueryO
Visual C# .NET OleDbConnection en = new OleDbConnection(strConn); cn.0pen(); en.ChangeDatabase("Northwind");
— или OleDbConnection en = new OleDbConnection(strConn); cn.OpenO; OleDbCommand cmd = cn.CreateCommandO; cmd.CommandText = "USE Northwind"; cmd.ExecuteNonOueryC);
72
Часть И
Подключаемся: использование поставщика данных .NET
Метод Close Для закрытия соединения вызывают метод Close объекта Connection. Помните: при использовании пула соединений физическое подключение к источнику данных просто помещается в пул. При вызове метода Close объекта Connection, уже помеченного как уничтоженный, приложение не генерирует исключений. Метод CreateCommand Позволяет создавать новые объекты Command. Он не принимает аргументов и возвращает новый объект Command, свойству Connection которого задан объект Connection, создавший данный объект Command. Следующие фрагменты кода эквивалентны: Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB; Data Source=(local)\NetSDK;" & "Initial Catalog=Nortnwind;Trusted_Connection=Yes;" Dim en As New OleDbConnectlon(strConn) Dim cmd As OleDb.OleDbCommand = cn.CreateCommand()
— или -
Dim strConn As String = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn) Dim cmd As New OleDb.OleDbCommandO cmd.Connection = en
Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new QleDbConnection(strConn); OleDbCommand cmd = cn.CreateCommand();
— или — string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); OleDbCommand cmd = new OleDbCommandO; cmd.Connection = en; Метод GetOleDbSchemaTable Позволяет получить информацию схемы о БД. Чтобы указать нужный тип информации схемы (таблицы, столбцы, хранимые процедуры и т.д.), передайте методу GetOleDbSchemaTable значение из перечисления OleDbScbemaGuid. Метод GetOleDbScbemaTable также принимает обязательный параметр Restrictions, выступающий в качестве фильтра для возвращаемых сведений схемы. Например, это позволит получать информацию не обо всех столбцах БД, а только о столб-
ГЛАВА 3 Подключение к базе данных
73
цах конкретной таблицы. Параметр Restrictions содержит массив значений. Каждый тип схемы допускает использование разных наборов ограничений. Чтобы получить информацию обо всех столбцах всех таблиц вашей БД, опустите параметр Restrictions: Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB;Data Source={local)\NetSDK; " & "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn) cn.Openf) Dim tbl As DataTable tbl = en.GetOleDbSchemaTablefOleDbSchemaGuid,Tables, Nothing) Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local}\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); cn.OpenO; DataTable tbl; tbl = en,GetOleDbSchemaTableCOleObSchemaGuid.Tables, null); Если вы хотите получить столбцы конкретной таблицы, воспользуйтесь параметром Restrictions и укажите имя нужной таблицы. В документации MSDN, говорится, что структура массива Restrictions для элемента Table перечисления OleDbScbemaGuid должна быть такой: {"КАТАЛОГ^ТАБЛИЦЫ",
"СХЕМА_ТАБМЦЫ",
"ИМЯ_ТАБЛИЦЫ",
"ИМЯ.СТОЛБЦА"}
Таким образом, следующий код возвращает список столбцов таблицы Customers: Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB;Data Source=(local)\NetSDK" & _ "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn) cn.OpenO Dim objRestrictions As ObjectO objRestrictions = New ObjectO {Nothing, Nothing, "Customers", Nothing} Dim tbl As DataTable tbl = en.GetQleDbSchemaTable(01eDbSchemaGuid.Columns, objRestrictions) Visual C# .NET n
string strConn = "Provider=SQLOLEDB; Data Source=(local)\\NetS K;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); cn.OpenO; string strRestrictions; object[] objRestrictions; objRestrictions = new object[] {null, null, "Customers", null};
4-5956
74
Часть II
Подключаемся: использование поставщика данных .NET
DataTable tbl; tbl = en.Get01eDbSchemaTable(01eDbSchemaGuid.Columns, objRestrictions);
Подробнее о составляющих параметра Restrictions для конкретных значений OleDbScbemaGuid — в документации MSDN. Метод GetOleDbSchemaTable возвращает объект DataTable (подробнее о нем в главе б), содержащий нужную вам информацию схемы. Структура возвращенного методом объекта DataTable зависит от типа запрошенной вами схемы. Следующий код просматривает записи таблицы со списком полей, возвращенной методом GetOleDbSchemaTable:
Visual Basic .NET tbl = en.GetOleDbSchemaTableCOleDbSchemaGuid.Columns, strRestrictions) Console.WrlteLine("Columns in Customers table:") For Each row In tbl.Rows Console.WriteLine(vbTab & row("COLUMN_NAME").ToString(}) Next row
Visual C# .NET tbl = en.GetOleDbSchemaTable(OleDbSchemaGuid.Columns, strRestrictions); Console.WriteLine("Columns in Customers table:"); foreach(DataRow row in tbl.Rows) Console.WriteLine("\t" + row["COLUMNJ№ME"].ToString()); Можно создать простое приложение, которое будет с помощью метода GetOleDbSchemaTable выводить информацию схемы о вашей БД (таблицы, представления, хранимые процедуры и т.д.) почти так же, как это делает Server Explorer. Метод GetOleDbSchemaTable основывается на функциональности поставщика OLE DB, используемого вашим объектом OleDbConnection. He все поставщики предоставляют все методы для работы со схемой. Если вы запросите схему, не поддерживаемую вашим поставщиком OLE DB, приложение сгенерирует перехватываемое исключение.
Метод Орел Для установки соединения с БД вызывают метод Open объекта Connection. Объект пытается подключиться к БД, используя значение своего свойства ConnectionString. Если соединение установить не удалось, объект Connection генерирует исключение. Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB;Data Source=(local)\NetSOK;" & "Initial Catalog=Northwind;TrtJSted_Connection=Yes;" Dim en As New OleDbConnection(strConn) Try cn.0pen() Catch ex As Exception Console.WriteLineC"Attempt to connect failed!" & vbCrLf & ex.Message) End Try
ГЛАВА 3 Подключение к базе данных
75
Visual C# .NET string strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleObConnection en = new OLeDbConnection(strConn);
try { cn.OpenO;
} catch (Exception ex) { Console.WriteLine{"Attempt to connect failed!\n" + ex.Message); }
Если вызвать метод Open открытого объекта Connection, соединение будет закрыто и повторно открыто. При использовании пула соединений в этом случае, возможно, будет создано дополнительное соединение с источником данных, Когда начальное соединение закрывается, оно помещается в пул. Однако поскольку пул соединений обрабатывается отдельным потоком, в момент, когда объект Connection запросит соединение с источником данных, основываясь на строке подключения, начального соединения может не оказаться в пуле. Метод ReleaseObjectPool Позволяет управлять пулом соединений OLE DB из ваших компонентов. Если вызвать метод ReleaseObjectPool для объекта Connection или непосредственно для класса OleDbConnection, соединение будет удалено из пула. По правде сказать, этот метод используется очень редко. В бета-версии Visual Studio .NET большинство разработчиков применяли метод ReleaseObjectPool, чтобы действительно закрыть физическое соединение с источником данных, а не помещать его в пул. В таком случае лучше сразу создать соединение, которое не будет помещаться в пул. Для этого вставьте в строку подключения следующий фрагмент: OLE DB Servlces=-4; В результате после вызова метода Close класса OleDbConnection соединение с источником данных закрывается, а не помещается в пул.
События объекта OleDbConnection Объект OleDbConnection предоставляет два события — InfoMessage и StateCbange (табл. 3-4). Таблица 3-4. События объекта OleDbConnection Событие InfoMessage StateChange
Описание Наступает, когда объект Connection получает от источника данных информационное сообщение Наступает при изменении свойства State объекта OleDbConnection
76
Часть II
Подключаемся: использование поставщика данных .NET
Событие InfoMessage Некоторые СУБД, например SQL Server, поддерживают информационные сообщения. SQL Server позволяет передавать сообщения клиентам с помощью команды PRINT. Такие сообщения не являются сообщениями об ошибках и не включают результаты запросов. Событие InfoMessage объекта Connection позволяет перехватывать информационные сообщения, как показано ниже. Visual Basic .NET Dim strConn As String = "Provider=SQLOLEDB; Data Source=(local)\NetSDK; " & "Initial Catalog=Northwind;Trusted_Connection=Yes; " Dim en As New 01eDbConnection{strConn) AddHandler en. InfoMessage, AddressOf cn_InfoHessage cn.OpenO With cn.CreateCommandO .CommandText = "PRINT 'Hello ADO. NET!'" . ExecuteNonQueryC ) End With Public Sub cn_InfoMessage(ByVal sender As Object, ByVal e As System. Data. OleDb.OleDblnfoMessageEventArgs) Console, WriteLineC'InfoMessage event occurred") Console. WriteLinefvbTab & "Message received: " & e. Message) End Sub
Visual C# .NET string strConn = "F'rovider=SQLOLEDB; Data Source=(local)\\NetSDK; " + "Initial Catalog=Northwind;Trusted_Connection=Yes; "; OleDbConnection en = new OleDbConnection(strConn); en. InfoMessage += new OleDblnfoMessageEventHandlertcn^InfoHessage); cn.OpenO; OleDbCommand cmd = cn.CreateCommandO; cmd. CommandText = "PRINT 'Hello ADO. NET"'; cmd. ExecuteNonQueryO; static void cn_InfoMessage(object sender, OleDblnfoMessageEventArgs e) < Console. WriteLineC'InfoMessage event occurred"); Console. WriteLine< "\tMessage received: " + e. Message);
; Примечание SQL Server также способен генерировать информационные сообщения с помощью команды RAISERROR. Информационными считаются сообщения, степень значимости которых меньше 10. Подробнее — в справочной системе SQL Server Books Online.
ГЛАВА 3 Подключение к базе данных
7/
Фрагменты кода на Visual Basic .NET, использующие события В Visual Basic .NET предусмотрено два способа добавления кода для обработки событий, предоставляемых каким-либо объектом. Первый, который я и применяю в книге, — посредством оператора Addttandler, Кроме того, можно добавить код для обработки событий, требующий ввода меньшего объема текста. На рис. 3-13 показан редактор кода Visual Basic. Имеется переменная сп, область действия которой — уровень модуля. Оператор Dim содержит ключевое слово WithEvents. Объявив переменную с помощью этого слова, вы без труда, средствами Visual Basic .NET, создадите процедуры для обработки событий объекта.
%! Mudufel.vfa |
ule Hodulel Dial UlthEve sub HeinO en - Нем 01 cn.QpenO cn.ciosei; Sub
Рис. 3-13. Добавление кода для обработки событий в Visual Basic .NET Над кодом располагаются два раскрывающихся списка. В левом списке перечислены модуль кода и все объектные переменные из области действия модуля, которые предоставляют события. Как видно, в данном списке выбран объект QleDbConnection. Если выбрать в левом списке переменную, предоставляющую события, в правом списке появится перечень этих событий. Когда вы выберете событие, Visual Basic .NET создаст процедуру с соответствующей подписью, предназначенную для обработки этого события, Данная возможность очень удобна для разработчиков, однако создает проблемы авторам. Область действия объектных переменных, объявленных с ключевым словом WitbEvents; должна быть на уровне модуля, т. е. я каждый раз должен указывать это при объявлении переменных в коде. Нужные фрагменты кода выглядят так: см. след. стр.
78
Часть II Подключаемся: использование поставщика данных .NET
'На уровне модуля Dim WithEvents en As QleObConnectlan Dim strCorvn As String * "Provider*SQlQLEDB;Data SQurce==(local)\NetSDK;" & "Initial Catalog=Northwlnd;Trusted^ConnectiORi=Yes; en = New QleDbGonnectiorKstrConn) AddHandler cn.StateChange, AddressQf cn_StateChange cn.GpenO Public Sufr erv_StateChange(ByVal sender AS Object, _ 8yVal e As System. Data. StateChange Event A rgs) Console. VfriteLlne("StateChaRge event occurred") Console, WriteUne(vbTab & "From " & e.OriginalState.ToString) Console. WriteUf}e AS SELECT CustomerlD, CompanyName, ContactName, ContactTitle FROM Customers WHERE CustomerlD = ^CustomerlD RETURN
Примечание Некоторые БД, например Oracle, не могут таким способом возвращать набор результатов из вызова хранимой процедуры. Подробнее о выборке результатов из хранимых процедур Oracle при помощи ADO.NET — в Базе знаний Microsoft. Как же вызвать эту процедуру из объекта Command? Один из способов — воспользоваться свойством CommandType данного объекта. Ему можно задать любое значение из перечисления CommandType: Text, TableDirect или StoredProcedure, Значение свойства CommandType по умолчанию — Text. Задавая свойству CommandType значение StoredProcedure, вы сообщаете объекту Command, что вызываете хранимую процедуру Объект Command совместит значение свойства CommandType с информацией набора Parameters и сгенерирует синтаксис вызова хранимой процедуры: Visual Basic .NET Dim en As New OleDbConnection(strConn) cn.0pen() Dim cmd As OleDbCommand = cn.CreateCommandO With cmd .CommandText = "GetCustomer" .CommandType = CommandType.StoredProcedure .Parameters.Add("@CustomerID", OleDbType.WChar, 5) .Parameters(O).Value = "ALFKI" End With Dim rdr As OleDbDataReader = cmd.ExecuteReaderO If rdr.Read() Then Console.WriteLine(rdr{"CompanyName")) Else Console.WriteLine("No customer found") End If rcfr.CloseO en.Closet)
ГЛАВА 4
Выполнение запросов к базе данных
97
Visual C# .NET OleDbConnection en = new QleDbConnection(strConn); cn.OpenO; OleDbCommand cmd = cn.CreateCotnmand{); cmd.CommandText = "GetCustomer"; cmd.CommandType = CommandType.StoredProcedure; cmd. Parameters. Add( "©Customer-ID", OleDbType.WChar, 5); cmd.Parameters[0].Value = "ALFKI"; OleDbDataReader rdr = cmd.ExecuteReaderO; if
(rdr.ReadO) Console.WriteLine{rdr["CompanyName"]); else Console.WrlteLineC'No customer found"); rdr.CloseO; cn.CloseO;
Стандартный способ вызвать хранимую процедуру — воспользоваться следующим синтаксисом: {? = CALL KyStoredProc Схема включает записи, которые ссылаются на таблицы и столбцы, связывают данные двух таблиц (sql:relationship) и помогают поставщику SQL XML .NET Data Provider определить SQL-тип данных столбца (sqldatatype). Тем не менее эта схема демонстрирует лишь малую толику возможностей, доступных при совместном использовании файла XML-схемы и поставщика SQL XML .NET Data Provider. Подробнее об этом — в разделе «Using Annotations in XSD Schemas» документации SQL XML3. Создав файл схемы, можно задать путь к нему свойству SchemaPatb объекта SqlXmlCommand. Следующий фрагмент кода выполняет обсуждавшийся выше запрос XPath и, используя описанную схему, возвращает сведения о размещенных конкретным клиентом заказах и их составе. Результаты запроса помещаются в объект XmlDocument. Visual Basic .NET 'Добавьте в начало модуля кода следующие строки Imports Microsoft.Data.SqlXml Imports System.Xml Dim strPathToResults As String = "C:\MyResults.XML" Dim strPathToSchema As String = "C:\HySchema.XSD"
478
Часть III
Автономная работа с данными: объект DataSet модели ADO.NET
Dim strConn As String = "Provider=SQLOLEDB;" & _ "Data Source=(local}\NetSDK;" & _ "Initial Catalog=Nortnwind;" & _ "Trusted_Connection=Yes;" Dim cmd As New SqlXmlCommand(strConn) cmd.SchemaPath = StrPathToSchema cmd.CommandText = "Qrders[CustomerID= T GROSR']" cmd.CommandType = SqlXmlCommandType.XPath Dim rdr As XmlReader = cmd.ExecuteXmlReader() Dim xmlDoc As New XmlDocument() xmlDoc.Load(rdr) rdr.CloseO xmlDoc.SaveCst rPathToResults) ShowXmllnlE(strPathToResults)
Visual C# .NET //Добавьте в начало модуля кода следующие строки using Microsoft.Data.SqlXml; using System.Xml; string strPathToResults = "C:\\MyResults.XHL"; string strPathToSchema = "C:\\MySchema.XSD"; string strConn = "Provider=SQLOLEOB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; SqlXmlCommand cmd = new SqlXmlCommancKstrConn); cmd.SchemaPath = strPathToSchema; cmd.CommandText = "Orders[CustomerID='GROSR']"; cmd.CommandType = SqlXmlCommandType.XPath; XmlReader rdr = cmd.ExecuteXmlReader(); XmlDocument xmlDoc = new XmlDocument(); xmlDoc,Load(rdr); rdr.CloseO; xmlDoc.Save(StrPathToResults); ShowXmlInIE(strPathToResults);
XSLT-трансформация Как уже говорилось, разработано несколько способов для задания формата XMLдокумента, причем два XML-документа могут содержать одинаковые данные, но различаться по схеме. Сопутствующая технология под названием XSLT (extensible Stylesheet Language Transformations) позволяет изменять структуру XML-документов. Можно считать, что XSLT-трансформация — это XML-документ с набором инструкций, описывающих порядок преобразования содержимого другого XMLдокумента. XSLT-трансформации очень удобны, когда нужно изменить структуру документа. Кроме того, XSLT-трансформация позволяет преобразовать XML в HTML. Чтобы применить к результатам своего запроса SQL XML XSLT-трансформацию, задайте свойству XslPatb объекта SqlXmlCommand путь к файлу этой трансформации. Подробнее об этом — чуть дальше.
ГЛАВА 12
Работа с XML-данными
.'-79
Передача обновлений Поставщик SQL XML .NET Data Provider позволяет передавать в БД обновления, У объекта SqIXmlAdapter есть метод Update', позволяющий передавать в БД обновления, хранящиеся в объекте DataSet. Если вы внимательно читали главу 10, наличие такого метода у объекта DataAdapter вас не удивит. Тем не менее SqIXmlAdapter передает обновления иначе, чем другие объекты DataAdapter, Многие из таких объектов (например. OleDbDataAdapter, SqlDataAdapter и OdbcDataAdapter) предоставляют свойства, содержащие объекты Command с логикой, необходимой для передачи обновлений в БД. Обычно эти объекты Command содержат несколько параметров, связанных со столбцами объекта DataTable. При вызове метода Update большинство объектов DataAdapter просматривают записи конкретного объекта DataTable. Обнаружив измененную запись, DataAdapter каждый раз с помощью соответствующего объекта. Command передает отложенное изменение в БД и затем вызывает метод DataRowAcceptCbanges. Объект SqIXmlAdapter работает иначе. В одном из предыдущих разделов этой главы я говорил об XML-документах формата diffgram (рис. 12-5). Вместо того чтобы искать отложенные изменения в объекте DataSet, просматривая объекты DataRow по одному за раз, SqIXmlAdapter обрабатывает имеющиеся в объекте DataSet отложенные изменения, генерируя соответствующий XML-документ формата diffgram. Затем поставщик SQL XML .NET Data Provider полностью обрабатывает этот документ, создавая сложный пакетный запрос для одновременной передачи всех изменений в БД. Просмотрев содержимое XML-документа формата diffgram на рис. 12-5, вы, возможно, поймете, как создавать наборы запросов INSERT, UPDATE и DELETE для передачи отложенных изменений в БД. Без вашей помощи поставщику SQL XML .NET Data Provider создать эти запросы не удастся. Помните файл аннотированной XML-схемы, с помощью которого SQL XML .NET Data Provider преобразовывал запрос XPath в SQL-запрос? Сейчас проблема другая, но решение то же. При работе с запросом XPath мы задаем свойству SchemaPath объекта SqlXmlCommand путь к нашему файлу схемы. Затем воспользуемся объектом SqIXmlAdapter и передадим изменения в БД, убедившись, что для объекта SqlXmlCommand определен файл схемы, включающий сведения о таблицах и столбцах, соответствующих содержимому XML-документа формата diffgram. Фактически передать обновление в БД можно средствами объекта SqlXmlCommand, свойству CommandText которого задано значение DiffGram. Просто создайте XML-документ формата diffgram, вызвав метод DataSetWriteXml. Затем настройте объект SqlXmlCommand для использования этого документа и файла схемы, и ...вуаля! Следующий фрагмент кода демонстрирует данную функциональность: Visual Basic .NET 'Добавьте в начало модуля кода следующие строки Imports Microsoft,Data.SqlXml Imports System.Xml Imports System.10 Dim strConn As String = "Provider=SQLOLEDB;" 4 _
480
Часть 111 Автономная работа с данными: объект DataSet модели ADO.NET
"Data Source=(local)\NetSDK;" & _ "Initial Catalog=Northwind;" & "Trusted_Connection=Yes;" Dim cmd As New SqlXmlComfnand(strConn) Dim strPathToSchema As String = "C:\MySchema.XSD" cmd.SchemaPath = strPathToSchema cmd.CommandText = "Orders[CustomerID='GROSR']" cmd.CommandType = SqlXmlCommandType.XPath cmd.RootTag = "ROOT" Dim da As New SqlXmlAdapter(cmd) Dim ds As New DataSetO da.FillCds) ds.Tables("Orders").Rows(0)("CiJstomerID") = "ALFKI" ds.Tables("Orders").Rows(1)("CiJStonierrD"} = "ALFKI" Dim strPathToDiffGram As String = "C:\MyDiffGram.XML" ds.WriteXml(strPathToDiffGram, XffllWriteMode.DiffGram) cmd = New SqlXmlCommand(strConn) cmd.SchemaPath = strPathToSchema cmd.CommandType = SqlXmlCommandType.DiffGram cmd.CommandStream = New FileStreamfstrPathToDiffGram, FileMode.Open, FileAccess.Read) cmd,ExecuteNonQuery() 'Отмена изменений Dim strSQL As String = "UPDATE Orders SET CustomerlD = 'GROSR' " & _ "WHERE OrderlD = 10268 OR OrderlD = 10785" cmd = New SqlXmlCommand(strConn) cmd.CommandText = strSQL cmd.CommandType = SqlXmlCommandType.Sql cmd. ExecuteNonQueryO
Visual C# .NET //Добавьте в начало модуля кода следующие строки jsing Microsoft.Data.SqlXml; using System.Xml; using System.10; string strConn = "Provider=SQLOLEDB;Oata Source=(locat)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; SqlXmlCommand cmd = new SqlXmlConrniand(strConn); string strPathToSchema = "C:\\MySchema.XSD"; cmd.SchemaPath = strPathToSchema; cmd.CommandText = "Qrders[CustomerID='GROSR']"; cmd.CommandType = SqlXmlCommandType.XPath; cmd.RootTag = "ROOT"; SqlXmlAdapter da = new SqlXmlAdapter(cmd); DataSet ds = new DataSetO; da.Fill(ds);
ГЛАВА 12
Работа с XML-данными
481
ds.Tables["Orders"].Rows[Q]["CustomerID"] = "ALFKI"; ds.Tables["Orders"].Rows[1]["CustomerID"] = "ALFKI"; string strPathToDiffGram = "C:\\MyDiffGrarn.XML"; ds.WriteXml(strPathToDiffGram, XmlWriteMode.DiffGrani); cmd = new SqlXmlCommand(strCcmn); cmd.SchemaPath = strPathToSchema; cmd.CofnmandType = SqlXmlCommandType.DiffGram; cmd.CommandStreain = new FileStream(strPathToDiffGram, FileHode.Open, FtleAccess.Read); cmd. ExecuteNonQueryO; //Отмена изменений string strSQL = "UPDATE Orders SET CustomerlD = 'GROSR' " + "WHERE OrderlD = 10266 OR OrderlD = 10785"; cmd = new SqlXmlCommandCstrConn); cmd.CommandText = strSQL; cmd.ComnmndType = SqlXmlCommandType.Sql; cmd. ExecuteNonQueryO; Примечание Код также включает командный запрос для отмены изменений к БД, благодаря чему этот фрагмент кода можно выполнять многократно. Чтобы убедиться, что SqlXmlCommand передал хранящиеся в XML-документе формата diffgram изменения, определите точку останова перед выполнением последнего запроса, Логика обновления, используемая объектом SqlXmlCommand Прежде чем продолжить, немного поговорим о логике, генерируемой объектом SqlXmlCommand для передачи изменений в БД. Это поможет вам глубже понять преимущества и недостатки передачи обновлений с использованием поставщика SQL XML .NET Data Provider. Когда фрагмент кода вызвал метод SqlXmlCommandExecuteNonQuery для передачи изменений из XML-документа формата diffgram, поставщик SQL XML .NET Data Provider сгенерировал и передал SQL Server следующий пакетный запрос: SET XACT_ABORT ON BEGIN TRAN DECLARE @eip INT,
@r__ Int,
@e__ int
SET @>eip = 0
UPDATE Orders SET CustomerID=N'ALFKI' WHERE ( OrderID=10268 ) AND ( CustomerID=N'GROSR' ) AND ( OrderDate=N'1996-07-30 00:00:00' ) ; SELECT ве_ = @@ERROR, @r__ = @ 1) RAISERROR ( N'SQLOLEDB Error Description: Ambiguous update, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r__ < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty update, no updatable rows found Transaction aborted ', 16, 1)
482
Часть III Автономная работа с данными: объект DataSet модели ADO.NET
UPDATE Orders SET CustomerID=N'ALFKI' WHERE ( OrderID=10785 ) AND ( CustomerID=N'GROSR' ) AND ( OrderDate=N'1997-12-18 00:00:00' ) ; SELECT @e__ = @@ERROR, @r__ = @$ROWCOUNT IF (@e__ != 0 OR @r__ != 1) SET @eip = 1 IF Or__ > 1) RAISERROR С N'SQLOLEDB Error Description: Ambiguous update, unique identifier required Transaction aborted ', 16, 1) ELSE IF (@r__ < 1) RAISERROR ( N'SQLOLEDB Error Description: Empty update, no updatable rows found Transaction aborted ', 16, 1) IF (0eip != 0) ROLLBACK ELSE COMMIT SET XACT.ABORT OFF
Для начала он указывает SQL Server отменить текущую транзакцию, если та вызовет ошибку, начинает транзакцию и определяет ряд переменных для хранения данных. Затем код выполняет первый запрос UPDATE и помещает данные в переменные, чтобы определить, есть ли ошибки и сколько записей затронул запрос. Если запрос затронул одну запись и не сгенерировал ошибку, код продолжает выполнять командные запросы и проверять, успешно ли проходит обновление. Когда все командные запросы выполнены, код при отсутствии ошибок подтверждает транзакцию и деактивирует параметр, указывавший SQL Server откатить транзакцию в случае ошибки. Это впечатляющий и правильно структурированный код. Он представляет собой большой и сложный пакет запросов, который, тем не менее, минимизирует число итераций, необходимых для передачи изменений в БД и проверки успешности данных операций. Если вы хотите реализовать подобную функциональность в своем приложении, генерируя собственные запросы, этот пакет — великолепный пример для подражания. Понимать отдельные запросы пакета вам не обязательно, но при передаче обновлений с использованием поставщика SQL XML .NET Data Adapter все же помните несколько правил. • Поставщик помещает данный пакет обновлений в транзакцию и, если при выполнении хотя бы одного запроса UPDATE возникнет ошибка, полностью откатывает эту транзакцию. Таким образом, передаются либо все обновления, либо ни одного. • При передаче изменений поставщик не выбирает из БД каких-либо данных. По завершении передачи вы не увидите новых значений автоинкремента или значений типа timestamp. • Если изменения передаются средствами метода SqlXmtAdapter.Update, по успешном завершении передачи изменений объект SqlXmlAdapter вызывает метод DataSetAcceptChanges. SqlXmlAdapter не вызывает методы AcceptChanges только тех объектов DataTable, которые указаны в файле схемы.
Простой пример с использованием ADO.NET и XML До этого момента я показывал только изолированные примеры использования XMLфункций ADO.NET Настала пора объединить их в одном приложении, которое продемонстрирует совместную мощь языка XML и XML-функций ADO.NET.
ГЛАВА 12 Работа с XML-данными
483
В приложении применяются параметризованные запросы, возвращающие список заказов конкретного клиента. Но прежде, чем закатить глаза, заметьте: теперь мы будем получать больший объем информации — данные из четырех связанных таблиц — Customers. Orders, Order Details и Products. Таким образом, список заказов станет четче — в нем появятся названия компаний, а также названия заказанных товаров. Приложение преобразует результаты запросов в XML-формат, с помощью XSLTтрансформации преобразует полученные XML-данные в HTML-код и затем выво дит этот код в Internet Explorer (рис. 12-8). icfosoft Interne! Еч)1г.г.0Je
Etft
-•
;
!j.iev*
"'•
Fjvoritei г>
Tods
yelp
L *^
Order Histoty Гот The Cracker Br,% 3 Orders
Pro dart Rossle Sauerkraut
Quantity 10
Tliunnger Rostbiahvum Cuia Malacca
• I Produrt ". i i
Quantity
Unit Price
Item Total
$4560
Ed 56 00
$123 79
£74274
£1945
I1S450
Item Total $18600
Laughing Lumberjack Lager
Product 1
Boston Crab Meat
Fib Mix
Рис. 12-8.
Web-страница, генерируемая приложением-примером
Наш пример — это консольное приложение, запускающее экземпляр Internet Explorer. Поскольку в качестве отправной точки используется именно консольное приложение, пример получился не слишком шикарный, но зато его легко использовать как ресурс для дальнейшей работы. Например, в двух случаях очень полезно преобразовать результаты запросов в HTML-код: при создании Web-приложении и формировании отчетов. Полагаю, это самые яркие примеры использования XML-функций ADO.NET. Помните: в работе с HTML и XSLT я, в общем-то, новичок. Я создал очень простую Web-страницу с помощью Microsoft FrontPage и затем сравнил ее структуру со структурой XML-документа, сгенерированного мной при помощи ADO.NET и содержавшего данные о моих заказах. Затем я по материалам книги, посвященной XSLT, попытался разобраться, как создать XSLT-трансформацию для преобразования XML-кода в HTML-код. Я открыто говорю о недостатке опыта: • потому что любой программист БД, не будучи профессионалом в XML, может в достаточной степени изучить XSLT для преобразования XML в HTML; • чтобы извиниться за то, что Web-страница получилась не очень привлекательной,
484
Часть III
Автономная работа с данными: объект DataSet модели ADO.NET
Два пути к одному конечному пункту Вообще-то я вас обманул, поскольку создал не один, а два примера. Оба они используют одинаковую XSLT-трансформацию и создают одинаковые HTML-файлы, но по-разному генерируют XML-документ с данными из БД Norhtwind. Первый пример, DataSetToHTML, подключается к локальной БД Norhtwind .NET SDK MSDE при помощи поставщика OLE DB .NET Data Provider. Он использует стандартные запросы и помещает их результаты в объект DataSet. Чтобы обратиться к содержимому DataSet, как к XML-документу, и выполнить XSLT-трансформацию для создания HTML-страницы, первый пример создает объект XmlDataDocument, связанный с объектом DataSet. Второй пример, SqlXmlToHTML, основан на поставщике SQL XML .NET Data Provider и XML-шаблоне запросов, который обращается к БД Northwind, но использует синтаксис FOR XML. Свойству XslPath объекта SqlXmlCommand во втором примере задан путь к файлу XSLT-трансформации,
ADO.NET и XML: счастливая пара ADO.NET предоставляет широкую поддержку языка XML. Благодаря XML-функциям ADO.NET, разработчики без труда смогут переходить от традиционных объектов доступа к данным к XML-объектам и обратно. Объект DataSet позволяет считывать и записывать данные и/или информацию схемы в XML-формате. Объект XmlDataSet позволяет легко обращаться к содержимому DataSet как к XML-документу. Поставщик SQL XML .NET Data Provider позволяет использовать XML-функции SQL Server 2000 и помещать результаты запросов в формате XML в файлы, XML-документы и объекты DataSet.
Вопросы, которые стоит задавать почаще Вопрос. Объекты DataSet, DataTable и DataColumn позволяют довольно гибко определять структуру XML-документа, создаваемого с помощью WriteXml, однако мне требуются еще более широкие возможности управления. Я хочу добавить в XML-файл инструкцию по обработке, ссылающуюся на таблицу стилей XSLT. Как это сделать? Ответ. Воспользуйтесь объектом XmlDataDocument и обращайтесь к содержимому DataSet как к XML-документу. Задайте свойству EnforceConstraints объекта DataSet значение False и затем с помощью объекта XmlDataDocument определите нужную вам структуру XML-документа. В обсуждаемой ситуации годится метод CreateProcessinglnstiiiction объекта XmlDataDocument. Затем вызовите метод XmlDataDocument.' Save и сохраните результаты в XML-файл.
Visual Basic .NET Dim ds As New DataSetC) ds.EnforceConstraints = False Dim xmlDoc As New XmlDataDocument(ds) Dim strPI as String = "type='text/xsl' href='HyTransform.XSLTT" Dim xmlPI as XmlProcessinglnstruction
ГЛАВА 12 Работа с XML-данными
485
xmlPI = xmlDoc.Create?rocessinglnstruction("xml-stylesheet", strPI) xmlDoc.InsertBeforefxmlPI, xmlDoc.DocumentElement) Dim strPathToXmlFile As String = "C:\MyData.XML" xmlDoc. Save(strPathToXmlFile)
Visual C# .NET DataSet d's = new DataSetO; ds.EnforceConstraints = false; XmlDataDocument xmlDoc = new XmlDataDocument(ds); string strPI = "type='text/xsl' href='MyTransfortn.XSLT'"; XmlProcessinglnstruction xmlPI; xmlPI = xmlDoc.CreateProcessingInstruction("xml-stylesheet", strPI); xmlDoc.InsertBeforeCxmlPI, xmlDoc.DocumentElement); string strPathToXmlFile = "C:\\HyData.XML"; xmlDoc.Save(StrPathToXmlFile);
Вопрос. Я вызываю уже имеющиеся хранимые процедуры, и мне нужно получать результаты запросов в XML-формате, однако. Как это сделать? Ответ. Укажите поставщику SQL XML .NET Data Provider преобразовать результаты запроса с XML-формат, задав свойству ClientSideXml объекта SqlXmlCommand значение True, как показано ниже: Visual Basic .NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & _ "Initial Catalog=Northwind;Trusted_Connection=Yes;" StrSQL = "EXEC CustOrdersOrders 'ALFKI' FOR XML NESTED" Dim cmd As New SqlXmlCommand(strConn) cmd.CommandText = strSQL cmd.ClientSideXml = True cmd.RootTag = "ROOT" Dim xmlDoc As New XmlDocumentO Dim xmlRdr As XmlReader = cmd.ExecuteXmlReader xmlDoc.Load(xmlRdr) xmlRdr.Close Console.WriteLine(xmlDoc.InnerXral)
Visual C# .NET string strConn, strSQL; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; 1 strSQL = "EXEC CustOrdersOrders 'ALFKI FOR XML NESTED"; SqlXmlCommand cmd = new SqlXmlCommand(strConn); cmd.CommandText = strSQL; cmd.ClientSideXml = true;
486
Часть III
Автономная работа с данными: объект DataSet модели ADO.NET
cmd.RootTag = "ROOT"; XmlDocument xmlDoc = new XmlDocumentf); XmlReader xmlRdr = cmd. ExecuteXmlReaderO; xmlDoc,Load(xmlRdr); xmlRdr.Close(); Console.WriteLine(xmlDoc.InnerXml);
Подробнее о преобразовании в формат XML на стороне клиента с помощью поставщика SQL XML .NET Data Provider — в разделе «Comparing Client-Side XML Formatting to Server-Side XML Formatting» файла справки SQL XML 3-
Ч А С Т Ь
4
СОЗДАНИЕ ЭФФЕКТИВНЫХ ПРИЛОЖЕНИЙ С ИСПОЛЬЗОВАНИЕМ ADO.NET
Г Л А В А
13 Создание эффективных Windows-приложений
х1так, вы уже умеете работать с различными объектами модели ADO.NET. Вам предлагают создать объект DataSet и поместить результаты запроса в объект DataTable при помощи объекта DataAdapterl Нет проблем. Требуется добавить объект DataRelation для перемещения между дочерними и родительскими данными двух связанных объектов DataTable' Это сможет даже ребенок. Необходимо создать логику для передачи изменений в БД? Легко. И хотя все эти навыки очень важны, их все же недостаточно, чтобы создать приложение, позволяющее просматривать и редактировать содержимое БД. Вам также потребуется создать пользовательский интерфейс. В этой главе я расскажу о том, как создавать эффективные Windows-приложения, используя полученные вами знания. В первой части этой главы обсуждаются этапы создания приложения, аналогичного созданному нами в главе 2 с помощью мастера Data Form Wizard. Вы узнаете, как связывание с данными экономит время при разработке пользовательского интерфейса приложения, а также о различных способах обновления и подключения. Заключительная часть главы посвящена различным методам работы с данными больших двоичных объектов (binary large object, BLOB) в Windows-приложениях.
Быстрое создание пользовательского интерфейса при помощи связывания с данными Предположим, вам нужно создать интерфейс пользователя. Вы можете написать код для получения из БД данных и передачи в БД изменений, но вам также требуется вывести эти данные на форме и предоставить пользователям возможность
ГЛАВА 13 Создание эффективных Windows-приложений
489
взаимодействовать с ними, добавляя, изменяя и удаляя записи данных. Помимо всего прочего, работу следует сделать как можно быстрее. Понятно, что можно написать код, считывающий содержимое объекта DataRow и выводящий соответствующие данные в элементах управления TextBox формы. Или же код, позволяющий перемещаться по записям данных, а также добавлять, изменять и удалять записи в объекте DataSet. Если бы вам требовалось создать группу приложений, реализующих эти функции и различающихся только по типу обрабатываемых данных, то для каждого из них пришлось бы писать одинаковые базовые процедуры. Пакет Windows Forms из состава Microsoft .NET Framework включает поддержку связывания с данными. Связывание предоставляет функциональность, аналогичную описывавшимся ранее процедурам для вывода содержимого DataSet в различных элементах управления, и реализует функции, позволяющие пользователю изменять это содержимое. Если вкратце, связывание с данными упрощает и ускоряет создание приложений для работы сданными, поскольку уменьшает объем кода, необходимый для создания пользовательского интерфейса. Примечание В действительности связывание с данными позволяет работать не только с объектами DataSet. Элементы управления можно связывать с такими структурами ADO.NET, как объекты DataSet и DataTable, массивы и любые другие объекты, реализующие интерфейс IList. Эта книга посвящена ADO.NET, и поэтому основное внимание я уделю связываншо с данными структур ADO.NET. Подробнее о связывании с данными других структур — в соответствующих разделах документации .NET Framework SDK. Но достаточно вступительных слов. Давайте с помощью связывания с данными создадим простое приложение для приема заказов. Это приложение (рис. 13-1) позволяет просматривать и изменять заказы, размещенные клиентами. Процесс создания приложения разделен на несколько этапов, демонстрирующих отдельные средства связывания с данными. Order Details "roduetlD
UnitPiice Quantity $45.Hf" 15
ЗЭ iG
Рис. 13-1-
iiemTolal " $684.00
S12.CC
Приложение для приема заказов
На прилагаемом к книге компакт-диске записан готовая версия данного приложения, а также версии для каждого из рассматриваемых далее этапов. Кроме того, '7-595Е
490
Часть IV
Создание эффективных приложений с использованием ADO.NET
на диске вы найдете версии приложения, созданные как с использованием Microsoft Visual Basic .NET, так и с использованием Microsoft Visual С* .NET. Примечание Приложение рассчитано на работу с БД Northwind. Инструкции по установке версии Microsoft Desktop Engine (MSDE) и баз данных, поставляемых вместе c.NET Framework SDK, см. в главе 3 этой книги. В числе прочих устанавливается и БД Northwind.
Этап 1. Создание объектов DataAdapter и DataSet Мы создаем Windows-приложение, и поэтому начать следует с создания нового Windows-проекта на языке, который вы предпочитаете. Назовите приложение Chapter 13. Задайте форме по умолчанию JrmEditOrders заголовок Edit Orders. В приложении я задал свойству MaximizeBox формы значение False, а свойству FormBorderStyle — значение Fixed3D. Это гарантирует, что пользователь не сможет изменить размер формы. Задавать указанным свойствам такие значения не обязательно, но мне не нравится, когда пользователи изменяют размер формы, не рассчитанной на это. Приложение предназначено для отображения сведений о заказах конкретного клиента, и в связи с этим нам потребуется объект DataAdapter для выборки информации из БД Northwind. Выберите на вкладке Data панели инструментов элемент управления QleDbDataAdapter и перетащите его на форму. Запустится мастер Data Adapter Configuration Wizard (подробнее о нем — в главе 5). В окне Connection мастера выберите существующее соединение с БД Northwind. Если соединений нет. щелкните New Connection и создайте новое соединение. В окне Query Type оставьте переключатель в положении по умолчанию — Use SQL Statements. Затем в окне SQL Statement введите такой SQL-оператор: SELECT OrderlD, CustomerlD, EmployeelD, OrderDate FROM Orders WHERE CustomerlD = ?
Я выбрал этот запрос по двум причинам. Во-первых, создается очень простое приложение, и поэтому следует ограничить число возвращаемых полей. Во-вторых, вместо того чтобы получать из БД все заказы, требуется получить только заказы конкретного клиента. Помните; если ограничить объем возвращаемых данных, производительность приложения повышается. Завершив работу с мастером, вы увидите в панели компонентов формы объекты QleDbDataAdapter и OleDbConnection. Измените их имена соответственно на daOrders и cnNortbwind. Щелкните в панели компонентов правой кнопкой и выберите в контекстном меню команду Generate DataSet. В открывшемся диалоговом окне измените имя нового класса DataSet на xsdChapterl3 и щелкните ОК. В окне Solution Explorer появится новый элемент — xsdChapterl 3-xsd, а в панели компонентов — экземпляр класса DataSet. Задайте этому экземпляру имя dsChapterlB. Столбец OrderlD таблицы Orders — это столбец с автоинкрементом. Как вы помните из главы 6, в объекте DataSet свойствам AutoIncrementSeed и AutolncrementStep столбцов с автоинкрементом рекомендуется задавать значение -1. Давайте зададим указанные свойства столбца OrderlD в только что созданном нами классе DataSet со строгим контролем типов. В окне Solution Explorer дважды щелкни-
ГЛАВА 13 Создание эффективных Windows-приложений
191
те файл схемы класса DataSet со строгим контролем типов (xsdChapter!3.xsd). Выделите столбец OrderlD. Задайте свойствам AutoIncrementSeed и AutolncremetitStep значение -1. Закройте окно и сохраните изменения.
Этап 2. Добавление связанных с данными элементов управления TextBox На форме уже есть объекты DataAdapter и DataSet. Теперь давайте добавим элементы управления lextBox, при помощи которых на форме будет отображаться информация о конкретном заказе. Чтобы сделать пользовательский интерфейс интуитивно-понятным, мы также добавим для каждого элемента управления метку с описанием отображаемых элементом данных, Сначала добавим метку и элемент управления TextBox для столбца OrderlD. Перетащите с панели инструментов на форму элемент управления Label. Задайте его свойству Name значение IblOrderlD, а свойству Text — значение Order ID:. Затем перетащите с панели инструментов на форму элемент управления TextBox. Задайте его свойству Name значение txtOrderlD, а свойству Text — пустое значение. Сейчас мы по-прежнему имеем дело с простым элементом управления TextBox. Чтобы связать его со столбцом OrderlD объекта DataSet, перейдите в окно Properi ies. Найдите раздел (DataBindings). Если свойства упорядочены по категориям (конфигурация по умолчанию), данный раздел отображается в категории Data. Я предпочитаю упорядочивать свойства по алфавиту; при этом раздел (DataBindings) расположен в верхней части списка свойств. Нам нужно связать содержимое столбца OrderlD со свойством Text элемента управления. Раскройте раздел (DataBindings), выберите элемент Text и щелкните направленную вниз стрелку, чтобы просмотреть список доступных столбцов. В списке указан объект DataSet. Раскрыв его узел, вы увидите список объектов DataTable. В нашем случае список содержит только один объект DataTable — Orders. Раскрыв узел этого объекта, вы увидите список объектов DataColumn. Щелкните столбец OrderlD (рис. 13-2).
Рис. 13-2. Связывание свойства Text элемента управления TextBox с одним из объектов DataColumn из состава DataSet Следуя инструкциям, приведенным ранее, создайте элементы управления Label и TextBox для столбцов CustomerlD, EmployeelD и OrderDate. Используйте те же префиксы имен и тот же формат свойства Text элементов управления. Свяжите
492
Часть IV Создание эффективных приложений с использованием ADO.NET
элементы управления TextBox с соответствующими столбцами объекта DataSet. Расположите элементы управления, как показано на рис. 13-3.
X
:
frmEdtQrdcri.vh [Btsign]
Т! Г1 J и I Solution 'Chapter 13' (1 protect) „Р Chapter 13 '+ i] Rrftrsreti 3 uiismbl/Wo.vb fflffl
*j] Исимвох
Рис. 13-3.
Добавление на форму элементов управления Label и TextBox
Как видно, связать элемент управления TextBox с объектом DataSet в период разработки очень легко. Кроме того, такое связывание можно осуществить программно: Visual Basic .NET txtOrderlD.DataBindings.AddCText", dsChapter13, "Orders.OrderlD") Visual C# .NET txtOrderlD.DataBindings.AddCText", dsChapter13, "Orders.OrderlD"); Этот код связывает свойство Text элемента управления со столбцом OrderlD таблицы Orders из состава объекта DataSet dsChapter!3. Примечание В приложении я задал свойству Readonly элемента управления TextBox, связанного со столбцом OrderlD, значение True, чтобы исключить редактирование содержимого этого столбца пользователями. По умолчанию цвет текста, доступного только для чтения, в элементе управления TextBox — серый, а не черный. Лично мне это не нравится, и я задал свойству ForeColor данного элемента управления значение Black. Кроме того, я назначил свойству TextAlignment элементов управления TextBox, связанных со столбцами OrderlD и EmployeelD, значение Right, поскольку названные столбцы содержат численные данные.
ГЛАВА 13 Создание эффективных Windows-приложений
493
Этап 3. Получение данных Теперь у вас есть объект DataSet и несколько элементов управления TextBox, связанных со столбцами этого объекта, однако нет никаких данных для вывода на экран. Запустив проект, вы увидите форму с пустыми элементами управления Label и TextBox. Это совершенно верно — ведь в объекте DataSet пока нет записей о заказах. Если вы имеете опыт работы с предыдущими версиями Visual Basic, то, возможно, помните, что при выводе связанного элемента управления приложение автоматически выполняло соответствующий запрос и получало его результаты. В .NET все по-другому. Вам потребуется добавить код, чтобы приложение при запуске выбирало из БД записи о заказах. Дважды щелкните форму. Откроется окно кода, и вы увидите процедуру обработки события Load. Перед ней добавьте такую строчку кода: Visual Basic .NET Dim strCustomerlD As String = "ALFKI" Visual C# .NET string strCustomerlD = "ALFKI"; Затем добавьте в процедуру обработки события Load такую строчку: Visual Basic .NET daOrders.SelectCommand.Parameters(O).Value = strCustomerlD daOrders.Fill(dsChapter13.Orders) Visual C# .NET daOrders.SelectCommand.Parameters[0].Value = strCustomerlD; daOrders.Fill(dsChapter13.Orders); Первая строка необходима потому, что запрос, созданный нами для объекта DataAdapter — параметризованный. Добавив этот код и запустив проект, вы увидите на форме сведения о заказе. Кроме того, в правый нижний угол формы приложения я добавил кнопку Close. В ее событии Click я вызываю метод Close формы, чтобы завершить работу приложения. Чтобы сделать то же самое в своем приложении, перетащите кнопку с панели инструментов, задайте ее свойству Name значение btnClose и свойству Text — значение Close. Дважды щелкните кнопку, чтобы просмотреть код обработки ее события Click. Дополните его следующими строками: Visual Basic .NET Me.CloseO Visual C# .NET this.CloseO;
494
Часть IV
Создание эффективных приложений с использованием ADO.NET
Этап 4. Добавление кнопок для перемещения по содержимому объекта DataSet На текущий момент приложение лишь отображает сведения о заказе. Можно добавить код, который проверяет значение свойства Count объекта DataTable Orders из состава нашего объекта DataSet и сообщает, сколько возвращено записей. Приложение не очень-то полезно, если способно отображать лишь один заказ. Давайте реализуем функциональность, обеспечивающую просмотр числа возвращенных записей и перемещение между ними. На форме приложения, под элементами управления с информацией о заказе, я добавил четыре кнопки и метку (рис. 13-4). Кнопки позволяют перемещаться по записям о заказах, а метка показывает текущий номер и общее число заказов. Jjxl Гбб43
Glided of 6
Рис. 13-4. Добавление на форму элементов управления для перемещения по содержимому объекта DataSet Элементы управления предоставляют данную функциональность при помощи экземпляра класса CurrencyManager. И хотя из имени класса следует, что он выступает в роли финансового консультанта, на самом деле именно этот класс обеспечивает функционирование связывания с данными. Windows-форма предоставляет свойство BindingContext, позволяй идее обращаться к объектам CurrencyManager, которые контролируют связанные с данными элементы управления формы. Элементы управления TextBox связаны с одной записью объекта DataTable Orders. Чтобы сменить отображаемую в этих элемента управления запись, измените значение свойства Position экземпляра класса CurrencyManager. Для перехода к следующей записи увеличьте текущее значение свойства Position на единицу, а для перехода к предыдущей записи — уменьшите его аналогичным образом. Вместо того чтобы объяснять назначение и место отдельных строк кода, я решил показать фрагмент кода приложения. Он включает переменную CurrencyManager уровня формы. Код процедуры, обрабатывающей событие Load формы, инициализирует эту переменную и добавляет обработчики событий ItemChanged и PositionCbanged объекта CurrencyManager. Процедуры обработки этих событий задают текст метки, отображающей номер текущего заказа. Кроме того, есть процедуры, обрабатывающие событие Click различных кнопок и соответствующим образом задающие значение свойства Position объекта CurrencyManager.
ГЛАВА 13
Создание эффективных Windows-приложений
Visual Basic .NET 'Переменная уровня формы Dim cmOrders As CurrencyManager Private Sub frmEditOrders_Load. . . cmdrders = CType(BindingContext(dsChapter13, "Orders"), CurrencyManager) AddHandler cmOrders.ItemChanged, AddressOf cmOrders_ItemChanged AddHandler cmOrders.PositionChanged, AddressOf cmOrders_PositionChanged DisplayOrdersPosition() End Sub
Private Sub DisplayOrdersPositlonO IblOrdersPosition.Text = "Order " & cmOrders.Position + 1 & _ " of " & cmOrders.Count End Sub Private Sub cmOrders_ItemChange6(ByVal sender As Object, ByVal e As ItemChangedEventArgs) DisplayOrdersPositionO End Sub Private Sub cmOrders_PositionChanged(ByVal sender As Object, ByVal e As System.EventArgs) DtsplayOrdersPositionO End Sub Private Sub btnOrdersMoveFirst_Click... cmOrders.Position = 0 End Sub Private Sub btnOrdersMovePrevious_Click... cmOrders.Position -= 1 End Sub Private Sub btnOrdersMoveNext_Click... cmOrders.Position += 1 End Sub Private Sub btnOrdersMoveLast_Click... cmOrders.Position = cmOrders.Count - 1 End Sub
Visual C# .NET //Переменная уровня формы CurrencyManager cmOrders; private void frmEdltOrders_Load...
495
496
Часть IV
Создание эффективных приложений с использованием ADO. NET
i cmOrders = (CurrencyManager) BindingContext[dsChapterl3, "Orders"]; cmOrders.ItemChanged += new ItemChangedEventHandler(cinOrders_ItemChanged); cmOrders, PosittonChanged += new EventHandler(cmOrders_PositionChanged); DisplayOrdersPositionf) ;
! private void DisplayOrdersPositionQ ! IblOrdersPosition.Text = "Order " + (cmOrders. Position + 1} + " of " + cmOrders, Count;
I private void cmOrders_ItemChanged(object sender, i DisplayOrdersPositionf ) ;
ItemChangedEventArgs e)
private void cmOrders_PositionChanged(object sender, EventArgs e) \ DisplayOrdersPositionf ) ;
private void btnQrdersMoveFirst_Click(object sender, System. EventArgs e) { cmOrders. Position = 0; } private void btnOrdersMovePrevious_Click(object sender, { cmOrders. Position-; }
System. EventArgs e)
private void btnOrdersHoveNext_Click(object sender, < cmOrders. Position**;
System. EventArgs e)
private void btnOrdersMoveLast_Click(object sender, { cmOrders. Position = cmOrders. Count - 1;
System. EventArgs e)
;-
Этап 5. Добавление кнопок Add и Delete Теперь пользователь получил возможность просматривать все записи о заказах, возвращенные объектом DataAdapter, а также изменять состав заказа, редактируя содержимое связанных элементов управления TextBox.
ГЛАВА 13
Создание эффективных Windows-приложений
497
Запустите форму и измените значение поля EmployeelD в первом заказе. Вы помните оригинальное и новое значение данного поля? Отредатировав значение поля EmployeelD в первом заказе, перейдите к следующему и затем снова вернитесь к первому заказу. Вы увидите, что новое значение поля EmployeelD по-прежнему на месте. Я изо всех борюсь с желанием чудесным водевильным голосом спросить: «Это введенное вами значение поля EmployeelD?» И хотя форма позволяет редактировать содержимое заказа, добавлять и удалять заказы нельзя... пока нельзя. Объект Currency Man age r предоставляет методы, позволяющие добавлять и удалять элементы из структуры данных, с которой связаны элементы управления. Метод AddNeiv добавляет новый элемент, а метод RemoveAt — удаляет существующий элемент. Элементы управления, связанные с объектом Сшгепсу Manager, соответствующим образом реагируют на вызов любого из этих методов. Если вы добавили элемент с помощью метода AddNew, элементы управления отобразят содержимое новой записи. При вызове метода RemoveAt элементы управления отобразят содержимое следующей доступной записи. Visual Basic .NET Private Sub btnOrdersAdd_CHck. . . cmOrders.AddNewO End Sub Private Sub btnOrdersDelete_Click. . . If cmOrders. Count > 0 Then cmOrders.RemoveAt (cmOrders. Position) Else MessageBox. Show("No Order to Delete!", "Delete Order", HessageBoxButtons.OK, MessageBoxIcon. Error) End If End Sub Visual C# .NET private void btnOrdersAdd_Click(object sender, System. EventArgs e)
< cmOrders.AddNewO; SetQrdersEditMode(true);
private void btnOrdersDelete_Click(object sender, System. EventArgs e) •i if (cmOrders. Count > 0) cmOrders.HemoveAt (cmOrders. Posit ion); else MessageBox. Show( "No Order to Deletel", "Delete Order", MessageBoxButtons.OK, MessageBoxIcon. Error);
498
Часть IV Создание эффективных приложений с использованием ADO.NET
Этап 6. Передача изменений в БД Теперь наше приложение позволяет редактировать, добавлять и удалять заказы. Но как вы, возможно, заметили, приложение не передает эти изменения в БД. Надеюсь, вы достаточно хорошо помните материал глав 5 и 10, чтобы понимать, почему это так. Связанные элементы управления изменяют содержимое DafaSet, однако у нас нет кода, вызывающего метод DataAdapter Update. Мы создали объект DataAdapter с помощью мастера Data Adapter Configuration Wizard, и поэтому определять логику обновления не требуется. За нас ее создал мастер. Все, что надо сделать нам. — добавить на форму кнопку Submit Changes и вызвать в процедуре обработки события Click этой кнопки метод DataAdapter.Update, чтобы передать изменения в БД. Код перехватывает возвращаемое значение метода Update объекта DataAdapter, указывающее число переданных в БД изменений. Я добавил пару строк кода, которые выводят число измененных записей о заказах, при ошибке обновления перехватывают генерируемое исключение и выводят диалоговое окно, если объект DataSet не содержит отложенных изменений, Суть этого кода — все тот же вызов метода DataAdapter,Update-, Visual Basic .NET If dsChapter13.HasChanges Then Try Dim intOrdersModified As Integer intOrdersModified = daOrders.Update(dsChapter13.Orders) Dim strOutput As String strOutput = "Modified " & intOrdersModified & " order(s)" MessageBox.Show(strOutput, "Update succeeded!", MessageBoxButtons,OK, MessageBoxIcon.Information) Catch ex As Exception MessageBox.Show(ex.Message, "Update failed!", HessageBoxButtons.OK, MessageBoxIcon.Error) End Try Else MessageBox.Show("No changes to submit!", "SubmitChanges", MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
Visual C# .NET if (dsChapter13.HasChanges()> i try
{
int intOrdersModified; intOrdersModified = daOrders.Update{dsChapter13. Orders); string strOutput; strOutput = "Modified " + intOrdersModified + " order(s)"; MessageBox.ShowfstrOutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon.Information);
ГЛАВА 13
Создание эффективных Windows-приложений
499
catch (Exception ex) 1 MessageBox.Show(ex. Message, "Update failed!", MessageBoxButtons . OK, HessageBoxIcon .Error) ;
else MessageBox.Stiow("No changes to submit!", "SubmitChanges", MessageBoxButtons. OK, MessageBoxIcon. Information);
Этап 7. Добавление кнопок Edit, Update и Cancel Созданное нами приложение — довольно простое: на форме лишь несколько элементов управления. Тем не менее работа с приложением не настолько интуитивно понятна, как может показаться. Запустите приложение и измените значение поля EmployeelD первого заказа. Щелкните кнопку Submit Changes. Откроется диалоговое окно с сообщением об отсутствии изменений для передачи в БД. Что же произошло? Согласно нашему коду, объект DataSel не содержит каких-либо изменений. Так что же случилось с только что внесенными вами изменением? Объект CurrencyManager по-прежнему хранит его и пока не записал в объект DataSet. Если перейти к следующему заказу и щелкнуть кнопку Submit Changes, изменение будет успешно передано в БД. Объект CurrencyManager не подтверждает отложенные изменения объекта DataSet, пока не реализуется переход к другой записи или пока не будет вызван метод CurrencyMariagerEndCurrentEdit. Такое поведение в чем-то аналогично функционированию метода DataRowBeginEdtt. Изменения действительно записываются в объект DataRow лишь после вызова метода EndEdit. Для передачи изменений стоит прямо перед вызовом метода DataAdapter,Update добавить вызов метода CurrencyManager EndCurrentEdit, Это вполне уместно в конкретной ситуации, Но я предпочитаю другой способ. На этапе 7 создания приложения я добавил кнопки Edit, Cancei и Update. При запуске приложения все данные в элементах управления TextBox доступны только для чтения. Чтобы изменить содержимое заказа, следует щелкнуть кнопку Edit. Лишь после этого лам удастся редактировать содержимое элементов управления TextBox (кроме элемента, связанного со столбцом OrderlD). Кнопки перемещения, а также кнопки Add, Edit, Delete и Submit Changes на период редактирования отключаются. Для продолжения работы следует щелкнуть кнопку Update или Cancel (рис. 13-5). При щелчке кнопки Update изменения текущего заказа подтверждаются. а при щелчке кнопки Cancel — отменяются. На добавление такой функциональности требуется лишь несколько минут, и они тратятся с умом. В результате работа приложения станет более интуитивно понятной. Исчезнут проблемы с передачей изменений, связанные с тем, что изменения не подтверждены. Кроме того, вам не придется напрягаться, чтобы понять, как и когда объект CurrencyManager подтверждает изменения содержимого DataSet.
Часть IV
500
Создание эффективных приложений с использованием ADO. NET
CudeilD Custom» tD EmotojeelD: Orcfei Dale.
Update
i
Cance
Рис. 13-5. Принудительное подтверждение или отмена изменений в составе текущего заказа Я добавил в код формы процедуру SetOrdersEcUtMode, которая принимает логическое значение, указывающее, может ли пользователь редактировать текущий заказ. Процедура в соответствии с принятым значением задает значения свойств Readonly элементов управления TextBox, а также значения свойств Enabled кнопок. Процедура вызывается в событии Click кнопок Add, Edit, Update и Cancel. Visual Basic .NET Private Sub SetOrdersEditMode(ByVal blnEdit As Boolean) txtCustomerlO. Readonly = Not blnEdit txtEmployeelD. Readonly = Not blnEdit txtOrderDate. Readonly = Not blnEdit btnOrdersHoveFirst. Enabled = Not blnEdit btnOrdersMovePrevious. Enabled = Not blnEdit btnQrdersMoveNext. Enabled = Not blnEdit btndrdersMoveLast. Enabled = Not blnEdit btnOrdersCancel. Enabled = blnEdit btnOrdersUpdate. Enabled = blnEdit btndrdersEdit. Enabled = Not blnEdit btnOrdersAdd.Enaoled = Not blnEdit btnOrdersDelete. Enabled = Not blnEdit btnSubmitChanges, Enabled = Not blnEdit End .Sub
Visual C# .NET private void SetOrdersEditMode(bool blnEdit) { txtCustoroerlD. Readonly = ! blnEdit; txtEmployeelD. Readonly = IblnEdit; txtOrderDate. Readonly = IblnEdit; btnOrdersMoveFirst. Enabled = IblnEdit;
ГЛАВА 13
Создание эффективных Windows-приложений
ЬОI
btnOrdersHovePrevious.Enabled = IblnEdit; btnOrdersMoveNext.Enabled = IblnEdit; btnOrdersMoveLast.Enabled = IblnEdit; btnOrdersCancel.Enabled = blnEdit; btnOrdersUpdate.Enabled = blnEdit; btnOrdersEdit.Enabled = IblnEdit; btnOrdersAdd.Enabled = IblnEdit; btnOrdersDelete.Enabled = IblnEdit; btnSubmitChanges.Enabled = IblnEdit;
Этап 8. Просмотр дочерних данных Теперь наше приложение позволяет просматривать и изменять данные таблицы Orders. Тем не менее такая функциональность не очень-то полезна, если не предоставить возможность изменять состав заказов. На рис. 13-6 показан пользовательский интерфейс следующей версии нашего приложения. Как видно, я добавил сетку, отображающую данные из таблицы Order Details. Когда вы переходите от одного заказа к другому, в сетке отображаются только связанные с этим заказом записи.
Рис, 13-6. Просмотр заказа и связанных с ним товаров Чтобы добавить данную функциональность в приложение: 1. добавьте объект DataAdapter, выбирающий все записи таблицы Order Deiails, соответствующие заказам клиента; 2. воссоздайте объект DataSet со строгим контролем типов, чтобы в будущем добавить в него объект DataTable с новыми данными, полученными на первом этапе. После этого добавьте в объект DataSet объект DataRelation, чтобы упростить поиск связанных с заказом товаров; 3. добавьте на форму элемент управления DataGrid и свяжите с объектом DataSet таким образом, чтобы в нем отображались лишь товары из состава текущего заказа; 4. добавьте в процедуру, обрабатывающую событие Click кнопки Submit Changes, логику для передачи в БД изменений из обоих объектов DataTable.
502
Часть IV Создание эффективных приложений с использованием ADO.NET
Получение только списка заказанных клиентом товаров Создать объект DataAdapter, получающий только заказы конкретного клиента, весьма просто, поскольку в таблице Orders определен столбец CustomerlD. Б таблице Order Details такого столбца нет. Таким образом, запрос, получающий только список заказанных клиентом товаров, должен одновременно ссылаться на таблицы Order Details и Orders. Есть несколько способов структурировать такой запрос. Бот три из них: SELECT D.OrderlD, D.ProductlD, D.UnitPrice, 0.Quantity FROM Orders 0 INNER JOIN [Order Details] D ON O.OrderlD = D.OrderlD WHERE 0.CustomerlD = ? SELECT OrderlD, ProductIO, UnitPrice, Quantity FROM [Order Details] WHERE OrderlD IN (SELECT OrderlD FROM Orders WHERE CustomerlD = ?) SELECT D.OrderlD, D.ProductlD, D.UnitPrice, D.Quantity FROM [Order Details] D, Orders 0 WHERE D.OrderlD = 0,OrderlD AND 0.CustomerlD = ?
Согласно SQL Query Analyzer, выводящему примерный план выполнения, SQL Server создает для каждого из этих запросов одинаковый план, По двум причинам я отдаю предпочтение синтаксису третьего запроса. Во-первых, он самый интуитивно понятный. Во-вторых, мастер Data Adapter Configuration Wizard не способен обработать такой синтаксис из-за наличия параметра в подзапросе. Похоже, что мастер использует преимущественно синтаксис INNER JOIN, И хотя при создании приложения я использовал синтаксис третьего из приведенных выше запросов, мастер преобразовал мой SQL-оператор согласно синтаксису первого запроса. Каждому — свое. Создав с помощью этого запроса новый объект OleDbDataAdapater, назовите его daDetails. Добавление объекта DataTable Order Details в класс DataSet со строгим контролем типов Добавив объект DataAdapter для выборки записей из таблицы Order Details, можно воспользоваться диалоговым окном Generate Dataset и добавить в класс DataSet со строгим контролем типов новый объект DataTable. Подробнее — на рис. 13-"?, Выберите из списка имеющихся объектов DataSet нужный. Убедитесь, что в списке таблиц в средней части диалогового окна помечена только одна таблица — та. которая соответствует новому объекту DataAdapter. Щелкните ОК, и Visual Studio .NET добавит новый объект DataTable в имеющийся класс DataSet со строгим контролем типов. Если оставить помеченными обе таблицы, Visual Studio .NET перезапишет объект DataTable Orders, имеющийся в объекте DataSet со строгим контролем типов. Это означает, что значения свойств AutolncrernentSeed \\AutoIncrementStep пропадут. Сбрасывать эти свойства нетрудно, но утомительно. Теперь, когда класс DataSet со строгим контролем типов содержит объекты DataTable, соответствующие таблицам Orders и Order Details, можно добавить объект
ГЛАВА 13 Создание эффективных Windows-приложений
503
DataRelation, определяющий между этими двумя объектами DataTable отношение на основе столбца OrderlD. В окне Solution Explorer дважды щелкните .xsd-файл класса, чтобы запустить конструктор XML Schema Designer. Перетащите столбец CustomerlD из объекта DataTable Orders в объект DataTable Order Details. Примите предлагаемые конструктором значения параметров по умолчанию.
Gen«ate a dataset that inekjdes ?he specified tables, Choose a dataset: * Enisling fchapteri 3. ifsdCbapterTs f" IJtew:
I,' гл'--''.
Cfioose which tablets) to add tothedataset:
Orders (daOrders)
'& fidd Ehhctataset to the designer.
Cancel
Help
Рис. 13-7. Добавление объекта DataTable Order Details в класс DataSet со строгим контролем типов Приложение также добавляет вычисляемое поле, отображающее общую стоимость заказанных единиц конкретного товара, Вы можете сделать то же самое, добавив в объект DataTable Order Details новый столбец. Задайте тип данных этого столбца как Decimal и задайте свойству Expression значение UnitPrice 'Quantity. Закройте конструктор и сохраните изменения.
Добавление элемента управления DataGrid, отображающего дочерние данные Связать элемент управления DataGrid с данными очень просто. Достаточно задать значения двух свойств — DataSource и DataMember, Добавьте на форму элемент управления DataGrid и задайте ему имя gridDetails. В окне Properties выберите свойство DataSource. Расткройте список справа, чтобы просмотреть перечень доступных источников данных. Вы увидите объект DataSet и отдельные объекты DataTable. Выберите свойство DataMember. Для него также имеется список возможных значений (рис. 13-8). Раскрыв в списке узел объекта DataTable Orders, вы увидите созданный вами ранее объект DataRelation. Если задать свойству DataMember значение DataRelation, элемент управления DataGrid отобразит только дочерние записи, используя объект DataRelation. Чтобы связать элемент управления DataGrid с данными в период выполнения, воспользуйтесь таким кодом:
504
Часть IV Создание эффективных приложений с использованием ADO.NET
Visual Basic .NET gridDetails.DataSource = dsChapter13 gridOetails.DataMemtier = "Orders. Order_x0020_Details' Visual C# .NET gridDetails.DataSource = dsChapter13; gridDetails. DataHember = "Orders. Orcter_x0020_Details' Properties
9. x >yst em. Windows, Forms, DataGi f\
cdumnHeaderiVisibli True •\ ContextMenu (none) _H0020_Petaib Order Details - |Tj Orders
: GndLineCalor GridLine5tyle HeaderBackColar S.Headeifont HeaderForeCobr ImeMode
^] Control 5oiid |_J Control Microsoft Sans 5er(f, 3 ^| ControlText No Control
Рис. 13-8. Задание значения свойства DataMember элемента управления DataGrid, отображающего дочерние записи В приложении, в конце процедуры, обрабатывающей событие Load формы, есть строка кода, вызывающая процедуру FormatDetailsGrid. Эта процедура добавляет в элемент управления DataGrid новый объект DataGrid'iableStyle, позволяющий управлять внешним видом элемента управления: выбирать отображаемые столбцы, а также задавать для каждого из них размер, формат и параметры выравнивания. Передача в БД изменений из обеих таблиц Глава 11 посвящена проблемам передачи в БД иерархических изменений. По существу, новые записи требуется передавать, начиная с верхней части иерархии (сначала записи о заказах, потом записи о составе этих заказов), а удаленные записи — начиная с нижней ее части (сначала записи о составе заказов, затем записи о заказах). Таким образом, нельзя взять и целиком передать вызываемому методу DataAdapter.Update объект DataTable. Для начала следует передать в БД информацию о новых и измененных заказах. После этого — все изменения таблицы Order Details. Затем — удаленные заказы. Изучив код процедуры, обрабатывающей событие Click кнопки Submit Changes, вы увидите, что он использует такую же логику. Я привожу этот код ниже, опустив для удобочитаемости блок Try/Catch.
ГЛАВА 13
Создание эффективных Windows-приложений
505
Visual Basic .NET Dim intOrdersModified, intDetallsModified As Integer Dim aRowsToUpdate As DataRow()
Dim dvrs As DataViewRowState 'Передаем новые или измененные заказы dvrs = DataViewRowState.Added Or DataViewRowState.ModifiedCurrent aRowsToUpdate = dsChapter13.Orders.SelectC"", "", dvrs) intOrdersModified = daOrders.Update(aRowsToUpdate) 'Передаем все изменения объекта DataTable, 'соответствующего таблице Order Details intDetailsModified = daDetails.Update(dsChapter13.0rder_Details) 'Передаем удаленные заказы dvrs = DataViewRowState.Deleted aRowsToUpdate = dsChapter13.Orders.Select("", "", dvrs) intOrdersModified += daOrders.Update(aRowsToUpdate) Dim strOutput As String strOutput = "Modified " & intOrdersModified & " order(s)" & vbCrLf & _ "Modified " & intOetailsHodified & " detail(s)" MessageBox.Show(strOutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon,Information)
Visual C# .NET int intOrdersHodified, intDetailsModified; DataRow[] aRowsToUpdate; DataViewRowState dvrs; //Передаем новые или измененные заказы dvrs = DataViewHowState.Added ] DataViewRowState.ModifiedCurrent; aRowsToUpdate = dsChapter13.Orders.SelectC'", "", dvrs); intOrdersModified = daOrders.Update(aRowsToUpdate); //Передаем все изменения объекта DataTable, //соответствующего таблице Order Details intDetailsModified = daDetails.Update(dsChapter13.0rder_Details); //Передаем удаленные заказы dvrs = DataViewRowState.Deleted; aHowsToUpdate = dsChapter13.Orders.SelectC'", "", dvrs); intOrdersModified += daOrders.Update(aRowsToUpdate); string strOutput; strOutput = "Modified " + intOrdersModified + " order(s)\n\r" + "Modified " + IntDetailsHodified + " detail(s)"; MessageBox.Show(strQutput, "Update succeeded!", MessageBoxButtons.OK, MessageBoxIcon.Information);
Этап 9. Связывание второй формы с тем же источником данных Связать несколько элементов управления на разных формах с одним источником данных сложно, но можно. Прежде чем подробно рассматривать процесс такого связывания, я отвлекусь и расскажу, как усовершенствовать интерфейс приложения, чтобы сделать редактирование данных еще более простым,
506
Часть IV Создание эффективных приложений с использованием ADO.NET
Элемент управления DataGrid — полезное и мощное средство. Я часто использую такие элементы управления для вывода содержимого нескольких записей. Тем не менее мне не очень-то нравится ситуация, когда пользователю предоставлена возможность редактировать данные непосредственно в DataGrid. Вы, вероятно, знаете, что изменения в элементе управления DataGrid удается отменить, нажав Escape или Ctrl+Z, однако редко кто из начинающих пользователей понимает, что это вообще возможно. Не надеясь, что пользователи обнаружат и запомнят этот способ, я предпочитаю сделать процесс редактирования более интуитивным, даже если он при этом и удлинится на пару этапов. Итак, на этапе 9 создания приложения я сделал объект DataGrid со сведениями о составе конкретного заказа доступным только для чтения. Чтобы изменить информацию о заказанных товарах, пользователь должен щелкнуть кнопку — то же требование, что и к редактированию информации о заказе. Если пользователю понадобится изменить информацию о заказанном товаре, приложение откроет модальную форму (рис. 13-9), позволяющую изменить нужную запись.
Рис. 13-9.
Редактирование информации о заказанном товаре на новой форме
Здесь можно редактировать запись о товаре и точно так же, как при изменении записи о заказе, для прекращения редактирования следует щелкнуть кнопку Update или Cancel. Теперь вернемся к связыванию нескольких элементов управления, расположенных на разных формах, с одним источником данных. Элементы управления Text Box новой формы связаны с той же записью данных, которая отображается в элементе управления DataGrid основной формы. И хотя связать таким образом элементы управления в период разработки нельзя, это допустимо в период выполнения, как показано ниже. Просмотрев код, выполняющийся по щелчку кнопки Edit, расположенной под элементом управления DataGrid, вы увидите, что он создает экземпляр формы с информацией о заказанных товарах и затем вызывает метод EditDetail этой формы. Данный метод принимает в качестве параметра объект Currency Manager. Код метода EditDetail показан далее. Как видно, он использует объект CurrencyManager, чтобы связать элементы управления TextBox на новой форме с той же записью данных. Свойство Current объекта CurrencyManager возвращает объект DataRowView. Элементы управления TextBox можно связать с объектом DataView, чтобы код на основе свойства DataView объекта DataRowView обращался к объекту DataView. с которым связан возвращенный объект DataRowView,
ГЛАВА 13
Создание эффективных Windows-приложений
Visual Basic .NET Dim drvDetail As DataRowView Dim vueDetail As DataView Public Sub EditDetail(ByVal cm As CurrencyManager) drvDetail = CType(cm.Current, DataRowView) vueDetail = drvDetail.DataView Me.BindingContext(vueDetail).Position = cm.Position txtOrderID,DataBindings.Add("Text", vueDetail, "OrderlD") txtProductID.DataBindings.Add("Text", vueDetail, "ProductID") txtUnitPrice.DataBindings.Add("Text", vueDetail, "UnitPrice") txtQuantlty.DataBindings.Add("Text", vueDetail, "Quantity") txtltemTotal.DataBindings.Add("Text", vueDetail, "ItemTotal") If He.ShowDialog = DialogHesult.OK Then cm.EndCurrentEditO Else cm.CancelCurrentEdit() End If End Sub
Visual C# .NET DataRowView drvDetail; DataView vueDetail; public void EditDetail(CurrencyManager cm) { drvDetail = (DataRowView) cm.Current; vueDetail = drvDetail.DataView; this.BindingContext[vueDetail].Position = cm,Position; txtOrderID.DataBindings.Add("Text", vueDetail, "OrderlD"); txtProductID.DataBindings.Add("Text", vueDetail, "ProductID"); txtUnitPrice.DataBindings.Add("Text", vueDetail, "UnitPrice"); txtQuantity.DataBindings.AddC'Text", vueOetail, "Quantity"); txtItemTotal.DataBindings.Add{"Text", vueDetail, "ItemTotal"); if (this.ShowDialogC) == DialogResult.OK) cm. EndCurrentEditO; else cm. CancelCur rentEditO;
507
508
Часть IV Создание эффективных приложений с использованием ADO.NET
Этап 10. Совершенствование пользовательского интерфейса Наше приложение позволяет просматривать и редактировать информацию о заказах клиента, однако стоит сделать кое-что еще, чтобы работать с ним стало еще удобнее. Если есть выбор, большинство пользователей предпочитает при работе с формой иметь дело с подробной информацией, а не с шифрованными ключевыми сведениями. Например, дочерняя форма, позволяющая редактировать запись о товаре, требует, чтобы пользователь знал значение ключа, а не название товара. Кроме того, не лучшим образом выглядит формат стоимости единицы и стоимости общего числа заказанных единиц товара. Посмотрим, как представить данные в более понятном формате (рис. 13-Ю).
Рис. 13-Ю.
Отображение данных в понятном формате
Добавление функции поиска с использованием элемента управления ComboBox На рис. 13-10 я заменил элемент управления TextBox, отображавший значение поля EmployeelD текущей записи о заказе, элементом управления ComboBox, отображающим имя принявшего заказ сотрудника. Добавить такую функциональность на самом деле очень просто — достаточно задать значения четырех свойств элемента управления ComboBox. Прежде чем добавить на форму элемент управления ComboBox и проверить предлагаемую функциональность в действии, следует добавить объект DataAdapter, возвращающий информацию из таблицы Employees. Я создал этот объект посредством следующего запроса: SELECT EmployeelD, LastName + FROM Employees
•*• FirstNarne AS EmployeeName
Добавив объект DataAdapter для выборки информации о сотрудниках, нужно создать экземпляр объекта DataSet со строгим контролем типов и добавить в него объект DataTable Employees. Нам нужно, чтобы элемент управления ComboBox отображал значения из столбца EmployeeName объекта DataTable Employees. Когда пользователь выбирает из
ГЛАВА 13
Создание эффективных Windows-приложений
509
списка имя сотрудника, Combo Box должен принять значение из столбца EmployeelD и поместить его в поле EmployeelD текущей записи о заказе. На самом деле мы свяжем элемент управления ComboBox одновременно с двумя разными источниками данных — с объектом DataTable Employees и с текущей записью о заказе. Примечание Приложение не будет изменять содержимое таблицы Employees, и поэтому создавать логику обновления для объекта DataAdapter, взаимодействующего с таблицей Employees, не требуется. Чтобы сообщить об отсутствии такой необходимости мастеру, достаточно щелкнуть в окне SQL Statement переключатель Advanced Options. Сначала давайте посмотрим, как связать элемент управления ComboBox с объектом DataTable Employees. У ComboBox есть свойство DataSource, значение которого задается так же, как и значение одноименного свойства элемента управления DataGrid. Выберите из списка доступных источников данных объект DataSet, Свойство DisplayMember определяет, значениями какого столбца ComboBox заполняет список, и принимает имя соответствующего столбца. В нашем случае это имя столбца EmployeeName объекта DataTable Employees. Затем задайте свойству ValueMember имя столбца EmployeelU объекта DataTable Employees. Все, что осталось сделать, — сопоставить элемент управления ComboBox с полем EmployeelD текущей записи о заказе. Это осуществляется аналогично связыванию свойства Text элемента управления TextBox со столбцом объекта DataSet. Найдите в окне Properties раздел (DataBindings), раскройте его. найдите свойство SelectedValue и свяжите это свойство со столбцом EmployeelD объекта DataTable Orders, В период выполнения осуществить все это позволяет следующий код: Visual Basic .NET cboEmployee.DataSource = dsChapter13 cboEmployee,DisplayMember = "Employees.EmployeeName" cboEmployee.ValueMember = "Employees.EmployeelD" cboEmployee,DataBindings.AddC'SelectedValue", dsChapter13, "Orders.EmployeelD")
Visual C# .NET cboEmployee.DataSource = dsChapter13; cboEmployee.DisplayMember = "Employees.EmployeeName"; cboEmployee.ValueHember = "Employees.EmployeelD"; cboEmployee. DataBindings. AddC'SelectedValue", dsChapteMS, "Orders.EmployeelD"); Теперь при перемещении по записям о заказах будет отображаться значение поля EmployeeName, а не поля EmployeelD. Кроме того, можно отредактировать запись о заказе и изменить значение поля EmployeelD, изменив имя сотрудника, отображаемое в элементе управления ComboBox, Управление форматом связанных данных Тип данных столбца UnitPrice объекта DataSet — Decimal. Как следствие, элемент управления TextBox на форме с информацией о заказанных товарах, связанный
Часть IV
510
Создание эффективных приложений с использованием ADO,NET
со столбцом UnitPrice, отображает содержимое этого столбца с использованием стандартного числового формата. Стоимость единицы товара, равная S4-50. будет отображаться в TextBox как 45Можно написать код, чтобы вручную изменить формат отображения данных на более подходящий. В одном из предыдущих разделов главы я приводил фрагмент кода, связывающий свойство Text элемента управления TextBox со столбцом объекта DataView. Visual Basic .NET txtOrderID.DataBindings.Add("Text", dsChapter13, "Orders.OrderlD") Visual C# .NET txtOrderID.DataBindings.Add("Text", dsChapter13, "Orders.OrderlD");
Метод Add возвращает объект Binding, который реагирует на события объекта CurrencyManager и перемещает данные между элементом управления TextBox и столбцом, с которым этот элемент связан, Объект Binding предоставляет два события — Format и Parse. Событие Format наступает, когда объект Binding загружает данные из источника в свойство, с которым связан. Событие Parse наступает, когда объект Binding считывает данные из связанного свойства и передает их в источник. С помощью этих двух событий удается управлять форматом отображения данных в связанных элементах управления TextBox. Следующий фрагмент кода приложения выводит значение поля UnitPrice в элементе управления TextBox с использованием формата, соответствующего типу данных currency. Изменение формата представления данных осуществляется с помощью метода метод ToString класса Decimal Visual Basic .NET Public Sub EditDetail(ByVal cm As CurrencyManager) Dim b As Binding b = txtUnitPrice.DataBlndings.Add("Text", vueDetail, "UnitPrice") AddHandler b.Format, AddressOf DecimalToCurrencyString AddHandler b.Parse, AddressOf CurrencyStringToDecimal End Sub
Private Sub DecimalToCurrencyString(ByVal sender As Object, ByVal cevent As ConvertEventArgs) If Not cevent.DesiredType Is GetType(String) Then Exit Sub End If If cevent.Value Is DBNull.Value Then cevent.Value = CDec(0).ToString("c") Else cevent.Value = CDec(cevent.Value).ToString("c")
ГЛАВА 13
Создание эффективных Windows-приложений
End If End Sub
Private Sub CurrencyStringToDecimal(ByVal sender As Object, ByVal cevent As ConvertEventArgs) If Not cevent. DesiredType Is GetType(Decimal) Then Exit Sub End If cevent. Value = Decimal. Parse(cevent. Value. ToString, Globalization. NumberStyles. Currency, Nothing) End Sub
Visual C# .NET public void EditDetail(CurrencyManager cm) Binding b; b = txtUnitPrice.DataBindings.Add( T Text", vueDetail, "UnitPrice"); b. Format += new ConvertEventHandler(DecimalToCurrencyString); b. Parse += new ConvertEventHandler(CurrencyStringToDecimal);
:
private void DecimalToCurrencyString(object sender, ConvertEventArgs cevent) { if { I cevent. DesiredType. Equals(typeof (string))) return; if (cevent. Value == DBNull. Value) cevent. Value = ((Decimal) 0).ToString("c"); else cevent. Value = ((Decimal) cevent. Value). ToStrlngC'c"); I
private void CurrencyStringToDecimal(object sender, ConvertEventArgs cevent)
;
if (! cevent. DesiredType. Equals(typeof( Decimal))) return; cevent. Value = Decimal. Parse(cevent. Value. ToStringO, System. Globalization. NumberStyles. Currency, null);
51 1
512
Часть IV
Создание эффективных приложений с использованием ADO.NET
Этап 11. Если хочешь сделать что-то хорошо... Давайте ненадолго прервемся и окинем взглядом созданное нами приложение. Благодаря функциям связывания с данными потребовалось очень мало кода, чтобы предоставить пользователям возможность просматривать и редактировать при помощи связанных элементов управления данные из двух объектов DataTable, между которыми определено отношение. В этом вся суть функций связывания с данными — предоставить базовую функциональность для создания пользовательского интерфейса с минимальным объемом кода. При первоначальном связывании элементов управления у нас были весьма ограниченные возможности для контроля их взаимодействия с данными объекта DataSet. На этапе 10 мы добавили код, изменивший формат представления данных в элементах управления TextBox. В папке прилагаемого компакт-диска, соответствующей этапу 10 разработки приложения, также записан код, позволяющий форматировать и принимать значения Null. Можно добавить дополнительный код, расширяющий возможности контроля связанных элементов управления, но помните: чем больше кода вы пишете, тем меньше выгоды от первоочередной ставки на связывание с данными. Приведу пример. Я закончил этап 10 и начал работать с приложением- примером, попутно обдумывая, как бы его усовершенствовать. Выяснилось, что на форме Edit Detail при изменении содержимого элементов управления TextBox, соответствующих столбцам Quantity и Price, не обновлялось содержимое элемента управления TextBox ItemTotal. В связи с этим я решил поискать способ, который позволил бы автоматически обновлять содержимое элемента управления TextBox ItemTotal при изменении содержимого элементов управления TextBox, соответствующих столбцам Quantity и Price. Я пробовал задавать значение свойства Text элемента управления TextBox ItemTotal в событии Leave элементов управления TextBox, соответствующих столбцам Quantity и Price. Пытался вызывать метод CurrencyManagerRefresh. Старался задействовать методы CurrencyManager.SuspendBinding и CurrencyManagerResumeBinding. Чего я только не делал, но так и не смог найти нужной функциональности. Полагаю, эту задачу удастся решить при помощи связывания с данными, но все же у связывания с данными другое предназначение. Чем больше функциональности мы добавляем в приложение посредством написанного нами кода, тем больше и больше мы ограничиваем круг обязанностей средств связывания с данными тремя простыми задачами — перемещением по имеющимся записям, выводом содержимого текущей записи в группе элементов управления и сохранением изменений содержимого текущей записи. Такой код можно написать и самостоятельно. Если для вывода данных в элементах управления и передачи изменений в структуру данных вы используете собственный код, вы полностью управляете и отвечаете за взаимодействие пользовательского интерфейса и структур данных. На этапе 11 данные в элементах управления TextBox выводятся уже без помощи связывания с данными. Просмотрев код формы Edit Orders, вы увидите процедуру SbowCurrentOrder, а также код, определяющий, какой заказ и названия каких товаров отображать с использованием объектов DataView. Кроме того, формы включают код для проверки данных, вводимых в различные элементы управления TextBox.
ГЛАВА 13 Создание эффективных Windows-приложений
513
Если пользователь указал недопустимое значение, например Четверг вместо цены товара, система выдаст сообщение соответствующего содержания.
Резюме: связывание с данными Как показывают различные этапы разработки приложения, средства связывания с данными, предоставляемые пакетом Windows Forms, позволяют создать мощный и надежный пользовательский интерфейс при минимуме кода. Тем не менее, пытаясь расширить возможности управления этим интерфейсом и добавляя в приложение собственный код, вы в некоторых случаях обнаруживаете, что не дополняете средства связывания с данными, а сражаетесь с ними. Б этом случае лучше написать собственный код, реализующий взаимодействие данных и пользовательского интерфейса.
Проблемы разработки приложений Создание полезного и интуитивно-понятного пользовательского интерфейса лишь один из аспектов разработки эффективного Windows-приложения. Давайте рассмотрим другие, не менее важные вопросы.
Выборка только необходимых данных При создании приложения важно учитывать потенциальное увеличение размеров БД. Б самом начале разработки запросы SELECT..FROM Таблица могут выполняться просто отлично, однако с ростом размера таблицы на выборку результатов запроса потребуется больше времени. Чем больше данных выбирается, чем больше времени потребуется. Рассмотрим созданное нами приложение. При запуске оно выполняет запросы для получения информации обо всех размещенных клиентом заказах. Правильно ли это? Иногда получение сведений обо всех заказах клиента — лишняя нагрузка на систему. Или пользователю интересны преимущественно заказы, еще не отправленные клиенту? А может, приложение должно выбирать только заказы, размещенные конкретным клиентом за последние три месяца? Один из факторов, определяющий, какие данные выбирать, — среда приложения. В некоторых случаях пользователю требуется загрузить данные на ноутбук по модему на 28 кбит/с, поработать с ними в автономном режиме, изменить их и затем по все тому же модему подключиться к БД и передать отложенные изменения. Обсуждаемый канал имеет весьма низкую пропускную способность, которую нет смысла расходовать попусту, однако в соответствии с требованиями среды приложения вам все равно придется загрузить на ноутбук из БД все необходимые данные.
Стратегии обновления Приложение-пример кэширует изменения и передает обновления с использованием оптимистического управления блокировками. Сейчас я расскажу о других стратегиях обновления
514
Часть IV
Создание эффективных приложений с использованием ADO.NET
Мгновенные и котированные обновления Решение о том, следует ли передавать изменения в БД мгновенно или кэшировать их и передавать их позже, зависит от потребностей вашего приложения. Когда пользователь изменяет запись о заказе, приложение-пример не передает это изменение в БД немедленно. Оно средствами ADO.NET кэширует это обновление и передает его лишь по щелчку кнопки Submit Change. Приложение легко модифицировать, чтобы изменение записи о заказе передавалось в БД по щелчку кнопки Update. Когда пользователь щелкнет кнопку Edit, приложение позволит изменить информацию о заказе и его составе. Если пользователь щелкнет кнопку Cancel, приложение отбросит сделанные изменения. После щелчка кнопки Update приложение, наоборот, сохранит изменения и затем с помощью объекта DataAdapter передаст их в БД. Одно из преимуществ автономной работы с данными и кэширования изменений — то, что приложению требуется гораздо реже взаимодействовать с БД. Тем не менее, чем позже пользователь передаст кэшированные изменения, тем больше вероятность, что другой пользователь уже успел изменить эти же данные в БД, и значит, попытка обновления не удастся. Взвесьте все «за* и «против» каждого способа и определите, что же все-таки нужно вашей программе. Если пользователи приложения-примера обрабатывают звонки клиентов, приобретающих непопулярный товар, кэшировать изменения стоит. Но, скажем, для системы бронирования авиабилетов, данный подход неприемлем. Думаю, вам совсем не нужно, чтобы пользователь пытался сохранить предполагаемый маршрут путешественника только для того, чтобы узнать, что последнее место на обратном рейсе было продано, пока путешественник вспоминал свой номер постоянного клиента. Повторная выборка перед разрешением изменений Данные в отсоединенной структуре, например DataSet, могут потерять актуальность. Однако в отличие от пакета молока, у объекта DataSet нет срока хранения. Когда соответствующие записи БД изменяются, не наступает каких-либо событий объекта DataSet. Вполне вероятно, что, когда пользователь вашего приложения изменит данные и попытается передать их в БД. они уже будут изменены в БД другим пользователем, поэтому попытка обновления не удастся. Рассмотрим наше приложение-пример. При запуске оно выбирает данные. Пользователь может щелкнуть кнопку Edit через какие-то секунды после запуска приложения. Чем дольше приложение работает, чем больше устаревают данные, Фактически перед изменением содержимого записи могут пройти минуты и даже часы. Есть вероятность, что, когда пользователь щелкнет кнопку Edit, соответствующая запись БД уже будет изменена другим пользователем. Если вы разрабатываете приложение, допускающее такие ситуации, при щелчке по кнопке Edit следует выбрать заново содержимое соответствующей записи из БД. Для повторной выборки содержимого записи создайте объект DataAdapter, выполняющий параметризованный запрос следующего вида: SELECT ,
. FROM Таблица WHERE ПолеКлюча = ?
ГЛАВА 13 Создание эффективных Windows-приложений
515
Если значение свойства PrimaryKey объекта DataTable определено, объект DataAdapter обновит содержимое объекта DataRow, используя данные из БД. Помните: этот запрос не сгенерирует исключение, если другой пользователь удалил в БД соответствующую запись. В этом случае он просто не вернет записей. Метод DataAdapterFill возвращает целое число, соответствующее количеству выбранных из БД записей. Если метод Fill вернул 0, записи в БД больше нет. Можно обработать эту ситуацию и изящно известить пользователя об отсутствии нужной записи. ADO.NET и пессимистическое управление блокировками Даже если изменения не кэшируются и сразу передаются в БД и, прежде чем предоставить пользователю возможность редактирования записи, вы повторно выбираете ее содержимое из БД, то и в этом случае при попытке обновления возможен отказ, поскольку на данные на сервере не наложена блокировка. Успех попытки обновления гарантирует лишь пессимистическое управление блокировками. Внимание! Пессимистическое управление блокировками — мощное и даже опасное средство. Бойтесь его. Сильно бойтесь. Оно показано разработчикам, по-настоящему понимающим эффект блокировки данных на сервере. Пессимистическое управление блокировками требуется лишь ограниченному кругу приложений, например системам бронирования авиабилетов. Не рекомендую вам использовать этот способ как универсальный способ, исключающий сбои при обновлении. Для большинства приложений лучше редкие сбои при оптимистическом обновлении, чем сбои при выполнении запросов из-за блокировки данных на сервере. Если при обновлении данных используется пессимистическое управление блокировками, перед редактированием содержимого на запись БД следует наложить блокировку'. Это гарантирует, что при обновлении не возникнут сбои из-за того, что запись успел изменить другой пользователь. Объектная модель ADO.NET предназначена для передачи обновлений с применением оптимистического управления блокировками. Как вы помните из глав ] 0 и 11, объект DataAdapter позволяет передавать в БД отложенные изменения из объекта DataSet. Когда вы изменяете содержимое объекта DataRow, DataSet не налагает блокировку на соответствующую запись БД. В ADO.NET, по крайней мере в первой версии этой модели, у объектов нет свойств, позволяющих организовать пессимистическое управление блокировкой. И все же задействовать такой тип управления блокировкой можно, воспользовавшись объектами Transaction. Тем не менее в транзакциях обычно выполняют нечто более сложное, чем простой запрос SELECT. Налагаются ли блокировки на данные при выполнении запроса SELECT в транзакции, зависит от БД, типа запроса и уровня изоляции транзакции. Уровень изоляции транзакции определяет, как работа, выполненная в одной транзакции, повлияет на работу, выполняемую в других транзакциях. Уроиень изоляции транзакций в SQL Server по умолчанию — Read Committed (чтение при подтвержденной транзакции). При этом на запись, измененную в транзакции, сразу же налагается блокировка. При простой выборке содержимого записи с помощью
516
Часть IV Создание эффективных приложений с использованием ADO.NET
запроса SELECT блокировка не налагается. Но если используется уровень изоляции Repeatable Read (повторяемое чтение) или Scrializable (серийный), то блокировка налагается и после выборки содержимого записи средствами запроса SELECT. Некоторые БД позволяют использовать в запросах указания по блокировке. Б SQL Server следующий запрос налагает на запись блокировку независимо от уровня изоляции транзакции: SELECT CustomerlD, CompanyName, ContactName, Phone FROM Customers WITH (UPDLOCK) WHERE CustomerlD = 'ALFKI'
Подробнее о поддерживаемых уровнях изоляции транзакций и указаниях по блокировке — в документации вашей СУБД. Следующий фрагмент кода при помощи объекта OleDbTransaction и указаний по блокировке, включенных в запрос SELECT, пессимистично налагает блокировку на запись БД MSDE. Когда код вернет результаты запроса, на сервере на запись налагается блокировка. Можно определить точку останова после вызова метода DataAdapterJFill и убедиться в наличии блокировки. На этом этапе вы сможете просмотреть содержимое записи при помощи средства выполнения произвольных запросов, например утилиты SQL Server Query Analyzer, но изменить это содержимое не удастся. Visual Basic .NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;Oata Source=(local)\NetSDK;" & _ "Initial Catalog=Northwind;Trusted_Connection=Yes;" strSQL = "SELECT CustomerID, CompanyName FROM Customers " & "WITH (UPDLOCK) WHERE CustomerlO = 'ALFKI'" ' Dim en As New OleDbConnection(strConn) en.Open() Dim txn As OleDbTransaction = cn.BeginTransaction Dim cmd As New 01eDbCommand(strSQL, en, txn) Dim da As New OleDbDataAdapter(cmd) Dim cb As New OleDbCommandBuilder(da) Dim tbl As New DataTableO da.Fill(tbl) Dim row As DataRow = tbl.Rows(O) row("CompanyName") = "Modified" da.Update(tbl) txn.RollbackQ cn.CloseO
Visual C# .NET string strConn, strSQL; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; StrSQL = "SELECT CustomerlD, CompanyName FROM Customers " + "WITH (UPDLOCK) WHERE CustomerlD = 'ALFKI'"; OleDbConnection en = new QleDbConnection(strConn); cn.0pen();
ГЛАВА 13 Создание эффективных Windows-приложений
517
OleDbTransaction txn = cri.BeginTransactionO; OleDbCommand cmd = new OleDbCommandCstrSQL, en, txn); OleDbDataAdapter da = new OleDbDataAdapter(cmd); OleDbCommandBuilder cb = new OleDbCotnmandBuilder(da); DataTable tbl = new DataTableO; da.Flll(tbl); DataRow row = tbl.Rows[0]; row["CompanyName"] = "Modified"; da.Update(tbl); txn.Rollback(); cn.Close{); Примечание В данном фрагменте кода объект CommandButtder используется исключительно для краткости. Подробнее о преимуществах создания собственной логики обновления — в главе 11.
Стратегии подключения Стратегий подключения к БД две, ваш выбор зависит от параметров приложения. Подсоединение и отсоединение Простейший способ подключения к БД — позволить объектам DataAdapter неявно открывать соединение. В созданном нами приложении используется именно он. При вызовах методов DataAdapter fill и DataAdapter.Update объект DataAdapter неявно открывает соединение, а по завершении яызовов — закрывает его, Хотя данный способ прост, он не всегда является самым лучшим. В зависимости от времени реакции сети и БД на открытие соединения с БД иногда требуется довольно много времени. Производительность приложения можно повысить, открывая соединение с БД при его запуске и поддерживая это соединение до завершения работы приложения. Однако у этого способа также есть недостатки. Он годится для небольшого числа пользователей, но когда параллельных пользователей много, он вряд ли приемлем. Кроме того, он применим лишь в двухуровневых приложениях, напрямую подключающихся к БД. Если ваше приложение взаимодействует с БД при помощи компонентов промежуточного уровня, его выбирать не стоит. Оптимальное решение — гибрид двух этих методов. При необходимости приложение явно открывает соединение с БД. Это напоминает способ, при котором объекты DataAdapter управляют состоянием соединения. Приложение при запуске выполняет такой код:
Visual Basic .NET daProducts.Fill(dsChapter13.Products) daEmployees.Fill(dsChapter13.Employees) daOrders.SelectCommand.Parameters(O).Value = strCustomerlD daOrders.Fill(dsChapteM3. Orders) daDetails.SelectCommand.Parameters(O). Value = str Customer-ID daDetails.Fill(dsCnapter13.0rder_Details)
518
Часть IV
Создание эффективных приложений с использованием ADO.NET
Visual C# .NET daProducts.Fill(dsChapter13.Products); daEmployees.Fill(dsChapter13.Employees); daOrders.SelectCommand.Parameters[0].Value = strCustomerlD; daOrders.Fill(dsChapter13.Orders); daDetails.SelectCommand.Parameters[Q].Value = strCustomerlD; daDetails.Fill(dsChapter13.0rder_Details) При каждом вызове метода DataAdapterfill неявно открывается и закрывается связанный с объектом DataAdapter объект Connection. Это означает, что код четырежды открывает и закрывает объект Connection. Вызвав метод СоппесПоп.Ореп перед вызовом методов DataAdapterfill, вы чуть-чуть повысите производительность приложения. Кроме того, явно открыв объект Connection до вызова методов объектов DataAdapter, вы сможете объединить изменения, ожидающие передачи в БД, в одну транзакцию. Если бы мне пришлось рекомендовать единый, универсальный способ управления состоянием соединения, я порекомендовал бы способ, описанный выше.
Пул соединений Пул соединений позволяет значительно повысить производительность многоуровневых приложений. Фактически, поскольку пул соединений по умолчанию включен, вы, возможно, уже используете его, даже не имея об этом представления. Повторно задействованы только соединения с одинаковыми строками подключения и реквизитами пользователей, и поэтому пул соединений в многоуровневом приложении применяется только тогда, когда компоненты промежуточного уровня используют идентичную строку подключения и реквизиты пользователя. Некоторые разработчики обеспечивают безопасность в многоуровневых приложениях средствами БД. Компоненты промежуточного уровня подключаются к БД с применением реквизитов пользователя, поэтому приложения, в которых реализован такой подход, не получают от пула соединений никаких преимуществ. Чтобы в полной мере воспользоваться преимуществами пула соединений, компоненты промежуточного уровня должны предоставлять собственные специфичные реквизиты. С помощью параметров сетевой безопасности ограничьте круг лиц, обладающих доступом к компонентам промежуточного уровня. Пул соединений предназначен преимущественно для многоуровневых приложений, но он годится для повышения производительности и простых двухуровневых приложений. Когда наше приложение-пример явно или неявно закрывает объект Connection, реальное соединение с БД помещается в пул. Если приложение, до того как истечет время хранения соединения в пуле, снова откроет объект Connection, соединение будет использовано повторно. Чтобы при работе с поставщиком OLE DB .NET Data Provider не применять в своем приложении пул соединений, добавьте в строк)' подключения такой атрибут: OLE OB Services=-4;
Если требуется исключить обращение к пулу соединений при работе с поставщиком SQL Client .NET Data Provider, добавьте следующий атрибут: Pooling=False;
ГЛАВА 13 Создание эффективных Windows-приложений
519
Работа с данными больших двоичных объектов Для повышения производительности можно хранить данные больших двоичных объектов (BLOB-данные) в файлах на сервере, а пути к этим файлам — в БД. Операционные системы лучше применять для работы с файлами. Хранить те же данные в БД менее эффективно. Например. SQL Server 2000 делит BLOB-данные объемом свыше 8 кбайт на несколько страниц БД. Таким образом, файл размером 40 кбайт делится на пять составных частей. Мне не очень-то нравится хранить BLOB-данные в БД, но я отчетливо представляю преимущества такого способа. При хранении одной части данных в БД и другой части в файлах увеличивается число используемых технологий. Усложняется обеспечение безопасности и архивация данных. Если вы решите хранить BLOB-данные в БД, в следующих разделах приводится несколько советов по работе с такими данными в ADO.NET. Отложенная выборка BLOB-данных Предположим, ваш запрос возвращает сотню записей, включающих поля с BLOBданными. Действительно ли следует возвращать все эти данные в наборе результатов запроса? В SQL Server поля с BLOB-данными могут содержать до 2 Гбайт информации. Представляете ли вы, сколько BLOB-данных вернет ваш запрос? Один из способов повысить производительность приложения — отложить выборку BLOB-данных из БД до момента, когда они понадобятся. Выбирайте обычные данные заблаговременно, а затем по мере необходимости получайте BLOBданные. Этот способ особенно полезен, если пользователь обращается только к BLOB-данным отображаемой в настоящий момент записи,
Обработка BLOB-данных с помощью объектов DataSet На самом деле обращаться и редактировать содержимое столбцов с BLOB-данными при помощи объектов DataSet очень просто. ADO.NET хранит текстовые BLOBданные в виде строк, а двоичные — в виде байтовых массивов. В отличие от предыдущих моделей доступа к данным объект DataRow не предоставляет методов Get-Chunk \\nnAppendChunk. Вам потребуется получать и редактировать все содержимое поля. Работают с содержимым поля с текстовыми BLOB-данными так же, как и с содержимым других полей с текстовыми типами данных. Visual Basic .NET Dim row As DataRow Dim strBlob As String 'Обращение к содержимому поля с текстовыми BLQB-данными strBlob = CStr(row("TextBlob")) 'Редактирование содержимого поля с текстовыми BLOB-данными rowC'TextBlob") = strBlob
520
Часть IV Создание эффективных приложений с использованием ADO.NET
Visual C# .NET DataRow row; string strBlob; //Обращение к содержимому поля с текстовыми BLOB-данными strBlob = (string) row["TextBlob"]; //Редактирование содержимого поля с текстовыми BLOB-данными row["TextBlob"] = strBlob; К полям с двоичными BLOB-данными обращаются точно также, как и к полям, содержащим двоичные данные меньшего объема. Visual Basic .NET Dim row As DataRow Dim aBinaryBlob As ByteO 'Обращение к содержимому поля с двоичными BLOB-данными aBinaryBlob = CType(row("BinaryBlob"), ByteO) 'Редактирование содержимого поля с двоичными BLOB-данными row("BinaryBlob") - aBinaryBlob
Visual C# .NET DataRow row; Byte[] aBinaryBlob; //Обращение к содержимому поля с двоичными BLOB-данными aBinaryBlob = (Byte[]) row["BinaryBlob"]; //Редактирование содержимого поля с двоичными BLQB-данными row["BinaryBlob"] = aBinaryBlob; Обработка BLOB-данных с помощью объектов DataReader Объект DataReader предоставляет альтернативу: обращаться сразу ко всему содержимому поля с ВЮВ-данными или выбирать это содержимое по фрагментам. Следующий фрагмент кода получает содержимое поля с BLOB-данными, один раз обратившись к объекту DataReader. Visual Basic .NET Dim Dim Dim Dim Dim
end As OleDbCommand rdr As OleDbDataReader intTextBlobColumnNo, intBinaryBlobColumnNo As Integer strTextBlob As String aBinaryBlob As ByteO
rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess) Do While rdr.Read StrTextBlob = rdr.GetStringfintTextBlobColuinnNo} aBinaryBlob = CType(rdr(intBinaryBlobColumnNo), ByteO) Loop rdr,Close
ГЛАВА 13 Создание эффективных Windows-приложений
521
Visual C# .NET OleDbCommand cmd; OleDbDataReader rdr; int IntTextBlobColumnNo, intBinaryBlobColumnNo; string strTextBlob; Byte[] aBinaryBlob; rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess); while (rdr.Readf)) { strTextBlob = rdr.GetString(intTextBlobColumnNo); aBinaryBlob = (Byte[]) rdr[intBinaryBlobColumnNo]; I
rdr.CloseO Примечание Этот фрагмент кода получает содержимое поля с текстовыми ВЮВ-данными, используя метод GetString со строгим контролем типов. Дня получения же двоичных ВЮВ-данных код неявно обращается к свойству Пет без контроля типов и затем преобразует возвращенное значение в байтовый массив. У объекта DataReader есть метод GetBytes, однако он возвращает данные фрагментами, а не единым куском. Размер ВЮВ-данных может быть очень большим. Хранить все содержимое поля с ВЮВ-данными в виде строки или байтового массива — не самая лучшая идея, особенно если поле содержит больше пары сотен мегабайт данных. Оптимальное решение для таких ситуаций — постепенно выбирать фрагменты ВЮВ-данных, записать содержимое поля на диск и затем обращаться к нему, когда это потребуется. Объект DataReader предоставляет два метода, GetBytes и GetChars, позволяющих выбирать двоичные данные фрагментами. Следующий фрагмент кода с помощью метода GetBytes выбирает из объекта DataReader двоичные ВЮВ-данные фрагментами по 8 кбайт и записывает их в файл. Используя эту же логику, вы получите текстовые ВЮВ-данные с помощью метода GetCbars. Visual Basic .NET 'Добавьте в начало модуля кода следующую строку Imports System.10 Dim cmd As OleDbCommand Dim rdr As OleDbDataReader Dim intBlobColumnNo As Integer = 1 Dim intChunkSize As Integer = 8192 Dim intOffset As Integer = 0 Dim intBytesReturned As Integer Dim aBinaryBlob(intCnunkSize) As Byte Dim strPathToFile As String = "C:\GetBytes.jP9" Dim filOutput As New FileStreamfstrPathToFile, FileMode.Create) rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess)
522
Часть IV Создание эффективных приложений с использованием ADO.NET
rdr.Read() Do
intBytesReturned = CInt(rdr.GetBytes(intBlobColumnNo, intOffset, _ aBinaryBlob, 0, intChunkSize)) If (intBytesReturned > 0) Then filOutput.Write(aBinaryBlob, 0, intBytesReturned) End If intOffset += intBytesReturned Loop Until intBytesfleturned о intChunkSize filOutput.Close() rdr.CloseO
Visual C# .NET //Добавьте в начало модули кода следующую строку using System.10; QleubCommand cmd; OleDbDataReader rdr; int intBinaryBlobCol = 1; int intChunkSize = 8192; int intOffset = 0; int intBytesReturned; Byte[] aBinaryBlob = new Byte[intChunkSize]; string strPathToFile = "C:\XGetBytes.jpg"; FileStream filOutput = new FileStream(strPathToFile, FlleMode.Create); rdr = cmd.ExecuteReader(CommandBehavior.SequentialAccess); rdr.ReadC);
do {
intBytesReturned = (int) rdr.GetBytes(intBinaryBlobCol, intOffset, aBinaryBlob, 0, intChunkSize); if (intBytesReturned > 0) filOutput.Write(aBinaryBlob, 0, intBytesReturned); intOffset += intBytesReturned; 1 while (intBytesReturned == intChunkSize); filOutput. CloseO; rdr.CloseO; Двоичные BLOB-данные в БД Northwind Вы, вероятно, заметили, что БД Northwind включает столбцы с BLOB-данными. Так, в таблице Employees есть столбец Photo, содержащий фотографию сотрудника. К сожалению, столбец Photo также содержит некоторые дополнительные данные, представляющие собой OLE-заголовок Access. Этот заголовок позволяет Access определить, данные какого типа содержит ВШВ-поле — .jpg-файл, документ Word, электронную таблицу Excel и т.д. Как следствие, если вы попытаетесь выбрать содержимое столбца Photo средствами ADO.NET (или ADO, RDO и др.), то не сможете загрузить эти данные в элемент управления PictureBox или просмотреть содержимое файла в программе для работы с изображениями, например Paint.
ГЛАВА 13
Создание эффективных Windows-приложений
523
Как же отбросить OLE-заголовок Access и оставить только нужные вам данные. Если вкратце, никак Формат OLE-заголовка Access — оригинальная разработка Microsoft, к которой нет документации. Тем не менее на прилагаемом к книге компакт-диске записано приложение LoadEmployeePhotos, позволяющее заменить содержимое по умолчанию столбца Photo таблицы Employee рисунками в формате .jpg. В папке приложения вы найдете .jpg-файлы с изображениями сотрудников. Приложение загружает 1 эти файлы в БД Northwind, выполняя ряд параметризованных запросов. Кроме того, данное приложение можно рассматривать как пример загрузки содержимого файлов в БД средствами параметризованных запросов, Пример приложения для работы с BLOB-данными Теперь, когда в таблице Employees есть реальные изображения, мы вкратце рассмотрим пример приложения, возвращающего и выводящего эти изображении на Windows-форме. Приложение ShowEmpfoyeesPhotos (рис. 13-11), записанное на прилагаемом к книге компакт-диске, возвращает сведения о сотруднике из БД Northwind в объект DataSet.
Рис. 13-11.
Вывод двоичных BLOB-данных в элементе управления PictureBox
В целях быстрого создания приложения я воспользовался объектом CurrencyManager и связанными элементами управления для контроля текущего номера и общего числа записей, а также для упрощения вывода сведений о сотрудниках. Используемый в приложении объект DataSet включает два отдельных объекта DataTable: один для BLOB-данных и один — для прочих данных. Дочерний объект DataTable также включает поле первичного ключа (EmployeelD), упрощающее переход от записи родительского объекта DataTable к соответствующей записи дочернего объекта DataTable. Кроме того, я добавил в родительский объект DataTable столбец FetchedPhoto, позволяющий определить, выбрана ли из БД фотография конкретного сотрудника. При запуске приложение получает из БД стандартные сведения о сотрудниках (значения полей EmployeelD, LastName, FirstName и т.д.). Затем, когда пользова-
524
Часть IV
Создание эффективных приложений с использованием ADO.NET
тель впервые обратится к записи о конкретном сотруднике, приложение считывает двоичные ВЮВ-данные — содержимое поля Photo, (фотографии имеют небольшой размер — всего 22 кбайт — и поэтому загрузка изображений при запуске приложения не оказала бы резко отрицательного влияния на производительность. Это особенно верно, если размер таблицы, из которой выбираются данные, невелик). Такой способ значительно повышает производительность приложения, выводящего на экран лишь часть возвращаемых записей.
Пользовательские интерфейсы, созданные с применением тяжелой артиллерии ADO.NET Вы видели, как встроенные функции связывания с данными Windows-форм упрощают и ускоряют создание пользовательских интерфейсов. Вы также знаете, что написанный собственноручно код предоставляет более широкие, по сравнению со связанными элементами управления, возможности управления пользовательским интерфейсом. Кроме того, вы познакомились с преимуществами и недостатками различных стратегий подключения к БД, выполнения запросов к БД, передачи обновлений и работы с BLOB-данными.
Вопросы, которые стоит задавать почаще Вопрос. Следует ли использовать связывание с данными приложении, которое я собираюсь распространять? Ответ. Я применяю связывание с данными при создании пользовательского интерфейса приложения, чтобы сделать это быстро и с минимальным объемом кода. Как только структура пользовательского интерфейса и схема данных, требуемых приложению, меня устраивает, я решаю, потребуется ли мне связывание с данными в дальнейшем. Если мне нужны более широкие возможности управления, чем те, которые предоставляет связывание, или я решил, что связывание не поможет сэкономить время на разработку приложения, я заменяю стандартные функции связывания с данными собственным кодом, Вернувшись к описанию этапа 11 разработки приложения-примера, вы увидите, что содержимое заказа выводится в связанном элементе управления DataGrid. Я отключил функции обновления, предоставляемые DataGrid, считая, что на полную замену функциональности DataGrid потребуется много времени, которое можно было бы потратить с большей пользой. Вопрос. Можно ли связать элементы управления с объектом DataSet без контроля типов? Ответ. Безусловно. Элементы управления в период разработки разрешается связывать с объектами DataSet без контроля типов, схемы которых также определяются в период разработки при помощи окон свойств. Кроме того, используя приводившийся в одном из предыдущих разделов код, элементы управления можно в период выполнения связывать как с объектами DataSet без контроля типов, так и с объектами DataSet со строгим контролем типов.
ГЛАВА 13
Создание эффективных Windows-приложений
525
Visual Basic .NET 'Связывание элемента управления TextBox с объектом DataColumn TextBox.DataBindings.Add{"Text", DataSet, "TableName.ColumnName") 'Связывание элемента управления DataGrid с объектом DataTable DataGrid.DataSource = DataSet DataGrid.DataMember = "TableName"
Visual C# .NET //Связывание элемента управления TextBox с объектом DataColumn TextBox.DataBindings.Add("Text", DataSet, "TableName.ColumnName"}; //Связывание элемента управления DataGrid с объектом DataTable DataGrid.DataSource = DataSet; DataGrid.DataMember = "TableName"; Вопрос. При запуске приложения мне нужно загружать данные. Ваши рекомендации? Ответ. Убедитесь, что эти данные действительно вам необходимы. Очевидно, что на возврат меньшего числа записей и/или столбцов потребуется меньше времени. Еще один вариант —- воспользоваться имеющейся в .NET Framework поддержкой многопоточности и загрузить данные при запуске приложения в другом потоке. Подробнее — в разделе документации .NET Framework SDK. посвященном пространству имен System-Threading. .NET Framework упрощает работу с потоками, особенно пользователям Visual Basic, однако тема управления потоками лежит вне круга вопросов, освещаемых в данной книге. Вопрос. Почему в коде на этапе 11 разработки приложения-примера положение текущего заказа определяется с помощью объекта DalaView, а не DataTahle't Ответ. Если пометить объект DataKow как удаленный, он по-прежнему будет находиться в наборе Rows объекта DataTable. Приложение позволяет помечать заказы на удаление, и если бы в нем использовался только объект DataTable, потребовалось бы на порядок больше кода, чтобы при перемещении по оставшимся заказам пропускать заказы, помеченные как удаленные. Вместо этого приложение применяет объект DataView. Если не изменять значение свойства Roii'StateFilter объекта DataView по умолчанию, через этот объект окажутся недоступными записи, помеченные как удаленные, что упрощает процесс перемещения по оставшимся заказам. Объект CurrencyManager функционирует аналогичным образом. Вопрос. Как задействовать пессимистичное управление блокировками в многоуровневом приложении, использующем промежуточный уровень без поддержки сведений о состоянии?
526
Часть IV
Создание эффективных приложений с использованием ADO.NET
Ответ. Я уже говорил, что пессимистическое управление блокировками на самом деле требуется лишь ограниченному кругу приложений. Определенно, пессимистическое управление блокировками в многоуровневом приложении, промежуточный уровень которого не поддерживает сведений о состоянии — трудная задача. Такая функциональность необходима системам бронирования авиабилетов. Пользователь бронирует место на борту самолета, и система блокирует соответствующую запись данных, чтобы никому больше не удалось заказать это место. Давайте вкратце остановимся на архитектуре. Поскольку приложение обращается к БД через промежуточный уровень без хранения сведений о состоянии, оно должно поддерживать блокировки, заданные пользователями, даже при отсутствии живых соединений. Кстати, я не знаю ни одной СУБД, которая удерживала бы заданную пользователем блокировку, пока пользователь не подключится снова, если он отсоединился. Тем не менее для реализации этой функциональности можно разработать собственную схему блокировки. Если честно, мне не приходилось развертывать многоуровневые приложения, основанные на такой архитектуре. Но если бы мое существование зависело от разработки подобных приложений, обращающихся к СУБД SQL Server, я бы поступил следующим образом. 1. Настроил параметры БД таким образом, чтобы пользователи могли изменять содержимое таблиц только посредством вызовов хранимых процедур. 2. Добавил в таблицу два столбца: один с уникальным ключом блокировки, а другой — с датой и временем успешного наложения блокировки пользователем, 3. Создал хранимую процедуру, позволяющую налагать блокировку на ряд данных. Хранимая процедура принимает в качестве параметров ввода первичный ключ ряда, а также GUID. Если на ряде нет блокировки, процедура помечает его как заблокированный. Вот пример такой процедуры: CREATE PROCEDURE spPessimisticLockAcquirelock (@ID int, @LockID uniqueidentifier) AS UPDATE tblPessimisticLock SET LockAcquired = GetDateO, LockID = §LockID WHERE ID = «ID AND LockAcquired IS NULL
4. Создал хранимую процедуру, позволяющую редактировать содержимое ряда. Эта процедура принимает параметры с полями первичного ключа ряда, новыми данными ряда и с ключом блокировки. Процедура обновляет ряд, только если переданный ключ блокировки соответствует ключу, имеющемуся в БД. Успешно обновив ряд, процедура снимает с него блокировку. CREATE PROCEDURE spPessimisticLockUpdateRow (@ID int, @DescCol varchar(32), @LockiD uniqueidentifier) AS UPDATE tblPessimisticLock SET DescCol = $DescCol WHERE 10 = OID AND LockID = @LockID IF @@ROWCOUNT = 1 BEGIN
ГЛАВА 13
Создание эффективных Windows-приложений
527
SET NOCOUNT ON UPDATE tblPessimisticLock SET LockAcquired = NULL, LockID = NULL WHERE ID = SID AND LockID = «LockID END
5. Создал хранимую процедуру, позволяющую снять с ряда заданную блокировку. CREATE PROCEDURE spPessimistlcLockReleaseLock
(@ID int, @LockID uniqueidentifier) AS UPDATE tblPessimisticLock SET LockAcquired = NULL, LockID = NULL WHERE ID = 810 AND LockID = @LockID 6. Создал задание, снимающее все неснятые вовремя блокировки. Следующий запрос ищет ряды с блокировкой, удерживаемой более 5 минут, и снимает ее: UPDATE tblPessimist!сLock SET LockAcquired = NULL, LockID = NULL WHERE DateAdcKmi, 5, LockAcquired) wel !РИ
helghT-.27ps;w(dth:419px;7-TNDEX: 101; aaek. Your lasi visit was: 3/22/2002 1:59:19
|
Рис. 14-1.
Свойство ViewState страницы
ГЛАВА 14 Создание эффективных Web-приложений
541
На рис. 14-1 показан исходный код страницы, которая с помощью показаного фрагмента кода отслеживает в свойстве ViewState время и дату последнего посещения страницы пользователем. Внимательно изучив HTML-код страницы, вы таидите, что свойство ViewState хранится в скрытом поле. Поскольку независимо от конфигурации все браузеры поддерживают скрытые поля, свойство ViewState годится для хранения данных в различных ситуациях, когда задействовать файлы cookie проблематично. Преимущества
Как и в случае с файлами cookie, хранение данных с использованием свойства ViewState страницы позволяет ASP.NET-коду не поддерживать сведений о состоянии и повышает масштабируемость этого кода. Поскольку свойство ViewState реализовано в виде скрытого поля, получать и записывать в него данные можно независимо от параметров безопасности браузера. Кроме того, в отличие от файлов cookie, свойство ViewState позволяет работать с гораздо большим объемом данных. Недостатки
Данные, записываемые в свойство ViewState страницы, передаются браузеру, и чем больше данных записывается, тем больше времени уходит на загрузку страницы и передачу данных серверу. Также, несмотря на то, что значение свойства ViewState хешировано, существует вероятность того, что пользователю удастся расшифровать его. Таким образом, свойство ViewState не обеспечивает приемлемого уровня безопасности. Свойство ViewState не рассчитано на хранение любых данных, а только тех, которые ASP.NET может сериализовать. Разрешается записывать значения с простым типом данных, например строки и целые числа, а вот значения с универсальным типом данных Object записать нельзя, поскольку ASP.NET не умеет сохранять и создавать экземпляры таких объектов. В свойство ViewState можно записывать классы, поддерживающие интерфейс ISerializable, например объекты DataSet и DataTable.
Хранение сведений о состоянии на стороне Web-сервера ASP.NET также предоставляет различные параметры хранения сведений о состоянии на стороне Web-сервера. Свойство Session У класса Page есть свойство Session, возвращающее экземпляр класса HttpSessionState. Как и в случае со свойством ViewState. в свойстве Session страницы разрешается хранить данные. Однако они хранятся на стороне сервера, а не передаются браузеру вместе с HTML-кодом страницы. ASP.NET поддерживает параметры, хранящиеся в свойстве Session, до завершения пользовательского сеанса. Кроме того, эти параметры хранятся отдельно от аналогичных параметров других сеансов. Таким образом, информация в свойстве Session страницы уникальна для конкретного сеанса.
542
Часть IV
Создание эффективных приложений с использованием ADO.NET
Преимущества
Поскольку хранение данных осуществляется Web-сервером, а не браузером, гарантируется защита этих данных. Пользователю не удастся средствами браузера просмотреть или изменить содержимое объекта Session. Кроме того, поскольку данные хранятся на сервере, объект Session можно использовать независимо от параметров клиента. Недостатки
Хранение сведений о состоянии с использованием свойства Session снижает масштабируемость кода на ASP.NET, поскольку при этом для каждого сеанса приложения требуются определенные ресурсы. Скажем, пользователь ищет товар в каталоге, и вы сохраняете результаты этого поиска в свойстве Session, чтобы пользователь мог перемещаться по ним, не выполняя при открытии новой страницы повторно один и тот же запрос. Прикиньте, сколько памяти потребуется для хранения результатов запроса. 5 кбайт? 50? 500? Теперь умножьте этот объем на число клиентов, использующих данную возможность вашего Web-приложения. J 0 клиентов? 100? 1000? Еще больше? Оцените общий объем ресурсов сервера, который понадобится вам при хранении сведений о состоянии с использованием свойства Session. Объект Application Объект Application аналогичен объекте Session за исключением того, что данные, доступные через свойство Application страницы, совместно используются всеми сеансами. Таким образом, данные объекта Application доступны всем клиентам. Преимущества
Как и объект Session, объект Application обеспечивает высокий уровень защиты хранимых в нем данных. Это связано с тем, что данные находятся на сервере и объект Application можно использовать независимо от параметров браузера. Поскольку данные в объект Application глобальны для всех сеансов приложения, этот объект идеально подходит для хранения неизменяемых данных, задействованных во всех сеансах, например списка категорий товаров. Недостатки
Данные в объекте Application используют ресурсы сервера, и увеличение их объема может отрицательно сказаться на производительности вашего приложения. Объект Cache Объект Page предоставляет свойство Cache, возвращающее экземпляр объекта Cache. Объект Cache похож на объект Application с повышенной надежностью. Данные объекта Cache доступны всем сеансам приложения. Просматривают и изменяют содержимое объекта Cache, как и содержимое объекта Application, однако когда вы добавляете в объект Cache элемент данных с помощью метода Add или Insert, он предоставляет следующую функциональность: • разрешается указать время удаления элемента из кэша, задав конкретное (DateTime) или относительное (TimeSpan) значение;
ГЛАВА 14 Создание эффективных Web-приложений
543
• можно задать значение свойства CacbeDependancy, чтобы при изменении зависимого элемента добавленный вами элемент данных удалялся из кэша. Или же определить значение свойства CacbeDependancy, чтобы элемент удалялся из кэша при редактировании содержимого конкретного XML-файла; • допустимо указать функцию обратного вызова, к которой обратится ASP.NET при удалении элемента из кэша; • объект Cache обладает теми же преимуществами и недостатками, что и объект Application, за исключением того, что предоставляет дополнительные возможности удаления данных из кэша. Кэширование вывода Представьте, что в левой части всех страниц вашего Web-приложения отображается список разделов узла. Например, список категорий товаров или просто список страниц. Предположим, что данные для построения этого списка хранятся гдето в БД и редко изменяются. Эти данные можно выбрать в объекте DataSet и поместить его в объект Application. Таким образом, ASP.NET-коду не придется каждый раз выбирать эти данные при выводе новой страницы. Вероятно, это значительно снизит объем сетевого трафика, однако при выводе новой страницы вы будете по-прежнему преобразовывать содержимое DataSet в HTML-код. ASP.NET предлагает изящное решение данной проблемы: кэширование вывода. Можно кэшировлть вывод, представляющий страницу или ее часть. Или же полностью или частично кэшировать вывод с кодом страницы на основе параметров. Если вам нужно кэшировать HTML-данные с кодом странице, не премините воспользоваться этой мощной функцией ASP.NET. Преимущества Если необходимо многократно генерировать один и тот же HTML-код на основе содержимого объекта DataSet, кэширование вывода окажется очень эффективным, поскольку требует меньше ресурсов сервера. Как и в случае с объектом Cache, при кэшировании вывода можно управлять сроком хранения данных. Недостатки
Как и все серверные функции кэширования, кэширование вывода требует ресурсов сервера.
Хранение сведений о состоянии в БД Никто не говорит, что обязательно хранить сведения о состоянии на Web-сервере. Есть специальные серверы БД, предназначенные для хранения и работы с данными. В БД допустимо хранить глобальные и уникальные для сеансов данные вашего Web-приложения, например содержимое корзины отдельных пользователей. Представьте, что вашему приложению нужно выполнить настолько сложный или длительный по времени запрос, что вы предпочли бы сохранить результаты этого запроса в отдельной таблице, а не выполнять его каждый раз, когда пользователь обращается к следующей странице результатов. Например, пользователь может выполнить в БД поиск приводов CD-RW со скоростью записи не менее 2 Ох,
544
Часть IV Создание эффективных приложений с использованием ADO.NET
которые поддерживают интерфейсы USB и FireWire и продаются в магазинах, расположенных не далее, чем в 30 милях от дома пользователя. Вот образец такого запроса: SELECT ProductID, ProductName, Description, UnitPrice, ... FROM Products WHERE UnitPrice < 200 AND Description LIKE 'ШВГ AND Description LIKE 'XFireWire*' AND ...
Представьте, что запрос возвращает 50 записей, на каждой странице отображается 10 товаров. Можно выполнять данный запрос каждый раз, когда пользователь открывает новую страницу результатов, а можно поместить результаты этого запроса в одну из таблиц БД. Воспользовавшись запросом INSERT INTO и идентификатором сеанса пользователя, чтобы отслеживать принадлежность результатов к определенному сеансу, следует создать вторую таблицу, где будут храниться результаты запросов к первой таблице: INSERT INTO ProductsQueryCache SELECT ? AS SessionID, ProductID, ProductName, Description, UnitPrice, ... FROM Products WHERE UnitPrice < 200 AND Description LIKE 'MSB*' AND Description LIKE 'XFireWireX 1 AND ...
Затем надо получать записи для следующей страницы из таблицы результатов, а не выполнять повторно оригинальный запрос к основной таблице каталога. Данный конкретный пример, вероятно, и не позволяет в полной мере оценить предлагаемую технику, но пригодится при работе с запросами, которые вам не хотелось бы выполнять многократно. Примечание Решив воспользоваться описанной техникой, не забудьте удалить кэшированные записи из БД в событии End объекта Session или когда это потребуется.
Преимущества Хранение информации в БД позволяет ASP.NET-коду не поддерживать сведений о состоянии. Данные, хранящиеся в БД, надежны. Если Web-сервер по какой-то причине откажет, после его восстановления данные из БД снова станут доступны. Данные из БД имеют высокую степень защиты, поскольку доступны пользователю только через ASP.NET-код. БД предназначены для работы с большими наборами результатов. Если вам действительно требуется поддерживать большие наборы результатов между обращениями к страницам, лучше всего хранить эти результаты в БД.
Недостатки Хранить сведения о состоянии в БД сложнее, чем просто хранить данные в простых объектах и наборах типа Session, Application, Cache, ViewState или в файле cookie,
ГЛАВА 14 Создание эффективных Web-приложений
545
Рекомендации по хранению сведений о состоянии То, как и где вы храните сведения о состоянии, может значительно влиять на производительность, масштабируемость и безопасность ваших Web-приложений. Основополагающих принципов, определяющих, необходимо ли Web-приложению хранить сведения о состоянии, и если да, то как и где. нет. Тем не менее стоит воспользоваться общими рекомендациями, продиктованными здравым смыслом. Хранение данных в объектах ViewState Применяйте объекты ViewState, когда имеете дело с небольшими наборами результатов и допускаете просмотр этих результатов пользователем. Если же необходимо обеспечить защиту данных и исключить возможность изменения информации пользователем, храните данные в объекте Session или в БД. Помните: при обращении к ним данные из объекта ViewSession каждый раз передаются от клиента к серверу и обратно. Чем больше размер содержимого объекта ViewSession, тем больше времени уйдет на передачу его по сети. Хранение данных в объекте Application В объекте Application рекомендуется хранить небольшой объем данных, глобальных для всех сеансов вашего приложения. Помните: чем больше данных содержит объект Application, тем сильнее снижается производительность. Хранение данных в объекте Session Объект Session рассчитан на хранение небольшого объема данных, критичных для конкретного сеанса, а также предназначен для случаев, когда хранение данных в объекте ViewState неприемлемо.по соображениям безопасности. Помните: чем больше данных в объекте Session, тем сильнее и в несколько раз быстрее по сравнению с объектом Application падает производительность. Казалось бы, всего 100 кбайт данных в объекте Session. Но умножьте это значение на число сеансов, поддерживаемых ASP.NET в конкретный момент времени! Рекомендую вам хранить большой объем данных, уникальных для отдельного сеанса, в БД, Хранение данных в БД Храните большой объем данных, уникальных для отдельного сеанса, в БД, Понятно, что обращаться к таким данным, находящимся в памяти, быстрее, чем получать их из БД. Но если вам требуется хранить большие объемы данных, специфичных для отдельных сеансов, разместив эти данные в БД, вы уменьшите объем памяти сервера, используемый вашим приложением в данный конкретный момент времени.
Кэширование вывода Если вам требуется многократно генерировать одинаковый HTML-код, используйте средства кэширования вывода ASP.NET, а не кэшируйте данные, необходимые для генерирования этого HTML-кода.
546
Часть IV Создание эффективных приложений с использованием ADO.NET
Постраничная разбивка информации Компаний, каталог товаров которых может уместиться на одной Web-странице. немного. Предположим, вы создали систему поиска в своем каталоге. Критериям поиска, которые ввел пользователь, удовлетворяет сотня товаров. Вероятно, вместо того чтобы помещать ссылки на все эти товары на одной странице, вы захотите разбить набор результатов на несколько страниц и вывести первую страницу, Обычно вывод первой страницы не представляет каких-либо трудностей. Но как реализовать функциональность для перехода к следующей или какой-то конкретной странице набора результатов? ASP.NET и ADO.NET предоставляют средства постраничного представления результатов запросов, которые мы сейчас и рассмотрим.
Средства постраничного представления информации, предоставляемые Web-элементом управления DataGrid Web-элемент управления DataGrid включает средства, значительно упрощающие постраничный вывод результатов запроса. Свойства AllowPaging и PagerStyle этого элемента позволяют программно управлять тем, где и как будут выводиться данные, с которыми связан DataGrid. На рис. 14-2 показана Web-страница, созданная с использованием связанного с данными элемента управления DataGrid и его средств постраничного представления информации.
Рис. 14-2. Web-страница, использующая средства постраничного представления информации, предоставляемые элементом управления DataGrid Разработчикам, создающим Web-приложения при помощи Visual Studio .NET, проще всего задать эти свойства средствами диалогового окна Property Builder элемента управления DataGrid. Чтобы запустить его, щелкните на Web-форме эле-
ГЛАВА 14
Создание эффективных Web-приложений
547
мент управления DataGrid правой кнопкой и выберите в контекстном меню команду Property Builder. Затем щелкните в правой панели открывшегося окна ссылку Paging. Откроется диалоговое окно, аналогичное показанному на рис. 14-3.
Рис. 14-3- Задание параметров постраничного представления информации для элемента управления DataGrid при помощи диалогового окна Property Builder
Свойство AltowPaging Задавая свойству Allou'Paging значение True, вы указываете элементу управления DataGrid создать ссылки для перемещения между страницами набора результатов, Если значение свойства AllowPagmg — True, сразу после связывания с источником данных элемент управления DataGrid автоматически создает вместе со страницами результатов ссылки для перемещения между этими страницами. Можно указать нужный вид ссылок — кнопки Next (предыдущая страница) и Previous (следующая страница) или номера, позволяющие перейти к конкретным страницам. Когда пользователь переходит к новой странице, наступает событие PagelndexChanged элемента управления DataGrid. Оно позволяет определить, к какой именно странице результатов перешел пользователь, и задать соответствующее значение свойству CurrentPagelndex DataGrid. Свойства AHowCustomPaging и VirtualltemCount Допустим, вы знаете, как получить содержимое только нужной страницы, и хотите при помощи средств постраничного представления информации DataGrid создать ссылки на другие страницы результатов. Задав свойству AllowCustomPaging элемента управления DataGrid значение True, можно задать свойству VirtualttemCount DataGrid значение, представляющее общее число записей в наборе результатов. При этом DataGrid создает ссылки на страницы, основываясь на значении свойства VirtualltemCount, а не на числе записей в источнике данных. Следующий фрагмент кода помещает в объект DataReader только первые 10 записей набора результатов и задает соответствующим свойствам элемента уп-
548
Часть IV
Создание эффективных приложений с использованием ADO.NET
равления DataGrid нужные значения для создания ссылок на другие страницы набора результатов. Visual Basic .NET gridResults.Allow/Paging = True gridResults.AllowCustomPaging = True gridResults.CurrentPagelndex = 0 gridResults.PageSize = 10 gridResults.PagerStyle.Mode = PagerMode.NumericPages gridResults.PagerStyle.Position = PagerPosition.TopAndBottom Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & _ "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim cnNorthwind As New OleDbConnection(strConn) StrSQL = "SELECT COUNT(CustomerlD) FROM Customers" Dim cmdFetchRowCount As New 01eDbCommand(strSQL, cnNorthwind) strSQL = "SELECT TOP 10 CustomerlD, CompanyName, ContactName, Country " & _ "FROM Customers" Dim cmdFetchOnePage As New 01eDbCommand(strSQL, cnNorthwind) cnNorthwind.Open() grldResults.VirtualltemCount = cmdFetchRowCount.ExecuteScalarQ Dim rdrOnePage As OleDbDataReader = cmdFetchOnePage.ExecuteReader() gridResults.DataSource = rdrOnePage gridResults.DataBind() rdrOnePage.CloseO cnNorthwtnd.CloseO
Visual C# .NET gridResults.AllowPaging = true; gridResults.AllowCustomPaging = true; gridResults.CurrentPagelndex = 0; gridResults.PageSize = 10; gridResults.PagerStyle.Mode = PagerMode.NumericPages; gridResults.PagerStyle.Position = PagerPosition.TopAndBottom; string strConn, strSQL; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection cnNorthwind = new OleDbConnection(strConn); strSQL = "SELECT COUNT(CustomerlD) FROM Customers"; OleDbCommand cmdFetchRowCount = new 01eDbCommand(strSQL, cnNorthwind); strSQL = "SELECT TOP 10 CustomerlD, CompanyName, ContactName, Country " + "FROM Customers"; OleDbCommand cmdFetchOnePage = new 01eDbCommand{strSQL, cnNorthwind); cnNorthwind.OpenO; gridResults.VirtualltemCount = Convert.ToInt32(cmdFetchRowCount.ExecuteScalarQ);
ГЛАВА 14 Создание эффективных Web-приложений
549
OleDbOataReader rdrOnePage = cmdFetchOnePage.ExecuteReader();
gridfiesults.DataSource = rdrOnePage; gridResults.OataBindO; rdrOnePage. CloseO; cnNorthwind.CloseO; Использованный в данном примере запрос очень прост — он с помощью раздела ТОР выбирает только первые 10 записей, возвращаемых запросом. Такой синтаксис удобен, когда требуется получить лишь первую страницу данных. Но как же получить содержимое других страниц набора результатов?
Средства постраничного представления информации, предоставляемые методом Fill объекта DataAdapter Как вы помните из главы 5, метод Fill объекта DataAdapter перегружен и одна из его сигнатур позволяет получить часть набора результатов, возвращаемого объектом DataAdapter. Второй параметр этой сигнатуры определяет число записей, пропускаемых перед началом выборки данных, а третий параметр — максимальное число выбираемых записей. Скажем, вам требуется выводить 10 записей на странице. Тогда для вывода записей пятой страницы нужно пропустить первые 40 записей и выбрать следующие 10. Приведенный ниже фрагмент кода помещает в объект DataSet только записи пятой страницы:
Visual Basic .NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & "Initial Catalog=Northwtnd;Trusted_Connection=Yes;" strSQL = "SELECT CustomerlD, CompanyName, ContactName, Country " & _ "FROM Customers" Dim da As New 01eDbDataAdapter(strSQL, strConn) Dim ds As New DataSetO da.Fill(ds, 40, 10, "Customers")
Visual C# .NET string strConn, strSQL; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" * "Initial Catalog=Northwlnd;Trusted_Connection=Yes;"; strSQL = "SELECT CustomerlD, CompanyName, ContactName, Country " + "FROM Customers"; OleDbDataAdapter da = new 01eDbDataAdapter(strSQL, strConn}; DataSet ds = new DataSetO; da.Fill(ds, 40, 10, "Customers");
Этот фрагмент очень прост, но сам подход к решению проблемы имеет значительный недостаток. В этом примере БД все равно возвращает данные всех записей таблицы. По-прежнему имеется объективное падение производительности, связанное с выборкой 40 записей, которые объект DataAdapter просто отбрасывает и не помещает в объект DataSet.
550
Часть IV
Создание эффективных приложений с использованием ADO.NET
Создание запросов, возвращающих страницу данных БД SQL Server и Access позволяют использовать в запросах раздел ТОР, с помощью которого удается возвращать только первые п записей. Так, следующий запрос возвращает первые 10 записей таблицы Customers, упорядочивая их по столбцам Country и CustomerlD: SELECT TOP 10 CustomerlD, CompanyName, ContactName, Country FROM Customers ORDER BY Country, CustomerlD
Если необходимо разбить результаты запроса на страницы, стоит воспользоваться этим синтаксисом и вернуть только записи для конкретной страницы. Например, для вывода пятой страницы результатов нужно получить 41—50 записи таблицы Customers. SELECT TOP 10 CustomerlD, CompanyName, ContactName, Country FROM Customers WHERE CustomerlD NOT IN (SELECT TOP 40 CustomerlD FROM Customers ORDER BY Country, CustomerlD) ORDER BY Country, CustomerlD
Здесь используется подзапрос, выбирающий первые 40 записей и упорядочивающий их по столбцам Country и CustomerlD. Затем родительский запрос выбирает первые 10 записей, не входящие в набор результатов подзапроса, и опять же упорядочивает их по столбцам Country и CustomerlD. Вот более универсальный синтаксис таких запросов: SELECT TOP РаэмерСтраницы Поле1, Поле2,
. . . FROM Таблица
WHERE ПолеКлюча NOT IN (SELECT TOP ЧислоПропускаеныхЗаписей ПолеКлюча FROM Таблица ORDER BY ПорядокСортировки) ORDER BY ПорядокСортировки
И БД SQL Server, и Access поддерживают разделы ТОР и NOT IN. Тем не менее указанные разделы поддерживаются далеко не всеми БД, и следовательно, их нельзя считать универсальным решением. Например, БД Oracle не поддерживают раздел ТОР. БД Oracle предоставляют функцию, аналогичную по возможностям разделу ТОР — rownum. Oracle нумерует возвращаемые запросом записи, поэтому с помощью функции rownum вы получите только первые п записей набора результатов. Тем не менее Oracle генерирует номера записей до сортировки результатов запроса, а значит, чтобы вернуть с помощью функции rownum часть записей запроса, в котором определен порядок сортировки, нужно прибегнуть к маленькой хитрости. Немного обмана и пара подзапросов — и с помощью функции rownum вы вернете нужную часть записей запроса, использующего порядок сортировки, Так, следующий Oracle-запрос возвращает 41 — 50 записи таблицы Customers: SELECT CustomerlD, CompanyName, ContactName, Country FROM (SELECT CustomerlD, CompanyName, ContactName, Country, rownum AS Row_Num FROM (SELECT CustomerlD, CompanyName, ContactName, Country FROM Customers ORDER BY Country, CustomerlD)
ГЛАВА 14
Создание эффективных Web-приложений
551
WHERE rownum 40
Примечание Я не считаю себя знатоком СУБД Oracle. Возможно, есть более простой способ создавать такие запросы для БД Oracle.
Приложение PagingOptions На прилагаемом к книге компакт-диске записано Web-приложение PagingOptions. В нем применяются различные средства, позволяющие пользователю постранично перемещаться по результатам запроса. В каждом случае данные и ссылки на страницы выводятся средствами элемента управления DataGrid. На одной из страниц постраничная разбивка данных осуществляется средствами DataGrid. Другие страницы кэшируют данные в объекты ViewState, Session, а также в БД. Есть страница, получающая с помощью подзапросов нужные данные из БД, страница, записывающая в БД результаты оригинального запроса, а также страница, получающая эти записанные результаты.
Редактирование данных на Web-странице Мы обсудили простой способ вывода данных на Web-странице. А как создать Webстраницу, позволяющую редактировать представленные на ней данные? Редактировать данные на Web-странице гораздо сложнее, чем на Windowsформе. Помните: элементы управления Web-страницы, связанные с данными, генерируют HTML-код на основе источника данных. Если вы выводите данные при помощи связанного элемента управления TextBox и пользователь изменяет его содержимое, это не отразится автоматически на содержимом источника данных. Для создания Web-страницы, допускающей редактирование данных, нужно вывести данные, предоставить пользователю возможность изменить их и затем заставить ASP.NET-код отреагировать на эти изменения и предпринять соответствующие действия. Природа Web-приложений, исключающая хранение сведений о состоянии, еще больше усложняет создание Web-страниц, позволяющих пользователю редактировать данные. Скажем, вы выводите содержимое записи данных с помощью нескольких элементов управления TextBox. Как, после того как пользователь изменит содержимое записи и щелкнет кнопку Update, найти нужную запись и внести в нее соответствующие изменения? Кэшировали ли вы при помощи объекта DataSel где-нибудь данные, скажем, в объекте ViewState или Session? Требуется ли вам обратиться к БД, чтобы повторно выбрать нужную запись данных? Как определить, изменились ли данные с момента первого обращения к ним пользователя?
Упрощение редактирования данных при помощи элемента управления DataGrid Не будем рвать с места в карьер. Для начала посмотрим, как с помощью элемента управления DataGrid реализовать на Web-странице функции, позволяющие выби-
552
Часть IV Создание эффективных приложений с использованием ADO.NET
рать и редактировать данные. Наша цель — создать Web-страницу, аналогичную показанной на рис. 14-4. 2 SbapptiiBCanlfiDaletwse
Mfctoieft КИнпнй Isplnrar
a/floealhostphoppineC jrt/stcro&itinuiubsH. во»
Kumbtr of Items. 5 Total Cost af Order: $82 00
| Submit Onler ] CoirtnU» Shopp.f
Laughing ' Lumberjack
Рис. 14-4.
Пример Web-страницы, позволяющей редактировать данные
Рис. 14-5- Задание параметров редактирования данных в диалоговом окне Property Builder элемента управления DataGrid Точно так же, как DataGrid упрощает создание пользовательского интерфейса для постраничного представления информации, этот элемент управления упрощает разработку' пользовательского интерфейса, позволяющего выбирать и редактировать записи. Щелкните элемент управления DataGrid в среде Visual Studio .NET правой кнопкой и выберите в контекстном меню команду Property Builder. Откро-
ГЛАВА 14 Создание эффективных Web-приложений
553
ется диалоговое окно (рис. 14-5), в котором можно отобрать нужные столбцы данных и указать, имеет ли пользователь право редактировать их содержимое. Кроме того, здесь разрешается добавить кнопки для редактирования, обновления и удаления записей.
Обработка событий, связанных с редактированием содержимого DataGrid Как бы то ни было, задание свойств элемента управления DataGrid при помощи диалогового окна Property Builder в действительности не предоставляет пользователю возможности редактировать данные, с которыми связан DataGrid. У элемента управления DataGrid на рис. 14-5 есть столбец Edit, Update, Cancel и столбец Delete. Если связать этот элемент управления с источником данных, Webстраница выглядит аналогично показанной на рис, 14-6,
Total Cost of Order Ш 00
SubmitOKfer
Conlinue Shopping
Рис. 14-6. Web-страница, использующая средства редактирования данных, предоставляемые элементом управления DataGrid Предположим, пользователь щелкает кнопку Edit первого товара в корзине. DataGrid не позволяет пользователю редактировать содержимое записи напрямую. Вместо этого страница обращается к серверу, где наступает событие EditCommand элемента управления DataGrid. В это событие вам следует вручную добавить код, который задавал бы свойству Editltemlndex (порядковый номер редактируемого элемента) DataGrid соответствующее значение. Затем, когда вы вызовете метод DataBind элемента управления DataGrid, DataGrid добавит в HTML-таблицу элементы управления TextBox, которые позволят пользователю изменить содержимое записи. Кроме того, DataGrid добавит для редактируемой записи кнопки Update и Cancel.
19-5958
554
Часть IV Создание эффективных приложений с использованием ADO.NET
Передача изменений в БД После того как пользователь внесет нужные изменения и щелкнет кнопку 1 Jpdate, наступит событие UpdateCommand элемента управления DataGrid. Его аргументы позволяют определить, какую запись изменил пользователь, а также получить ее текущее содержимое. Тем не менее добавить логику для передачи измененного содержимого записи в БД вам придется вручную.
Внесение изменений в объект DataSet Чтобы внести изменения в объект DataSet найдите с помощью аргументов события UpdateCommand соответствующий объект DataRow, внесите изменения и затем передайте их с помощью объекта DataAdapter. Следующий фрагмент кода средствами аргументов события UpdateCommand получает название и новое количество товара, сведения о котором в корзине изменил пользователь. Затем код передает новые данные в БД с помощью объекта DataAdapter.
Visual Basic .NET Private Sub gridCart_UpdateCommand(ByVal source As Object, _ ByVal e As DataGridCommandEventArgs) Dim daCart As New OleDbDataAdapterQ Dim tblCart As New DataTableQ Dim vueCart As New DataView(tblCart) vueCart.Sort = "ProductName" Dim strNewQuantity As String strNewQuantity = CType(e.Item.Cells(2).Controls(0), TextBox).Text Dim intlndexToEdit As Integer intlndexToEdit = vueCart.Find(e.Item. Cells(1). Text) vueCart(intIndexToEdit)("QuantLty") = Clnt(strNewQuantity) daCart.Update(tblCart) End Sub
Visual C# .NET private void gridCart_UpdateCominand{ object source, DataGridCommandEventArgs e) I OleDbDataAdapter daCart = new OleDbDataAdapterf); DataTable tblCart = new DataTableO; DataView vueCart = new DataView(tblCart); vueCart.Sort = "ProductName"; string strNewQuantity; strNewQuantity = ((TextBox) e.Item.Cells[2].Controls[0]).Text; int intlndexToEdit; intlndexToEdit = vueCart.Find(e.Item.Cells(1).Text); vueCart[irvtIndexToEdit][ "Quantity"] = Convert,Tolnt32{strNewQuantity); daCart.Update(tblCart); I
ГЛАВА 14
Создание эффективных Web-приложений
555
Создание собственных запросов UPDATE Показанный в предыдущем разделе фрагмент кода с помощью аргументов события UpdateCommand получает название и новое количество товара, сведения о котором Б корзине изменил пользователь. Вместо того чтобы менять содержимое DataSet на основе этих данных и передавать коррективы в БД с помощью объекта DataAdapter, стоит воспользоваться этой же информацией и создать собственный запрос UPDATE, например: UPDATE ShoppingCarts SET Quantity =
WHERE ProductName = AND ShoppingCartID =
Приложение ShoppingCart Ня прилагаемом к книге компакт-диске записано Web-приложение под названием ShoppingCart, позволяющее перемещаться по каталогу БД Northwind и добавлять товары из него в корзину. Кроме того, пользователь может изменять содержимое корзины, добавляя товары или изменяя их количество. Приложение также позволяет разместить заказ и помещает его в таблицу Order Details БД Northwind. Web-приложение включает две дополнительных Web-страницы, StoreCartlnDatabase и StoreCartlnViewState, предоставляющих одинаковую функциональность. Единственное отличие этих страниц в том, где они хранят содержимое корзины пользователя. Как следует из названий, страница StoreCartlnDatabase хранит содержимое корзины пользователя в БД, а страница StoreCartlnViewState — в объекте ViewState. При создании системы приема заказов через Интернет я бы стал хранить содержимое корзин в БД, но мне подумалось, что полезно показать здесь и решение на основе объекта ViewState.
Вопросы, которые стоит задавать почаще Вопрос. Как, прежде чем я свяжу объект DataReader с элементом управления, определить, вернул ли запрос данные? Ответ. Это — один из распространенных вопросов Web-разработчиков, не имеющий, к сожалению, простого решения. Если с помощью метода DataReader Read определить, вернул ли запрос записи, и затем связать с объектом DataReader элементы управления, первая запись набора результатов запроса в них не отображается. Если вы просто связываете объект DataReader с элементом упраапения DataGrid, с помощью свойства Count набора Items DataGrid удастся определить, сколько записей вернул запрос. Тем не менее иногда нужно узнать число возвращенных записей до того, как связать элементы управления с объектом DataReader. Предположим, вы средствами следующего запроса выбираете заказы конкретного клиента: SELECT OrderlD, Customer-ID, OrderDate FROM Orders WHERE CustomerlD = ?
При работе с БД, поддерживающей пакетные запросы, можно выполнить пакетный запрос, который сначала вернет число записей, удовлетворяющий критерию поиска, а затем вернет сами эти записи, например:
556
Часть IV
Создание эффективных приложений с использованием ADO.NET
SELECT COUNT(OrderlD) FROM Orders WHERE CustomerlD = ?; SELECT Order-ID, CustomerlD, QrderDate FROM Orders WHERE CustomerlD = ?
Visual Basic .NET Dim strConn, strSQL As String strConn = "Provider=SQLOLEDB;Data Source=(local)\NetSDK;" & "Initial Catalog=Northwind;Trusted_Connection=Yes;" Dim en As New OleDbConnection(strConn) strSQL = "SELECT COUNT(OrderlD) FROM Orders WHERE CustomerlD = ? ; " & _ "SELECT OrderlD, CustomerlD, OrderDate FROM Orders " & _ "WHERE CustomerlD = ?" Dim cmd As New 01eDbCommand(strSQL, en) cmd.Parameters.Add("@CustomerID", OleDbType.WChar, 5) cmd.Parameters.Add("@CustomerID2", OleDbType.WChar, 5) cmd.Parameters("@CustomerID").Value = "ALFKI" cmd.Parameters("@CustornerID2").Value = "ALFKI" cn.OpenO Dim rdr As OleDbDatafteader = cmd.ExecuteReaderO rdr.ReadO If rdr(O) > 0 Tnen 'Запрос вернул записи rdr.NextResultQ gridOrders.DataSource = rdr gridOrders.DataBindO Else 'Запрос не вернул записей End If rdr.CloseO cn.CloseC)
Visual C# .NET string strConn, strSQL; strConn = "Provider=SQLOLEDB;Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection=Yes;"; OleDbConnection en = new OleDbConnection(strConn); StrSQL = "SELECT COUNT(OrderlD) FROM Orders WHERE CustomerlD = ?;" + "SELECT OrderlD, CustomerlD, OrderDate FROM Orders " + "WHERE CustomerlD = ?"; OleDbCommand cmd = new 01eDbCommand(strSQL, en); cmd.Parameters,Add("@CustomerID", QleDbType.WChar, 5); cmd.Parameters.Add("@CustomerID2", OleDbType.WChar, 5); cmd.Parameters["®CustomerID"].Value = "ALFKI"; cmd.Parameters["@CustomerID2"].Value = "ALFKI"; cn.OpenO; OleDbDataReader rdr = cmd.ExecuteReaderO; rdr.ReadO; if (Convert.Tolnt32(rdr[0]) > 0)
ГЛАВА 14 Создание эффективных Web-приложений
557
//Запрос вернул записи rdr.NextResult(); gridOrders.DataSource = rdr; gridOrders.Datafiind();
> else < //Запрос не вернул записей } rdr. CloseC); cn.CloseO;
При работе с БД, не поддерживающей пакетные запросы, следует воспользоваться таким же способом, но выполнять не пакет, а отдельные запросы, Вопрос. Как использовать в Web-приложении оптимистическое управление блокировками при передаче изменений в БД? Ответ. На самом деле ответ зависит от требований вашего приложения. В приложении ShoppingCart при передаче обновлений в БД используются только поля первичного ключа. Эта логика работает, потому что приложение поддерживает отдельную корзину для каждого сеанса. Таким образом, возможность редактирования содержимого одной корзины несколькими пользователями исключена, Для более жесткого контроля параллелизма можно непосредственно перед тем, как пользователь начнет редактировать запись, кэширошть ее содержимое. Тогда у вас будут оригинальные значения полей записи, которыми можно воспользоваться в разделе WHERE запроса UPDATE и исключить возможность обновления записи, если ее уже успел изменить другой пользователь. Более изящное решение — добавить в БД поле с типом данных timestamp и использовать в разделе WHERE значения первичного ключа и этого поля. При этом уменьшается объем данных, кэшируемых в объекте ViewState, Session или в скрытых полях, Вопрос. У меня есть объект DataSet, включающий два объекта DataTable, между которыми определено отношение на основе объекта DataRelation. Как вывести в связанном элементе управления DataGrid только дочерние записи конкретной родительской записи? Ответ. У объекта DataRcnv имеется метод GetChtidRows, возвращающий массив объектов DataRow, который содержит только дочерние записи. Тем не менее связать элементы управления типа DataGrid с массивом объектов DataRow нельзя. Можно создать объект DataView, инициализировать его, заполнив содержимым дочернего объекта DataTable, и затем задать свойству RowFiUer объекта DataView такое значение, при котором через DataView окажутся доступными только дочерние записи. К счастью, есть более простой способ. Создайте объект DataView и инициализируйте его, заполнив содержимым родительского объекта DataTable. Затем найдите в объекте DataView нужную родительскую запись и с помощью метода Cre&teChildView создайте объект DataView, содержащий только дочерние записи.
558
Часть IV Создание эффективных приложений с использованием ADO.NET
Visual Basic .NET Dim dsCustomersOrders As New DataSetf) Dim vueCustomers, vueOrders As DataView vueCustomers = New DataView(dsCustomersOrders.Tables("Customers")) vueCustomers.Sort = "CustomerlD" Dim intCustomerlndex As Integer = vueCustomers.FindC'ALFKI") If intCustomerlndex >= 0 Then 'Located the desired parent row Dim drvCustomer As DataRowView = vueCustomers(intCustomerlndex) vueOrders = drvCustomer.CreateChildView("CustomersOrders") gridOrders.DataSource = vueOrders gridOrders.OataBindO Else 'Невозможно найти нужную родительскую запись End If Visual C# .NET DataSet dsCustomersOrders = new DataSetO; DataView vueCustomers, vueOrders; vueCustomers = new DataView(dsCustomersOrders.Tables["Custcnners"]); vueCustomers.Sort = "CustomerlD"; int intCustomerlndex = vueCustomers.FindC'ALFKI"); if {intCustomerlndex >= 0) { //Located the desired parent row DataRowView drvCustomer = vueCustomers[intCustomerIndex]; vueOrders = drvCustomer,CreateChildViewC'CustomersOrders"); gridOrders.DataSource = vueOrders; g ridOrders.DataSindQ; ) else I
;
//Невозможно найти нужную родительскую запись
ПРИЛОЖЕНИЯ
ПРИЛОЖЕНИЕ
А Прочие поставщики данных .NET
Одесь подробно обсуждается работа с поставщиками данных .NET, отличающимися от поставщика OLE DB .NET Data Provider. В большинстве показанных ранее фрагментов кода, а также в книге в целом я взаимодействовал с БД при помощи поставщика OLE DB .NET Data Provider. Конечно, существуют и другие поставщики данных .NET. Первая версия Microsoft .NET Framework включала поставщик SQL Client .NET Data Provider. Вскоре после этого Microsoft выпустила поставщик ODBC .NET Data Provider. Когда я работал над этой книгой, специалисты Microsoft разрабатывали поставщик данных Oracle Client .NET Data Provider. Сейчас мы поговорим о том, как использовать эти поставщики данных .NET и чем они отличаются от поставщика OLE DB .NET Data Provider.
Поставщик данных SQL Client .NET Data Provider Назначение поставщика SQL Client ,NET Data Provider — обеспечить максимально быстрый доступ к БД Microsoft SQL Server и Microsoft Desktop Engine (MSDE).
Именованные параметры и маркеры параметров В отличие драйверов OLE DB и ODBC, использующих маркер параметров ?, поставщик SQL Client .NET Data Provider поддерживает именованные параметры. Чтобы создать для поставщика SQL Client .NET Data Provider параметризованный запрос, возвращающий заказы конкретного клиента, нужно использовать такой синтаксис: SELECT OrderlD, Customer-ID, EmployeelD, OrderD ate FROM Orders WHERE CustomeriD = @CustomarID
Приложение А Прочие поставщики данных .NET
561
Поставщик OLE DB .NET Data Provider использует позиционные маркеры параметров ?, и для него приведенный выше запрос будет выглядеть так: SELECT OrderlD, CustomerlD, EmployeelD, OrderD ate FROM Orders WHERE CustomerXD = ?
Чем вызвана разница в синтаксисе? В целях помощи разработчикам, имеющим опыт работы предыдущими технологиями доступа к данным (OLE DB и ODBC), в поставщике OLE DB .NET Data Provider предусмотрена поддержка универсальных маркеров параметров, использовавшиеся в этих технологиях для создания запросов. OLE DB и ODBC разрабатывались как универсальные технологии доступа к данным. Их целью было создать независимый от СУБД код и заставить базовые компоненты преобразовывать стандартный синтаксис в код, специфичный для БД. Так почему же поставщик SQL Client .NET Data Provider применяет именованные параметры? Потому что того требует SQL Server. Выполнить в SQL Server параметризованный запрос, включающий маркеры параметров ?. нельзя. Когда вы обращаетесь к БД SQL Server через поставщик OLE DB .NET Data Provider с использованием параметризованного запроса, поставщик SQL Server OLE DB Provider анализирует запрос и заменяет маркеры параметров именованными параметрами. Поставщик SQL Server OLE DB Provider поддерживает универсальный стандарт и преобразует запрос в формат, приемлемый для SQL Server. Назначение поставщика SQL Client .NET Data Provider — максимально точно сопоставить свои функции аналогичным функциям SQL Server и обеспечить наибольшую производительность при работе с БД SQL Server. Таким образом, SQL Client .NET Data Provider не анализирует ваши запросы для преобразования маркеров параметров в именованные параметры. Специфичные для СУБД поставщики данных .NET рассчитаны на обеспечение максимальной производительности при работе с соответствующими БД. Как следствие, для достижения высокой производительности приходится частично жертвовать независимостью от платформы.
Подключение к БД SQL Server с помощью объекта SqIConnection Для подключения к БД поставщик SQL Client .NET Data Provider использует объект SqIConnection. Этот объект позволяет подключаться к БД SQL Server и MSDE. Задать свойство ConnectionString объекта SqIConnection можно явно или при помощи конструктора. Затем следует вызвать метод SqlConnection.Open-. Visual Basic .NET Dim strConn As String strConn = "Data Source=(local)\NetSDK;Initial Catalog=No rthwind;" & "Trusted_Connection=Yes;" Dim en As New SqlConnection(strConn) cn.OpenQ cn.CloseO
562
Приложения
Visual C# .NET string strConn; strConn = "Data Source=(local)\\NetSDK;Initial Catalog=N orthwind;" + "Trusted_Connection=Yes;"; SqlConnection en = new SqlConnection(strConn); cn.0pen(); en.Close();
Вы, вероятно, заметили, что строка подключения здесь практически идентична строке, использовавшейся для подключения к БД SQL Server и MSDE при помощи объекта OleDbConnection. Единственное отличие в том, что опущен атрибут Provider^.... Подробнее об атрибутах строки подключения, которые следует применять при работе с объектом SqlConnection — в разделе документации MSDN, посвященном свойству ConnectionString. Объект SqlConnection также предоставляет два свойства, отсутствующих у объекта OleDbConnection: PacketSize и Workstationld. Они доступны только для чтения, однако их значения задают с помощью свойства ConnectionString объекта SqlConnection.
Получение результатов запроса с помощью объекта SqIDataAdapter Для получения результатов запроса и помещения их в объект DatoSet или DataTable применяют объект SqIDataAdapter. Работа с ним идентична использованию объекта OleDbDataAdapter, но есть одно важное отличие. Как уже говорилось ранее, для выполнения параметризованного запроса следует использовать именованные параметры. Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=(local)\NetSDK;Initial Catalog=No rthwind;" & _ "Trusted_Connection=Yes;" strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " & _ "WHERE CustomerlD = eCustomerlO" Dim da As New SqlDataAdapter(strSQL, strConn) Dim param As SqlParameter param = da.SelectCommand.Parameters.Add("@Cust omerlD", SqlDbType.NChar, 5) param.Value = "ALFKI" Dim tbl As New DataTable("Orders") da.Fill(tbl)
Visual C# .NET string strConn, strSQL; strConn = "Data Source=(local)\\NetSDK;Initial Catalog=N orthwind;" + "Trusted_Connection=Yes;"; strSQL = "SELECT OrderlO, CustomerlD, OrderDate FROM Or ders " + "WHERE CustomerlD = $CustomerID"; SqIDataAdapter da = new SqlDataAdapter{strSQL, strConn); SqlParameter param;
Приложение А
Прочие поставщики данных .NET
563
param = da.SelectCommand.Parameters,Add("@Cust omerlD", SqlDOType.NChar, 5); param.Value = "ALFKI";
DataTable tbl = new DataTablef"Orders"); da.Fill(tbl);
Использование объектов SqICommand и SqIDataReader Объект SqICommand позволяет выполнять командные запросы, а также получать результаты запросов при помощи объекта SqIDataReader. Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=(local)\NetSDK; Initial Catalog=No rthwind;" & "Trusted_Connection=Yes;" strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " & _ "WHERE CustomerlD = @CustomerID" Dim en As New SqlConnection(strConn) Dim crnd As New SqlCommandCstrSQL, en) Dim param As SqlParameter param = cmd.Parameters.Add("@CustomerID", SqiD bType.NChar, 5) param.Value = "ALFKI" cn.0pen() Dim rdr As SqIDataReader = cmd,ExecuteReaderO Do While rdr.Read() Console.WriteLine("OrderID = " & rdr.Get!nt32(0)) Console.WriteLine("CustomerlD = " & rdr.GetString(f)) Console.WriteLine("OrderDate = " & rdr.GetDateTime(2)) Console.WriteLineO Loop rdr.CloseO en,Close()
Visual C# .NET string strConn, strSQL;
strConn = "Data Source={local)\\NetSDK;Initial Catalog=N o r t h w i n d ; " + "Trusted_Connection=Yes;"; StrSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " + "WHERE CustomerlD = @CustomerID"; SqlConnection en = new SqlConnection(strConn); SqICommand end = new SqlCommandCstrSQL, en); SqlParameter param; param = cmd.Parameters.Add{"@CustomerID", SqlD bType.NChar, 5); param.Value = "ALFKI"; cn.0pen(); SqIDataReader rdr = cmd. ExecuteReaderO; while (rdr.ReadO) < Console.WriteLinef'OrderlD = " + rdr.Get!nt32(0)); Console.WriteLine("CustomerID = " +• rdr.GetString(l));
564
Приложения
Console. WriteLine("OrderDate = " + rdr.GetDateTime(2)); Console.WriteLineO;
I rdr.Closef); cn.Close();
Примечание Объект Command не поддерживает значение Table из перечисления CommandType. Как вы помните из главы 12, объект SqlCommand также предоставляет объект ExecuteXmlReader. Используя его, вы получите результаты запроса FOR XML с помощью объекта XmlReader,
Методы GetSqkTnnfiaHHbix> и пространство имен SqlTypes Как и объект QleDbDataType, объект SqlDataReader предоставляет несколько методов Ое11 позволяющих возвращать значения с разными типами данных .NET. Кроме того, SqlDataReader предоставляет и дополнительные методы СеКТицЦанныхУ, соответствующие различным типам данных из пространства имен System JDataSqlTypes. Следующий фрагмент получает данные одной из записей таблицы Orders и сохраняет это содержимое с использованием типов данных, относящихся к пространству имен SqlTypes — Sqllnt32. SqlString и SqlDateTime-. Visual Basic .NET 'Imports System.Data.SqlTypes Dim strConn, strSQL As String strConn = "Data Source=(local)\NetSDK; Initial Catalog=No rthwind;" & "Trusted_Connection=Yes;" strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " & _ "WHERE OrderlD = 10643" Dim en As New SqlConnection(strConn) Dim cmd As New SqlCommand(strSQL, en) Dim rdr As SqlDataReader Dim intOrderlD As Sqllnt32 Dim strCustomerlD As SqlString Dim datOrderOate As SqlDateTime cn.OpenO rdr = cmd.ExecuteReader(CommandBehavior,Single Row) If rdr.Read Then intOrderlD = rdr.GetSqlInt32(0) StrCustomerlD = rdr.GetSqlString(l) datOrcterDate = rdr.GetSqlDateTime(2) End If rdr.Close() cn.Close()
Приложение А
Прочие поставщики данных .NET
565
Visual C# .NET //using System.Data.SqlTypes; string strConn, strSQL; strConn = "Data Source=(local)\\NetSDK;Initial Catalog=N orthwind;" + "Trusted_Connection=Yes;"; strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " + "WHERE OrderlD = 10643"; SqlConnection en = new SqlConnection(strConn); SqlCommand cmd = new SqlCommandfstrSQL, en); SqlDataReader rdr; Sqllnt32 intOrderlD; SqlStrlng strCustomerlD; SqlDateTime datOrderDate; cn.0pen(); rdr = cmd.ExecuteReader(CommandBehavior.Single Row); if (rdr.ReadO) { intOrderlD = rdr.GetSqlInt32(0); strCustomerlD = rdr,GetSqlString(1); datOrderDate = rdr.GetSqlDateTime(2);
} rdr.CloseO; cn.CloseO;
Зачем использовать эти специфичные для поставщика типы данных? Основных причин две. • Производительность. Типы данных из пространства имен SqlTypes повышают производительность кода, поскольку именно их внутренне использует поставщик SQL Client .NET Data Provider. Если возвращать данные с использованием типов данных .NET, вызывая методы Getlnt32, GetString и т.д., поставщикуSQL Client .NET Data Provider придется преобразовывать эти данные. Использование специфичных для поставщика типов данных позволяет избежать такого преобразования. Я обнаружил, что производительность кода на основе пространства имен SqlTypes обычно на 10—15% выше производительности кода, использующего стандартные типы данных .NET, • Простота кода. Мало кому из программистов нравится обрабатывать значения NULL. В показанном ранее фрагменте кода, использовавшем стандартные типы данных .NET, нет проверок на наличие значений NULL. Если бы любое из полей в запросе содержало значение NULL, код сгенерировал бы исключение. Типы данных .NET не позволяют сохранять значения NULL. Прежде чем получать данные, убедитесь с помощью метода DataReaderJsDBBull в отсутствии таких значений. Тем не менее специфичные для поставщика типы данных позволяют использовать значения NULL. Все классы из пространства имен SqlTypes предоставляют метод IsNulL Таким образом, результаты запроса можно сохранять с использованием этих типов, не проводя предварительную проверку данных. И хотя такая проверка данных в коде все же требуется, ее можно выполнить позже. При применении типов SqlTypes обращение к объекту SqlDataReader упроща-
566
Приложения
ется, т. е. результаты запросов удается получить быстрее. Кроме того, это позволяет быстрее закрыть объект SqlDataReader и освободить соединение,
Вызов хранимых процедур Объект SqlCommand позволяет вызывать хранимые процедуры SQL Sewer и MSDE. Данный объект предоставляет свойство CommandType, упрощающее код для вызова хранимый процедур. Можно задать свойству CommandText объекта SqlCommand имя хранимой процедуры, свойству CommandType — значение StoredProcedure и затем вызвать эту хранимую процедуру: Visual Basic .NET Dim strConn As String strConn = "Data Source=(local)\NetSDK; Initial CataLog=No rthwind;" 4 "Trusted_Connection=Yes;" Dim en As New SqlConnection(strConn) Dim cmd As New SqlCommand("CustOrdersQrders", en) cmd.CommandType = CommandType.StoredProcedure Dim param As SqlParameter param = cmd.Parameters.Add("@CustomerID", SqlD bType.NChar, 5) param.Value = "ALFKI" cn.0pen() Dim rdr As SqlDataReader = cmd.ExecuteReader() Do While rdr.ReadO Console.WriteLine(rdr("QrderID")) Loop rdr.Close() cn.Close()
Visual C# .NET string strConn; strConn = "Data Source=(local)\\NetSDK; Initial Catalog=N orthwind;" + "Trusted_Connection=Yes;"; SqlConnection en = new SqlConnection(strConn); SqlCommand cmd = new SqlCommandC'CustOrdersOrd ers", en); cmd.CommandType = CommandType.StoredProcedure; SqlParameter param; param = cmd.Parameters.Add("@CustomerID", SqlD bType.NChar, 5); param.Value = "ALFKI"; cn.OpenQ; SqlDataReader rdr = cmd. ExecuteReaderQ; while (rdr.ReadO) Console.WriteLine(rdr["OrderID"]); rdr.CloseO; cn.CloseO;
Воспользовавшись средствами трассировки SQL Server, вы увидите, что поставщик SQL Client .NET Data Provider преобразовал эту информацию в следующий синтаксис:
Приложение А
Прочие поставщики данных .NET
567
EXEC CustOrdersOrders @CustomerID
Для обеспечения максимальной производительности кода используйте такой же синтаксис, но не изменяйте значение свойства CommandType no умолчанию — Text. Чтобы получить с использованием этого синтаксиса данные при помощи параметров вывода, добавьте после имени параметра ключевое слово OUT; EXEC ХранимаяПроцедура ФЛараметрВвода, @ПараметрВывода OUT
Получение информации схемы БД Объект OleDbConnection предоставляет метод GetOleDbScbemaTable, позволяющий получить из БД информацию схемы, например список таблиц или столбцов. Прямого эквивалента этой функции у поставщика SQL Client .NET Data Provider нет. Тем не менее SQL Server позволяет получить такие данные с помощью представлений информационной схемы. Следующий запрос возвращает сведения о таблицах БД SQL Server: SELECT * FROM INFORMATION.SCHEMA.TABLES
Для получения сведений о таблицах, столбцах, хранимых процедурах, ограничениях и т.д. предусмотрены различные представления. Чтобы вам было от чего отталкиваться, я покажу некоторые наиболее распространенные запросы. Данный запрос возвращает список имен таблиц: SELECT TABLE_NAME FROH INFORMATION_SCHEMA.TABLES WHERE TABLE.TYPE = 'BASE TABLE 1
Данный запрос возвращает список имен представлений: SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE.TYPE = 'VIEW
Данный запрос возвращает список имен столбцов таблиц: SELECT TABLE.NAME,
COLUMN_NAHE,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH,
NUMERIC_PRECISrON, NUMERIC_SCALE FROH INFORMATION.SCHEMA.COLUMNS WHERE TABLE_NAME IN (SELECT TABLE.NAME FROM INFORMATION.SCHEMA.TABLES WHERE TABLE.TYPE
= 'BASE T A B L E 1 )
ORDER BY TABLE.NAME
Данный запрос возвращает список имен процедур: SELECT SPECIFIC.NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE.TYPE = 'PROCEDURE'
Данный запрос возвращает список параметров этих процедур: SELECT SPECIFIC.NAME,
PARAMETER.NAME,
CHARACTER_MAXIHUM_LENGTH,
PARAMETER_MODE,
NUMERIC_PRECISION,
DATAJTYPE,
NUMERIC_SCALE
FROH INFORMATION_SCHEMA.PARAMETERS WHERE SPECIFIC.NAME IN (SELECT SPECIFIC_NAME FROM INFORMATION SCHEMA.ROUTINES
Приложения
568
WHERE ROUTINE. TYPE = ORDER
BY
'PROCEDURE')
SPECIFIC NAME
Подробнее об использовании представлений информационной схемы — в SQL Server Books Online.
Поставщик данных ODBC .NET Data Provider Вскоре после выхода .NET Framework Microsoft выпустила третий поставщик данных .NET — ODBC .NET Data Provider. Он предназначен для взаимодействия с БД, использующими ODBC-драйверы. Поскольку первая версия поставщика ODBC .NET Data Provider не входит в состав .NET Framework, нельзя просто создать в Visual Studio .NET новый проект и начать работать с этим поставщиков. Загрузив с Web-узла MSDN и установив поставщик ODBC .NET Data Provider, добавьте в своем проекте ссылку на него при помощи диалогового окна Add Reference (рис. Л-1).
•NET
[COM | Projects Version
.NET Co,,. .NETRu,,. Mterpsoft.DaU.Qcbc.dl d Microsoft. Cat d,5qlXm Microsoft JScript Microsoft .msht ml Microsoft, stdformar. Mcrosoft. VisualBasi с, V,.sa, Microsoft.VisualC Microsoft, VisualStudio,.vccod... VCCod .VCProiect Microsoft.Visu. Mirrrnnfr Umi, .WPrm...
7.0.3300.0 7,0.3300,0 I.Q.3300.G 3,0.1523,0 7,0.3300,0 ' I
..IV
7,0.3300,0 7,0.3300.0 7,0.3300,0 7,0.3300.0 7,0.3300,0 7.П.^ПП П
Tpa№
~~~~
™~
T*l
O:\WIhlrJHMicrosoft, NEHFra... D:I.WINNHMicroEoFt.NET\Fra.,, O:\Pfogr am Rte^MfcrQSOft.tJ.,. D;\ProgramFile5l.SQLXML3,0... D : \WINNT\Micro5oft ,NET\Frd. , , D:\ProgfamFile5y4iciasoft.N,.. DilProgiam Files \тл\ его soft. N... D;\WlNNT\Microsoft.NET\Fra... D:lW,INNTlMiCtOSOrt.NET\F[a... D : ^S,NETiCommon7\roE\Pu. . . D : \VS.NET\Common7UoriPu. . , П' \V1.NFTlrnrnmnn7lrnFlfti... £\
eeted Components: c soft .oats iOdbt.dl
Cancel
Рис. А-1.
Добавление ссылки на поставщик ODBC .NET Data Provider
Из рис. А-1 видно, что пространство имен поставщика ODBC .NET Data Provider отличается от пространств имен поставщиков OLE DB и SQL Client .NET Data Provider. Объекты поставщика ODBC .NET Data Provider, по крайней мере первой его версии, относятся к пространству имен MicrosoftData.Qdbc. Данный поставщик, вероятно, войдет в состав будущих версий .NET Framework, и вполне возможно. что его пространством имен станет SystemData.Odbc. В приведенных далее фрагментах кода предполагается, что вы при помощи соответствующих конструкций языка по вашему выбору (команда Import в Visual Basic .NET и команда using в Visual С* .NET) добавили в проект и модуль кода ссылки на поставщик ODBC .NET Data Provider.
Приложение А
Прочие поставщики данных .NET
569
Подключение к БД при помощи объекта OdbcConnection Для подключения к БД с помощью поставщика ODBC .NET Data Provider используется класс OdbcConnection. Создайте экземпляр объекта OdbcConnection, задайте его свойству ConnectionString нужное значение (явно или средствами конструктора) и вызовите метод OdbcConnection.Open. Вот несколько примеров строк подключения для взаимодействия с БД при помощи объекта OdbcConnection. Подробнее о подключении в БД — в документации, посвященной свойству ConnectionString объекта OdbcConnection, а также в документации вашего ODBC-драйвера. Подключение к БД SQL Server с указанием имени пользователя и пароля: Driver={SQL Server};Server=(local)\NetSDK; Database=Northwind; 1ЛО=ИмяПользователя;РИО=Пароль; Подключение к БД SQL Server по доверенному соединению: Driver={SQL Serve г};Server=(local)\NetSDK; Database=Northwind;Trusted_Connection=Yes; Подключение к БД SQL Server с использованием DSN-имсни ODBC ODBC: 05Н=ИмяИсточнжаДаннь1х; Подключение к БД SQL Server с использованием файлового DSN-имени ODBC: ОЗН=ИмяФайловогоИсточникаДанных; Следующий фрагмент кода подключается на локальном компьютере к экземпляру БД MSDE, устанавливаемому вместе с .NET Framework SDK: Visual Basic .NET Dim strConn As String strConn = "Driver={SQL Server};Server=(local)\NetSDK;" & "Database=Northwind;Trusted_Connection=Yes;" Dim en As New OdbcConnection(strConn) cn.OpenO cn.CloseO Visual C# .NET string strConn; strConn = "Driver={SQL Server};Server=(local)\\NetSDK;" + "Database=Northwtnd;Trusted_Connection=Yes;"; OdbcConnection en = new OdbcConnection(strConn ); cn.OpenO; cn.CloseO;
Использование параметризованных запросов Поставщик ODBC .NET Data Provider поддерживает параметризованные запросы точно так же, как и поставщик OLE DB .NET Data Provider. В строке запроса обозначьте параметры с помощью маркеров ? и затем добавьте в набор Parameters
570
Приложения
объекта OdbcCommand соответствующие объекты OdbcParameter. Именованные параметры поставщиком ODBC .NET Data Provider не поддерживаются.
Получение результатов запроса
с помощью объекта OdbcDataAdapter Получить результаты запроса и поместить их в объект DataSet или DataTahle позволяет объект OdbcDataAdapter: Visual Basic .NET Dim strConn, strSQL As String strConn = "Driver=(SQL Server};Server=(local)\NetSDK;" & _ "Database=Northwind;Trusted_Connection=Yes;" strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " & "WHERE CustomerlD = ?" Dim da As New OdbcDataAdapter(strSQL, strConn} Dim param As OdbcParameter param = da.SelectCommand.Parameters.Add("@Cust omerlD", OdbcType.NChar, 5) param.Value = "ALFKI" Dim tbl As New DataTableC'Orders") da.Fill(tbl)
Visual C# .NET string strConn, strSQL; strConn = "Driver={SQL Server};Server=(local)\\NetSDK;" + "Database=Northwind;Trusted_Connection=Yes;"; StrSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " + "WHERE CustomerlD = ?"; OdbcDataAdapter da = new OdbcDataAdapter(strSQ L, strConn); OdbcParameter param; param = da.SelectCommand.Parameters.Add{"@Cust omerlD", OdbcType.NChar, 5); param.Value = "ALFKI*; DataTable tbl = new DataTableC'Orders"); da.Fill(tbl);
Просмотр результатов запроса при помощи объекта OdbcDataReader Следующий код основан на том же запросе, но получает и выводит его результаты с помощью объектов OdbcCommand и OdbcDataReader. Visual Basic .NET Dim strConn, strSQL As String strConn = "Driver={SQL Server};Server=(local)\NetSDK;" & "Database=Northwind;Trusted_Connection=Yes;" strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " & _ "WHERE CustomerlD = ?" Dim en As New OdbcConnection(strConn)
Приложение А
Прочие поставщики данных .NET
571
Dim cmd As New OdbcCommancKstrSQL, en) Dim param As OdocParameter param = cmd.Parameters.Add("@CustomerID", Odbc Type.NChar, 5) param.Value = "ALFKI" cn.OpenO Dim rdr As OdbcPataReader = cmd.ExecuteReader( ) Do While rdr.ReadO Console.WriteLine("OrderID = " & rdr.Get!nt32(0)) Console.WriteLineC'CustomerlD = " & rdr.GetString(1» Console.WriteLine("OrderDate = " & rdr.GetDateTime(2)) Console.WriteLine() Loop rdr.Close() cn.CloseO Visual C# .NET string strConn, strSQL; strConn = "Driver=(SQL Server};Server=(local)\\NetSDK;" + "Database=Northwind;Trusted_Connection=Yes;"; strSQL = "SELECT OrderlD, CustomerlD, OrderDate FROM Or ders " + "WHERE CustomerlD = ?"; OdbcConnection en = new OdbcConnection(strConn ); DdbcCommand cmd = new OdbcCommand(strSQL, on); OdbcParameter param; param = cmd.Parameters.Add("@CustomerID", Odbc Type.NChar, 5); param.Value = "ALFKI"; cn.OpenO; OdbcDataReader rdr = crod.ExecuteReaderO; while (rdr.ReadO) ( Console.WriteLine("QrderID = " + rdr.GetInt32(0)>; Console.WriteLineC'CustomerlD = " + rdr.GetString(1»; Console.WriteLine("OrderDate = " + rdr.GetDateTime(2)); Console.WriteLinef);
! rdr.Closef); en.Closef);
Вызов хранимой процедуры В первой версии поставщика ODBC .NET Data Provider объект OdbcCommand не поддерживает значений Table и StoredProcedure из перечисления CommandType. Для вызова хранимых процедур с помощью поставщика ODBC .NET Data Provider вам потребуется изучить синтаксис CALL ODBC. К счастью, он прост: {? = CALL MyStoredProc(?, ?, ?)} Перед именем хранимой процедуры следует добавить ключевое слово O\LL. Чтобы передать в вызове хранимой процедуры параметры, независимо от их типа (параметры ввода, вывода или ввода-вывода) используйте маркер ?. Маркеры па-
572
Приложения
раметров разделяются запятыми, а список параметров нужно заключить в скобки. Для перехвата возвращаемого значения добавьте перед ключевым словом конструкцию ?=, точно так же, как если бы в коде требовалось получить значение, возвращаемое вызовом функции. Весь запрос следует заключить в фигурные скобки. Следующий фрагмент кода вызывает параметризованную хранимую процедуру с использованием поставщика ODBC .NET Data Provider: Visual Basic .NET Dim strConn, strSQL As String strConn = "Driver={SQL Server};Server={local)\NetSDK;" & "Database=Northwind;Trusted_Connection=Yes;" strSQL = "{CALL CustOrdersOrdersC?)}" Dim en As New OdbcConnection(strConn) Dim cmd As New OdbcCommand(strSQL, en) Dim pa ram As OdbeParameter param = cmd. Parameters.Add("@CiJStomerID", Odbc Type.NChar, 5) param.Value = "ALFKI" cn.OpenO Dim rdr As OdbcDataReader = cmd,ExecuteReader( ) Do While rdr.ReadO Console.WriteLineC'OrderlD = " & rdr.Get!nt32(0)) Console.WriteLine("OrderDate = " & rdr.GetDateTime(l)) Console. WriteLineO Loop rdr.CloseO cn.Close()
Visual C# .NET string strConn, strSQL; strConn = "Driver={SQL Server};Server=(local)\\NetSDK;" + "Database=Northwind;Trusted_Connection=Yes;"; strSQL = "{CALL CustOrdersOrdersC?)}"; OdbcConnection en = new OdbcConnection(strConn }; OdbcCommand cmd = new OdbcCommand(strSQL, en); OdbcParameter param; param = cmd.Parameters.Add("@CustomerID", Odbc Type.NChar, 5); param.Value = "ALFKI"; cn.OpenO; OdbcDataReader rdr = cmd.ExecuteReaderO; while (rdr.ReadO) -, Console.WriteLine("OrderID = " + rdr.Get!nt32(0)); Console.WriteLineC'QrderDate = " + rdr.GetDateTime(l)); Console.WriteLineO; ) rdr.CloseO; cn.CloseO;
Приложение А
Прочие поставщики данных .NET
573
Получение информации схемы БД К сожалению, поставщик ODBC .NET Data Provider, no крайней мере первая его версия, не предоставляет средств получения информации схемы БД. У этого поставщика нет эквивалента метода QleDhConnection.GetOleDbSchema. Чтобы получить из БД SQL Server и MSDE сведения схемы, по-прежнему можно выполнять запросы к представлениям информационных схем, обсуждавшимся в разделе, посвященном поставщику SQL Client .NET Data Provider, но такие запросы, поддерживаются не всеми СУБД. Возможно, в следующих версиях поставщика эти ограничения будут преодолены,
Поставщик данных Oracle Client .NET Data Provider На момент написания этой книги специалисты Microsoft разрабатывали поставщик данных .NET для БД Oracle. Он рассчитан на взаимодействие с БД Oracle версий 8i и более поздних и позволит работать с новыми типами данных Oracle, такими, как LOB и BFILE. Кроме того, поставщик позволит выбирать содержимое нескольких курсоров REF из хранимой процедуры. Microsoft не сообщает, как и где будет выпущен поставщик Oracle Client .NET Data Provider и будет ли он отдельным компонентом или составной частью последующих версий .NET Framework. Если поставщик станет отдельным компонентом, в проектах на него придется добавлять ссылку, как описано в разделе, посвященном поставщику ODBC .NET Data Provider. На момент написания данной книги пространство имен поставщика Oracle Client .NET Data Provider — MicrosoftData.OracteCHent. Предполагается, что Oracle Client .NET Data Provider будет взаимодействовать с вашими БД Oracle при помощи клиентских библиотек Oracle. Для взаимодействия с БД Oracle посредством поставщика Oracle Client .NET Data Provider вам следует установить клиентские компоненты Oracle версии 8.1.7 или более поздней. Кроме того, для каждой БД, к которой вы будете подключаться, необходимо с помощью утилиты конфигурирования клиента Oracle определить псевдоним. В показанных далее фрагментах кода предполагается, что вы при помощи соответствующих конструкций языка по вашему выбору (команда Import в Visual Basic .NET и команда using в для Visual С* .NET) добавили в проект и модуль кода ссылки на поставщик Oracle Client .NET Data Provider.
Подключение к БД при помощи объекта OracleConnection Для подключения к БД Oracle используется объект OracleConnection. Как и при работе с другими поставщиками данных .NET, создайте экземпляр объекта OracleConnection, задайте его свойству ConnectionString нужное значение (явно или средствами конструктора) и вызовите метод OracleConnection.Open. Visual Basic .NET Dim strConn As String strConn = "Data Source=MyOracleDatabaseAlias;" & _ "User ID=MyUserID;Password=MyPasswor d;"
574
Приложения
Dim en As New OracleConnection(strConn) cn.Openf) cn.Close()
Visual C# .NET string strConn; strConn = "Data Source=MyOracleDatabaseAlias;" + "User ID=MyUserID;Password=MyPasswor d;"; OracleConnection en = new OracleConnection(str Conn); cn.OpenO; cn.CloseO;
Использование параметризованных запросов Как и поставщик SQL Client .NET Data Provider, Oracle Client .NET Data Provider поддерживает только именованные параметры. Единственное отличие в том, что перед параметром должно стоять двоеточие, т. е. параметризованный Oracleзапрос выглядит так: SELECT EMPNO,
ENAME FROM EHP WHERE JOB = ;JOB
Получение результатов запроса с помощью объекта OracleDataAdapter Следующий фрагмент кода заполняет с помощью объекта OracleDataAdapter объект DataTable результатами показанного ранее параметризованного запроса.
Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=MyOracleDatabaseAlias;" & _ "User ID=HyUserID;Password=MyPasswor d;" StrSQL = "SELECT EMPNO, ENAME FROM EHP WHERE JOB = :JOB " Dim da As New OracleDataAdapter(strSQL, strCon n) Dim param As OracleParameter param = da.SelectCommand.Parameters.Add(":JOB" , OracleType.VarChar, 9) param.Value = "CLERK" Dim tbl As New DataTable() da.Fill(tbl) Console.WriteLine("Retrieved " & tbl.Rows.Count & " row(s)")
Visual C# .NET string strConn, strSQL; strConn = "Data Source=MyOracleDatabaseAlias;" + "User ID=MyUserID;Password=MyPasswor d;"; StrSQL = "SELECT EMPNO, ENAHE FROM EMP WHERE JOB = :JOB "; OracleDataAdapter da = new OracleDataA6apter(s trSQL, strConn); OracleParameter param; param = da.SeLectCommand.Parameters.Addf";JOB" , OracleType.VarChar, 9);
Приложение А
Прочие поставщики данных .NET
575
param.Value = "CLERK"; DataTable tbl = new DataTableO; da.Fill(tbl); Console.WriteLine("Retrieved " + tbl.Rows.Count + " row(s}");
Просмотр результатов запроса при помощи объекта OracleDataReader Следующий фрагмент кода возвращает те же данные с использованием объекта OracleDataReader. Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=MyOracleDatabaseAlias;" & "User ID=MyUserID;Password=HyPasswor d ; " strSQL = "SELECT EHPNO, ENAME FROM EHP WHERE JOB = :JOB " Dim en As New OracleConnection(strConn) Dim cmd As New OracleCommandCstrSQL, en) Dim param As OracleParameter param = cmd.Parameters.Add(":JOB", OracleType. VarChar, 9) param.Value = "CLERK" cn.Openf) Dim rdr As OracleDataReader = cmd.Executefleade r() Do While rdr.ReadO Console.WriteLine("EmpNo = " & rdr.GetDecimal(Q)) Console.WriteLineC'EName = " & rdr.GetString(l)) Console.WritelineO Loop rdr.Close{) cn.CloseO
Visual C# .NET string strConn, strSQL; strConn = "Data Source=MyOracleDatabaseAHas;" + "User ID=MyUserID;Password=MyPasswor d;"; strSQL = "SELECT EMPNO, ENAME FROM EMP WHERE JOB = :JOB "; OracleConnection en = new OracleConnection(str Conn); OracleCommand cmd = new OracleCommand(strSQL, en); OracleParameter param; param = cmd.Parameters.Add(":JOB", OracleType. VarChar, 9); param.Value = "CLERK"; cn.OpenO; OracleOataReader rdr = cmd.ExecuteReader(); while (rdr.ReadO) { Console.WriteLineC'EmpNo = " + rdr.GetDecimal(O)); Console.WriteLineC'EName = " + rdr.GetString{1»; Console.WriteLineO;
576
Приложения
rdr.Close(); cn.CloseO;
Специфичные для Oracle типы данных Поставщик Oracle Client .NET Data Provider включает специфичные для Oracle типы данных, точно так же, как SQL Client .NET Data Provider — для SQL Server. Использование таких типов данных повышает производительность кода и ускоряет выборку данных из объекта DataReader, поскольку сохранять значения с этими типами данных допустимо без предварительной проверки на наличие значений NULL. Кроме того, многие из этих типов данных предоставляют дополнительную функциональность, недоступную при использовании соответствующего типа данных .NET. Следующий фрагмент кода получает и выводит результаты запроса с использованием специфичных для Oracle типов данных.
Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=HyOracleDatabaseAlias;" & _ "User ID=MyUserID;Password=MyPasswor d;" StrSQL = "SELECT EMPNO, ENAME FROM EMP WHERE JOB = :JOB " Dim en As New OracleConnection(strConn) Dim cmd As New OracleCommand(strSQL, en) Dim param As OracleParameter param = cmd.Parameters.Add(": JOB", QracleType. VarChar, 9) param.Value = "CLERK" Dim numEmpNo As OracleNumber Dim strEName As OracleString cn.QpenO Dim rdr As OracleDataReader = cmd.ExecuteReade r() Do While rdr.Read() numEmpNo = rdr.GetOracleNumber(O) strEName = rdr.GetOracleString(l) Console.WriteLineC'EmpNo = " & numEmpNo.ToStringO) Console.WriteLlne("EName = " & strEName.ToStringO} Console. WriteLineO Loop rdr.Close() cn.CloseO
Visual C# .NET string strConn, strSQL; strConn = "Data Source=MyOracleDatabaseAlias;" + "User ID=MyUserID;Password=MyPasswor d;"; StrSQL = "SELECT EMPNO, ENAME FROM EMP WHERE JOB = :JOB "; OracleConnection en = new QracleConnection(str Conn); OraclaCommand cmd = new OracleCommand(strSQL, en); OracleParameter param;
Приложение А
Прочие поставщики данных .NET
param = cmd.Parameters.Add(":JOB", QracleType. VarChar, param,Value = "CLERK";
577
9);
QracleNumber numEmpNo; OracleString strEName; cn.OpenO; OracleDataReader rdr = cmd.ExecuteReaderC); while (rdr.ReadO)
( numEmpNo = rdr.GetOracleNumber(O); strEName = rdr.GetOracleString(l); Console.WrlteLineC'EmpNo = " + numEmpNo. ToStringO); Console.WriteLine("EName = " + strEName. ToStringO); Console.WriteLine();
} rdr.CloseO; cn,Close();
Вызов хранимой процедуры Задайте свойству CommandText объекта OracleCommand имя нужной хранимой процедуры и затем добавьте параметры в набор Parameters объекта Command. Добавляя параметры в набор, поставьте перед их именами двоеточия. Затем вызовите метод ExecuteNonQuery-, Visual Basic .NET Dim strConn As String strConn = "Data Source=MyOracleDatabaseAlias;" & "User ID=MyUserID;Password=MyPasswor d;" Dim en As New OracleConnection(strConn) Dim cmd As New OracleCommand("GetNumOrders", с n) cmd.CommandType = CommandType.StoredProcedure Dim param As OracleParameter param = cmd.Parameters.AddC'pCustomerlD", Orac leType.Char, 5) param.Value = "ALFKI" param = cmd.Parameters.AddC'pNumOrders", Oracl eType.Int32) param,Direction = ParameterDirection. Output cn.OpenO cmd.ExecuteNonQuery() Console.WriteLine(pa ram.Value) cn.CloseO
Visual C# .NET string strConn; strConn = "Data Source=MyOracleDatabaseAlias;" "User ID=MyUserID;Password=MyPasswor OracleConnection en = new OracleConnection(str OracleCommand cmd = new OracleCommandC'GetNumO cmd.CommandType = CommandType.StoredProcedure; OracleParameter param;
+ d;"; Conn); rders", en);
578
Приложения
param = cmd.Parameters.AddC'pCustomerlD",
Orac leType.Char,
5);
param.Value = "ALFKI";
param = cmd.Parameters.Add("pNumOrders", Oracl eType.Int32); param.Direction = ParameterDirection.Output; cn.0pen(); cmd. ExecuteNonQueryO; Console.WriteLine(param. Value); en.Closet); Чтобы избежать нагрузки по преобразованию поставщиком этого кода в соответствующий синтаксис Oracle, не изменяйте значение свойства Command-Type по умолчанию, Text, и задайте свойству CommandText запрос в таком формате: BEGIN GetNumOrders(;pCustomerID, :pNumdrders);
END;
Выборка данных из курсоров REF Поставщик Oracle Client .NET Data Provider позволит выбирать посредством вызова хранимой процедуры данные из нескольких курсоров REF. Скажем, у вас есть такое определение пакета Oracle: CREATE PACKAGE PackCursorTest AS TYPE curOrders IS REF CURSOR RETURN OrdersKR OWTYPE; TYPE curDetails IS REF CURSOR RETURN Order.D etailsXROWTYPE; PROCEDURE OrdersAndDetailsForCustomer (pCustomerlD IN CHAR, pQrders OUT curOrder s, pDetails OUT curDetails); END; CREATE PACKAGE BODY PackCursorTest AS PROCEDURE OrdersAndDetailsForCustomer
( pCustomerlD IN CHAR, pOrders OUT curOrders, pDetails OUT curDetails
) AS BEGIN OPEN pOrders FOR SELECT * FROM Orders WHER E CustomerlD = pCustomerlD; OPEN pDetails FOR SELECT * FROM Order.Deta Us WHERE OrderlD IN (SELECT OrderlD FROM Orders WHERE Custom erID = pCustomerlD); END; END;
Следующий фрагмент кода вызывает хранимую процедуру и помещает содержимое обоих курсоров REF в один объект DataSet: Visual Basic .NET Dim strConn, strSQL As String strConn = "Data Source=MyOracleDatabaseAlias;" 4 "User ID=HyUserID;Password=MyPasswor d;" Dim en As New OracleConnection(strConn)
Приложение А
Прочие поставщики данных .NET
strSQL = "PackCursorTest.OrdersAndDetailsForCustomer" Dim cmd As New OracleCommand(strSQL, en) cmd-CommandType = CommandType.StoredProcedure Dim param As OracleParameter param = cmd,Parameters.Add("pCustomerID", Orac leType.Char, 5) param.Value = "ALFKI"
param = cmd.Parameters.AddC'pOrders", OracleTy pe.Cursor) param.Direction = ParameterDirection.Output param = cmd.Parameters.Add("pDetails", Oracle! ype.Cursor) param,Direction = ParameterDirection.Output Dim da As New OracleDataAdapter(cmd) da.TableMappings.Add("Table", "Orders") da.TableMappings.Add("Table1", "Order_Details") Dim ds As New DataSetf) Dim tbl As DataTabLe
da.Fill(ds) For Each tbl In ds,Tables Console.WriteLine(tbl.TableName & " now has " & tbl.Rows.Count & " row(s)") Next tbl Visual C# .NET string strConn, strSOL; strConn = "Data Source=MyOracleDatabaseAlias;" + "User ID=MyUserID;Password=MyPasswor d ; " ; OracleConnection en = new OracleConnection(str Conn); strSQL = "PackCursorTest.OrdersAndDetailsForCustomer"; OracleCommand cmd = new OracleCommand(strSQL, en); cnid.ComniandType = CommandType.StoredProcedure; OracleParameter param; param = cmd.Parameters.Add("pCustomerIO", Orac leType.Char, 5): param.Value = "ALFKI"; param = cmd.Parameters.AddC'pOrders", OracleTy pe.Cursor); param.Direction = ParameterDirection.Output; param = cmd.Parameters.AddC'pDetails", OracleT ype.Cursor); pa ram.Direction = ParameterDirection.Output; OracleDataAdapter da = new OracleDataAdapterfc md); da.TableMappings.Add("Table", "Orders"); da.TableMappings.Add("Tablel", "Order_Details"); DataSet ds = new DataSetO; da.Fill(ds); foreach (DataTable tbl in ds.Tables) Console,WriteLine(tbl.TableName + " now has " + tbl.Rows.Count + " row(s)");
579
580
Приложения
Получение информации схемы БД У поставщика Oracle Client .NET Data Provider нет собственных функций для получения информации схемы, например имен таблиц и столбцов, из БД Oracle. Тем не менее вы сможете обращаться к словарю Oracle и получать нужные сведения. Так, следующие запросы возвращают из БД список таблиц и столбцов: SELECT TABLE_NAME FROM USER.TABLES SELECT TABLE_NAHE, COLUMN_NAME FROM USER_TAB_COLUHNS ORDER BY TABLE_NAME
Подробнее об использовании словаря данных Oracle — в документации на Oracle.
Проблемы, общие для поставщиков данных .NET Поставщики данных .NET обеспечивают повышенную производительность и предоставляют расширенные возможности управления, поскольку каждый из них можно настроить под потребности конкретной СУБД, на работу с которой он рассчитан. Тем не менее переход к отдельным поставщикам данных .NET вызвал в среде разработчиков некоторое замешательство.
Создание кода, независимого от поставщика Предположим, вы создали приложение, использующее поставщик SQL Client .NET Data Provider. Время не стоит на месте, и в соответствии с потребностями клиента вы решили модифицировать приложение так, чтобы оно могло работать и с БД SQL Server, и с БД Oracle. Чем больше кода в приложении основано на поставщике SQL Client .NET Data Provider, тем больше придется менять. Однако, если разделить код на компоненты и организовать их взаимодействие при помощи универсальных интерфейсов типа DataSet, DataTable, IDataReader и DbDataAdapter, вам потребуется изменить лишь код конкретных компонентов. Рассмотрим два примера, использующих такой метод. В первом примере мы создадим функцию, которая внутренне использует поставщик SQL Client .NET Data Provider, но возвращает данные посредством универсального интерфейса DataTable. Затем мы обсудим, как изменить функцию для использования другого поставщика, не затрагивая вызывающий ее код. Во втором примере реализован этот же подход, но задействована функция, возвращающая объект DataAdapter средствами универсального интерфейса {DbDataAdapter. Следующий фрагмент кода вызывает функцию GetQrdersForCustomer, принимающую строку со значением столбца CustomerlD и возвращающую объект DataTable с заказами указанного клиента. Внутренне функция использует параметризованный объект SqlDataAdapter. Visual Basic .NET Dim strCustomerlD As String = "ALFKI" Dim tblOrders As DataTable tblOrders = GetOrdersForCustomer(strCustomerID )
Приложение А
Прочие поставщики данных .NET
581
Private Function GetOrdersForCustomer(Customer ID As String) As DataTable Dim strSQL, strConn As String strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " & _ "FROM Orders WHERE CustomerlD = ©CustomerlD" strConn = "Data Source=(local)\NetSDK;" & "Initial Catalog=Northwind;Trusted_Connection= Yes;" Dim da As New SqlDataAdapter(strSQL, strCo nn) Dim param As SqlParameter param = da.SelectCommand.Parameters.Add("@ CustomerlD", SqlDbType.NChar, 5} param,Value = CustomerlD Dim tbl As New DataTable("Orders")
da.Fill(tbl) Return tbl End Function Visual C# .NET string strCustomerlD = "ALFKI"; DataTable tblOrders; tblOrders = GetOrdersForCustomer(strCustoirerID ); private DataTable GetOrdersForCustomer(string CustomerlD) { string strSQL, strConn; strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " + "FROM Orders WHERE CustomerlD = @CustomerID"; strConn = "Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection= Yes;"; SqlDataAdapter da = new SqlDataAdapter(str SQL, strConn); SqlParameter pa'ram; param = da.SelectCommand.Parameters.Add("@ CustomerlD", SqlDbType.NChar, 5); param.Value = CustomerlD; DataTable tbl = new DataTable("Orders");
da.Fill(tbl); return
tbl;
}
Как уже говорилось, вам может потребоваться модифицировать приложение для взаимодействия с другими СУБД. Функция GetOrdersForCustomer внутренне работает с поставщиком SQL Client .NET Data Provider, но для параметра и возвращаемого значения использует универсальные типы данных — string и DataTable. Таким образом, функцию можно переписать для другого поставщика .NET, не изменяя код с объектом DataTable. Вот как я откорректировал код функции GetOrdersForCustomer, не трогая ее сигнатуру. Visual Basic .NET Private Function GetOrdersForCustomer(Customer ID As String) As DataTable Dim strSQL, strConn As String strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " & _ "FROM Orders WHERE CustomerlD = :CustomerID"
582
Приложения
strConn = "Data Source=MyOracleDatabaseAlias;" 4 _ "User ID=MyUserID;Password=MyPassword;" Dim da As New OracleDataAdapter(strSQL, st rConn) Dim param As OracleParameter param = da.SelectCommand.Parameters.Add(": CustomerlD", OracleType.Char, 5) param.Value = CustomerlD Dim tbl As New DataTable("Orders") da.FillCtbl) Return tbl End Function Visual C# .NET private DataTable GetOrdersForCustomer(string
CustomerlD)
! string strSQL, strConn; strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " + "FROM Orders WHERE CustomerlD = :CustomerlD"; strConn = "Data Source=MyOracleDatabaseAHas;" + "User ID=MyUserID;Password=MyPassword;"; OracleDataAdapter da = new OracleDataAdapt er{strSQL, strConn); OracleParameter param; param = da.SelectCommand.Parameters.Add(": CustomerlD", OracleType.Char, 5); param.Value = CustomerlD; DataTable tbl = new DataTableC"Orders"); da.FillCtbl); return tbl; I
Еще один способ основан на том, что код доступа к данным возвращает объекты при помощи универсальных интерфейсов, поддерживаемых различными поставщиками .NET. Так, можно написать функцию, создающую экземпляр объекта SqlDataAdapter и возвращающую его средствами универсального интерфейса IDbDataAdapter. Visual Basic .NET Dim daOrders As IDbDataAdapter = GetOrdersAdap ter() Dim strCustomerlD As String = "ALFKI" Dim param As IDbDataParameter param = CType(daOrders.SelectCommand. Parameter s{0), IDbDataParameter) param.Value = StrCustomerlD Dim ds As New DataSetQ Dim tblOrders As DataTable = ds.Tables.AddC'Or ders") daOrders,Fill(ds) Private Function GetOrdersAdapterO As IDbData Adapter Dim strSQL, strConn As String strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " & _ "FROM Orders WHERE CustomerlD = «CustomerlD" strConn = "Data Source=(local)\NetSDK;" &
Приложение А Прочие поставщики данных .NET
583
"Initial Catalog=Northwind;Trusted_Connection= Yes;" Dim da As New SqlDataAdapter(strSQL, strCo nn) da.TableMappings.Add("Table", "Orders") da.SelectCommand.Parameters.Add{"@Customer ID", SqlDbType.NChar, 5) Return da End Function
Visual C# .NET IDbDataAdapter daOrders = GetOrdersAdapterf); string strCustomerlD = "ALFKI"; IDbDataPararneter param; param = (IDbDataParameter) daOrders.SelectComm and.Parameters[0]; param.Value = strCustomerlD; DataSet ds = new DataSetO; DataTable tblOrders = ds.Tables.Add("0rders"); daOrders,Fill(ds);
private IDbDataAdapter GetOrdersAdapterQ I string strSQL, strConn; strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " + "FROM Orders WHERE CustomerlD = @CustomerID"; strConn = "Data Source=(local)\\NetSDK;" + "Initial Catalog=Northwind;Trusted_Connection= Yes;"; SqlDataAdapter da = new SqlDataAdapter(str SQL, strConn); da.TableMappings.Add{"Table", "Orders"); da.SelectCommand.Parameters.Add("©Customer ID", SqlDbType.NChar, 5); return da; : В дальнейшем функцию GetQrdersAdapter можно изменить, как показано ниже, чтобы она генерировала и возвращала объект OradeDataAdapter при помощи интерфейса IDbDataAdapter. Корректировать при этом код, использующий интерфейс IDbDataAdapter, не требуется. Visual Basic .NET Private Function GetOrdersAdapterC) As IDbData Adapter Dim strSQL, strConn As String strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " & "FROM Orders WHERE CustomerlD = :CustomerID" strConn = "Data Source=MyOracleDatabaseAlias;" 4 "User ID=MyUserID;Password=MyPassword;" Dim da As New QracleDataAdapter(strSQL, st rConn) da.TableMappings.Add("Table", "Orders") da.SelectCommand.Parameters.Add(":Customer ID", OracleType.Char, 5) Return da End Function
584
Приложения
Visual C# .NET private
IDbDataAdapter
GetOrdersAdapter()
{ string strSQL, strConn; strSQL = "SELECT OrderlD, CustomerlD, EmployeelD, Order Date " + "FROM Orders WHERE CustomerlD = :CustomerID"; strConn = "Data Source=HyOracleOatabaseAlias; " + "User ID=MyUserID; Password=HyPassword; "; OracleDataAdapter da = new OracleDataAdapt er(strSQL, strConn); da. TableMappings.Addf "Table", "Orders"); da . SelectCommand . Pa ramete rs . Add( " : Custome r ID" , 0 racleType . Char, 5) ; return da;
Выбор подходящего типа данных поставщика .NET Если вы знаете основы объектной модели поставщиков данных .NET, то понимаете, что писать код для того или иного поставщика достаточно просто. Правда, переходя от одного поставщика данных .NET к другому, я иногда забываю правильный формат строки подключения или правила создания параметризованных запросов, но это все мелочи. Вот один из вопросов по поставщику ODBC .NET Data Provider, наиболее часто задаваемых в открытых группах новостей, посвященных ADO.NET: «Как мне выбрать для параметров подходящий тип данных поставщика?» Обычно разработчикам приходится его решать, когда они создают собственную логику обновления для объектов DataAdapter. Скажем, в объекте UpdateCommand вашего объекта DataAdapter используется такой запрос: UPDATE Orders SET CustomerlD = ?, OrderDate = WHERE OrderlD = ?
?
Какой тип данных поставщика лучше всего подходит для параметра QrderDate? К счастью, правильный тип данных можно выбрать на основании типа данных, определенного для столбца таблицы. Однако все не столь просто. Если вы обращаетесь к БД SQL Server Northwind при помощи поставщика ODBC .NET Data Provider, свойству OdbcType объекта QdbcParameter следует задать значение QdbcTypeDateTime, Однако при использовании поставщика OLE DB .NET Data Provider свойству OleDbType объекта OleDbParameter нужно задать значение OleDbTypeDBTimeStamp. Как же бедному разработчику быть? Самый лучший способ, который я могу предложить, — • воспользоваться методом GetScbemaTableDataReader. Если параметр соответствует столбцу БД. создайте запрос, возвращающий данные этого столбца. Затем примените объект Command и выполните запрос с помощью метода ExecuteReader. Вызовите метод GetScbemaTable итогового объекта DataReader. Каждая запись объекта DataTable, возвращенного методом DataReader.GetScbemaTable, соответствует столбцу оригинального запроса. Найдите в этом объекте DataTable запись, соответствующую вашему параметру. Просмотрите содержимое поля ProviderType данной записи и преобразуйте целое число в значение из соответствующего перечисления.
Приложение А
Прочие поставщики данных .NET
585
Следующий фрагмент кода определяет подходящее значение свойства OdbcType для столбца OrderDate БД Northwind. Visual Basic .NET Dim strConn, strSQL As String strConn = "Driver={SQL Server};Server=(local)\NetSDK;" & "Database=Northwlnd;Trijsted_Connection=Yes;" Dim en As New OdbcConnection(strConn) cn.OpenO strSQL = "SELECT OrderDate FROM Orders" Dim cmd As New OdbcCommand(strSQL, en) Dim rdr As OdbcDataReader rdr = cmd.ExecuteReader(CommandBehavior.Schema Only) Dim tbl As DataTable = rdr.GetSchemaTableO rdr.CloseQ cn.Close() Dim intOrderDateType As Integer intOrderDateType = CType(tbl.Rows(0)("Provider Type"), Integer) Dim odbcOrderDateType As OdbcType odbcOrderDateType = CType(intOrderDateType, Od bcType) Console.WriteLine("OrderDate") Console.WriteLineCvbTab & "ProviderType = " & intOrderDateType) Console,WriteLinefvbTab & "OdbcType = " & OdbcOrderDateType.ToStringO) Visual C# .NET string strConn, strSQL; strConn = "Driver={SQL Server};Server=(local)\\NetSDK;" + "Database=Northwind;Trusted_Connection=Yes;"; OdbcConnection en = new OdbcConnection(strConn ); cn.OpenO; strSQL = "SELECT OrderDate FROM Orders"; OdbcCommand cmd = new OdbcCommand(strSQL, en); OdbcDataReader rdr = ctnd. ExecuteReader(Comroand Behavior.SchemaOnly); DataTable tbl = rdr.GetSchemaTable(); rdr.Close(); cn.CloseO; int intOrderDateType = (int) tbl.Rows[0]["Prov iderType"]; OdbcType odbcOrderDateType * (OdbcType) intOrd erDateType; Console.WriteLine("OrderDate"); Console.WriteLine("\tProviderType = " + intOrderDateType); Console. WriteLine( "\tOdbcType = " + odbcOrderDateType.ToStringO);
Если писать такой код вам не хочется, воспользуйтесь утилитой Ad Hoc Query Tool, записанной на прилагаемом к книге компакт-диске (подробнее об этой утилите — в приложении Б). На рис. А-2 утилита Ad Hoc Query Tool отображает информацию схемы о результатах запроса, включая столбец с именем специфичного для поставщика .NET типа данных. Как видно, столбцу OrderDate БД соответствует значение DateTime из перечисления OdbcType.
20
595В
586
Приложения
Рис. А-2. Определение подходящего типа данных поставщика .NET с помощью утилиты Ad Hoc Query Tool
ПРИЛОЖЕНИЕ
Б Утилиты
3
дссь рассматриваются записанные на прилагаемом к книге компакт-диске утилиты, разработанные мной, чтобы упростить программистам создание приложений для доступа к данным — ADO.NET Ad Hoc Query- Tool ADO.NET DataAdapter Builder и ADO.NET Navigation Control. Каждая утилита представляет собой незавершенный проект. Я собираюсь периодически размещать в Интернете обновления, добавляющие новые функции и исправляющие обнаруженные ошибки. Они доступны на Web-узле, адрес которого указан в записанном на компакт-диске файле Readme. Как гласит пословица, невозможно постоянно удовлетворять требования всех окружающих вас людей. Помня об этом, я поместил на компакт-диск и исходный код каждой утилиты. Ad Hoc Query Tool и DataAdapter Builder написаны па Microsoft Visual Basic .NET исходный код утилиты Navigation Control дан как на Visual Basic .NET так и на Visual С* .NET Заметьте: эти утилиты не обеспечиваются технической поддержкой и не предназначены для распространения (подробнее — в Лицензионном соглашении Microsoft в конце книги). Используйте их на свой собственный страх и риск.
Утилита ADO.NET Ad Hoc Query Tool При создании приложений для доступа к данным мне нередко хочется проверить содержимое таблиц БД. Visual Studio .NET позволяет сделать это. но не предоставляет способа для выполнения произвольных запросов к БД с использованием выбранного вами поставщика данных .NET В связи с этим я решил создать утилиту, предоставляющую нужную мне функциональность. Утилита ADO.NET Ad Hoc Query Tool (рис. Б-1) позволяет выполнять произвольные запросы к БД с использованием поставщика данных .NET по вашему выбору. Кроме того, она позволяет редактировать результаты запросов и передавать эти
Приложения
588
изменения обратно в БД. С помощью ADO.NET Act Hoc Query Tool удастся также просмотреть информацию схемы о результатах запроса и сгенерировать код на ADO.NET, создающий объект DataTable для храпения результатов вашего запроса,
-
,||....
I
I
Рис. Б-1. Утилита ADO.NET Ad Hoc Query Tool Давайте вкратце рассмотрим эту утилиту.
Подключение к БД Для подключения к БД выберите в главном меню приложения команду Connect. Откроется диалоговое окно (рис. Б-2), где можно указать строку подключения и НУЖНЫЙ поставщик данных .NET.
Connection String.
jProvider=50LOLEDB.Data Souce=(local]\NetSDK;l
.NET Date Provider; fgfebb
Рис. Б-2.
Создание строки подключения средствами Ad Hoc Query Tool
Добавление поставщиков данных .NET По умолчанию в списке поставщиков данных утилиты Ad Hoc Query Tool указаны лишь два поставщика, поставляемых в составе Microsoft .NET Framework — OLE DB .NET Data Provider и SQL Client .NET Data Provider. Тем не менее утилита поддерживает и другие поставщики данных .NET. Для работы с поставщиком данных .NET, установленным на вашем компьютере и не входящим по умолчанию в состав Microsoft .NET Framework, выберите из
Приложение Б
Утилиты
589
списка поставщиков в диалоговом окне Connection пункт