Доступ к данным с помощью ADO.NET ДОСТУП К ДАННЫМ С ПОМОЩЬЮ ADO.NET ............................................................................................. 1 1. ВВЕДЕНИЕ ............................................................................................................................................................. 2 2. ДОСТУП К ДАННЫМ С ПОМОЩЬЮ ADO.NET....................................................................................................... 2 2.1. Краткий обзор ADO.NET ........................................................................................................................... 2 2.2. Сравнение ADO и ADO.NET ....................................................................................................................... 3 3. КОМПОНЕНТЫ ADO.NET И ОБЪЕКТНАЯ МОДЕЛЬ................................................................................................ 4 3.1. DataSet.......................................................................................................................................................... 4 3.1.1. Объект TablesCollection .......................................................................................................................... 4 3.1.2. Объект RelationsCollection ..................................................................................................................... 4 3.1.3. ExtendedProperties .................................................................................................................................... 5 3.2. Managed providers ....................................................................................................................................... 5 3.3. Microsoft ADO.NET managed providers ...................................................................................................... 5 3.4. Поддерживаемые базы данных и OLE DB провайдеры .......................................................................... 6 4. ИСПОЛЬЗОВАНИЕ MANAGED PROVIDER ............................................................................................................... 6 4.1. Управление подключениями ....................................................................................................................... 6 4.2. Формат строки подключения – ADOConnection ..................................................................................... 7 4.3. Формат строки подключения – SQLConnection ...................................................................................... 7 4.4. Команды ...................................................................................................................................................... 7 4.6. Использование хранимых процедур в команде ......................................................................................... 8 4.7. Создание простых DataSetCommand ...................................................................................................... 13 4.8. Обновление базы данных с помощью DataSetCommand и DataSet ....................................................... 14 4.9. Определение отображений таблиц и столбцов .................................................................................... 15 4.10. Mapping/Changing отображение столбцов во время исполнения ...................................................... 17 4.11. Использование параметров DataSetCommands .................................................................................... 18 4.12. Параметры Input/Output и возвращаемые значения ............................................................................ 19 4.13. Объединённое подключение (Connection Pooling) ................................................................................ 22 4.13.1. Определение подкласса SQLConnection ............................................................................................. 22 4.13.2. Включение в код строк, которые организуют объединенное подключение. .................................. 22 4.13.3. Подключение Microsoft.ComServices.dll ............................................................................................. 22 4.13.4. Конфигурирование объединения компонент ..................................................................................... 22 4.13.5. Поддержка транзакций ...................................................................................................................... 23 4.14. Команды генерируемые автоматически .............................................................................................. 23 4.14.1. Правила организации команды Update .............................................................................................. 23 4.14.2. Правила организации команды Insert ................................................................................................. 24 4.14.3. Ограничения логики механизма AutoGen ........................................................................................... 24 4.15. Добавление и удаление событий SQL Managed provider ..................................................................... 24 4.15.1. Обвальные исключения, порождённые обработчиком событий .................................................... 25 4.15.2. Семантика добавления и удаления событий..................................................................................... 25 4.15.3. Сравнение делегатов при добавление и удаление событий ............................................................. 25 5. ИСПОЛЬЗОВАНИЕ DATASET................................................................................................................................ 25 5.1. ИСПОЛЬЗОВАНИЕ DATASET С СУЩЕСТВУЮЩИМИ ДАННЫМИ ....................................................................... 26 5.2. Создание DataSet программным путём .................................................................................................. 27 5.3. Добавление DataTable в DataSet .............................................................................................................. 27 5.4. Добавление отношений между двумя таблицами ................................................................................. 28 5.5. Установка отношений между таблицами ............................................................................................ 29 5.6. Добавление ограничений для DataSet....................................................................................................... 29 5.6.1. ForeignKeyConstraint ............................................................................................................................. 29 5.6.2. UniqueConstraint ..................................................................................................................................... 30 5.7. Работа с событиями DataSet .................................................................................................................. 30 5.8. Использование Typed DataSet ................................................................................................................... 31 6. ИСПОЛЬЗОВАНИЕ DATATABLE ........................................................................................................................... 32 6.1. Создание DataTable .................................................................................................................................. 32 6.2. Добавление столбцов в DataTable ........................................................................................................... 33 6.3. Создание Expression Columns ................................................................................................................... 34 6.4. Создание AutoIncrement столбцов ........................................................................................................... 35 6.5. Создание первичного ключа для таблицы............................................................................................... 35 6.6. Порядок применения Insert, Update и Delete ........................................................................................... 36 7. РАБОТА С ДАННЫМИ В ТАБЛИЦАХ ADO.NET.................................................................................................... 36 7.1. Добавление данных в таблицу ................................................................................................................. 36 7.2. Состояния строки .................................................................................................................................... 37 7.3. Removing или Deleting DataRow из таблицы .......................................................................................... 38 7.4. Работа с данными в таблице .................................................................................................................. 38 7.5. Добавление и чтение информации об ошибках строки ........................................................................ 40 7.6. Принятие или отклонение изменений строк.......................................................................................... 41 7.7. Работа с событиями DataTable .............................................................................................................. 42 7.8. Фильтрация и сортировка строк в таблице ......................................................................................... 43 7.9. Optimistic Concurrency .............................................................................................................................. 44 7.10. Исполнение транзакций ......................................................................................................................... 46 8. СВОД СОБЫТИЙ ADO.NET ................................................................................................................................. 46 1. Введение Работа с данными стала главной задачей современного программного обеспечения, как автономных, так и сетевых прикладных программ. Технология ADO.NET предлагает решение многих проблем, связанных с доступом к данным. ADO.NET - эволюционное развитие ActiveX Data Objects (далее ADO). Это - модель программирования на основе стандартов для создания распределенного, прикладного программного обеспечения, предназначенного для совместного использования данных (data-sharing). ADO.NET имеет несколько существенных преимуществ относительно предыдущих версий ADO, а также других компонент доступа к данным. Эти преимущества относятся к следующим категориям: Interoperability (способность к взаимодействию), Maintainability (надёжность), Programmability (программируемость), и Performance (эффективность). Ниже, мы более подробно рассмотрим эти преимущества. Для прикладных программ на основе ASP.NET предусмотрена возможность использования технологии ADO.NET, что позволяет обращаться к данным по сети или через Веб. Поэтому, совместное использование ASP.NET и ADO.NET также будет предметом нашего рассмотрения. Подобно ADO, ADO.NET – это Application Programming Interface (интерфейс прикладных программ, далее API), призванный обеспечить доступ к данным и информации. ADO.NET поддерживает такие современные требования, как создание внешних клиентских интерфейсов (front-end) к базам данных, и программирование объектов промежуточного слоя, применяющиеся в прикладных программах, инструментальных средствах, языках, или Internet браузерах. 2. Доступ к данным с помощью ADO.NET ADO.NET является преемником Microsoft ActiveX Data Objects (ADO). Это W3C стандартизированная модель программирования для создания распределенных прикладных программ, нацеленных на совместное использование данных. Подобно ADO, ADO.NET является программным интерфейсом (API) для прикладного программного обеспечения, позволяющим обращаться к данным и другой информации. ADO.NET поддерживает такие современные требования, как создание клиентского интерфейса к базам данных на фронтальном уровне и на уровне промежуточного слоя объектов клиентских приложений, инструментальных средств, языков программирования или Internet браузера. 2.1. Краткий обзор ADO.NET Технология ADO.NET разработана на основе промышленных стандартов. ADO.NET, подобно ADO, обеспечивает интерфейс доступа к OLE DB - совместимым источникам данных, таким, как Microsoft SQL Server 2000. Прикладные программы, позволяющие пользователям совместно использовать данные, могут использовать ADO.NET для подключения к источниками данных, а также для поиска, управления, и модификации этих данных. Также, прикладные программы (далее ПП) могут использовать OLE DB для управления данными, хранящимися в не относительных форматах, таких, как Microsoft Excel. В решениях, требующих офлайнового или удаленного доступа к данным, ADO.NET использует XML для обмена данными между программами или с Веб страницами. Любой компонент, который обслуживает XML, также может использовать и компоненты ADO.NET. Если передача пакетов компонентом ADO.NET подразумевает поставку набора данных в файле XML, компонентом, способным обеспечить его получение, может быть только компонент ADO.NET. Передача данных в XML – формате даёт возможность программистам легко отделить обработку данных от компонент пользовательского интерфейса data-sharing ПП, разместив их на отдельных серверах. Это может существенно повысить эффективность и надёжность многопользовательских систем. Для распределенных ПП, использование наборов данных XML в ADO.NET обеспечивает лучшую эффективность, чем использование COM для офланового обслуживания данных в ADO. Поскольку передача наборов данных происходит через файлы XML, описанные в очень простом, принятом повсюду стандарте и являющиеся (по сути) обычными текстовыми файлами, компоненты ADO.NET не имеют ни одного из архитектурных ограничений COM. Наборы данных XML, используемые в ADO.NET избавлены также от необходимости конвертации данных в рекордсете для получения типов данных, применимых в COM. Фактически, любые два компонента могут совместно использовать наборы данных XML при условии, что они оба используют ту же самую схему форматирования набора данных XML. ADO.NET обладает хорошей масштабируемостью, что требуется для совместно использующих данные ПП, работающих на основе Веб. Такие Веб-программы могут обслужить десятки, сотни, а то и тысячи пользователей. ADO.NET не применяет длительные блокировки баз данных или такие активные подключения, которые на долгое время монополизируют ресурсы сервера, являющимися (как правило) весьма ограниченными. Это позволяет увеличивать число пользователей без значительного увеличения утилизации ресурсов системы. 2.2. Сравнение ADO и ADO.NET ADO.NET - эволюционное развитие ADO. Самый простой путь быстрого понимания преимуществ ADO.NET состоит в сравнении с ADO. Термин ADO ADO.NET Резидентное Использует объект RecordSet, представление данных в который напоминает отдельную памяти таблицу. Использует объект DataSet, который может содержать одну или более таблиц, представленных объектами DataTable Отношения между множеством таблицами Требует, чтобы запрос JOIN транслировал данные из объединяемых таблиц базы данных в отдельную, результирующую таблицу. Поддерживает объект DataRelation, чтобы сопоставить строки в одном объекте DataTable со строками в другом объекте DataTable. Последовательное сканирование данных DataSet Использует навигационную парадигму для непоследовательного доступа к строкам в таблице. Следует за отношениями, чтобы передвигаться от строк в одной таблице к соответствующим строкам в другой таблице. Офлайновый доступ Использует RecordSet, для которого типична поддержка соединения, представленная объектом Connection. Вы подключаетесь к базе данных посредством запросов к OLE DB провайдеру. Подключается к базе данных посредством стандартизированных запросов к объекту DataSetCommand, который подключается к OLE DB провайдеру (а иногда, непосредственно к API, обеспечивающемуся СУБД). Программируемость Использует объект Connection, чтобы передать команды, адресуемые источникам данных, лежащим в основе конструкции данных. Использует строгий формат программирования характеристик XML. Данные включают в себя собственные описатели, поэтому, имена элементов кода отражают реальные проблемы, решаемые кодом. Лежащие в основе конструкций данных типы таблиц, строки, и таблицы, делают код проще для понимания и написания. Совместное использование офлайновый данных между уровнями или компонентами Использует COM для передачи RecordSet в офлайне. Поддерживаются только те типы данных, которыми располагает стандарт COM. Требуется преобразование типов, которое отнимает системные ресурсы. Передает DataSet с XML файлом. Формат XML не имеет никаких ограничений на типы данных и не требует никаких преобразований типов. Передача данных через межсетевые экраны (firewalls) Проблематично, потому что межсетевые экраны обычно конфигурируются так, чтобы предотвратить запросы системного уровня, которые используются в COM. Поддерживается, потому что объекты DataSet, используемые ADO.NET, передают XML, разработанный на основе HTML, который может проходить через межсетевые экраны. Блокировки баз данных и длительные, активные подключения к базам данных могут стать причиной коллизий Офлайновый доступ к базам данных без накладывания длительных блокировок или применения длительных, активных подключений к базе данных, существенно Инспектирование данных Масштабируемость и сильно ограничивать ресурсы базы данных. разгружает ресурсы базы данных. 3. Компоненты ADO.NET и объектная модель Есть две основные части ADO.NET. «DataSet» - который передаётся между компонентами ADO.NET и управляемые провайдеры (Managed Providers). 3.1. DataSet Объект DataSet является самым важным в ADO.NET. DataSet это простая резидентная база данных, которая обеспечивает непротиворечивую модель программирования независимо от источника данных. DataSet представляет собой законченный набор данных, включая связанные таблицы, ограничения, и отношения между таблицами. Модель объекта DataSet представлена ниже. DataSet |-RelationsCollection |-ExtendedProperties └TablesCollection └ DataTable |-Rows | └ DataRows |-DefaultView |-ChildRelations |-ParentRelations |-Constraints |-Columns | └ DataColumn └ ExtendedProperties └ PrimaryKeys Объект DataSet используют для управления содержанием набора данных. Методы и объекты в DataSet совместимы с реляционной моделью баз данных. 3.1.1. Объект TablesCollection ADO.NET DataSet представляет собой совокупность одной или более таблиц, представленных объектами DataTable. Объект TablesCollection содержит все объекты DataTable, имеющиеся в DataSet. DataTable определяется System.Data и представляет отдельную таблицу memory-resident данных, которая содержит совокупность столбцов, представляемых объектом ColumnsCollection, который определяет схему и строки таблицы. DataTable содержит совокупность строк, представляемых объектом RowsCollection, который осуществляет выборку данных в таблице. Наряду с текущим состоянием, объект DataTable сохраняет своё первоначальное состояние и отслеживает все изменения, которые происходят с данными. DataSet может сохранять и перезагружать представляющие контент данные посредством XML. 3.1.2. Объект RelationsCollection Типичный набор данных (DataSet) содержит отношения, определяемые объектом RelationsCollection. Отношения, представляемые объектом DataRelation, сопоставляют строки в одной таблице, строкам в другой таблице данных. Это работает аналогично отношениям foreign-key в реляционной базе данных. DataRelation идентифицирует соответствие столбцов в двух таблицах DataSet. Отношения допускают передвижения от одной таблицы к другой в пределах DataSet. Основные элементы DataRelation, это: имя отношений, сами связываемые две таблицы, а также первичные ключи (primary key) и foreign key столбцы в таблицах. Отношения могут строится по более чем одному столбцу в таблице, по массиву объектов DataColumn для primary и foreign keys. Когда DataRelation создан, ADO.NET проверяет возможность установления отношений. ADO.NET добавляет отношения к RelationsCollection лишь единожды, что позволяет избежать появления последующих изменений, которые могли негативно воздействовать на заданные отношения. 3.1.3. ExtendedProperties Объект ExtendedProperties содержит введённую информацию о пользователе, пароли или о времени, когда данные объекта DataSet должны быть обновлены. 3.2. Managed providers Managed provider устанавливает подключения между DataSet и источниками данных, например, базой данных SQL Server. Managed providers состоит из набора интерфейсов, которые осуществляют доступ к источнику данных. Существует три исполняемых компонента: - Подключение (Connection), команды, и параметры обеспечивают интерфейс между источником данных и объектом DataSet. Интерфейс DataSetCommand определяет столбец и отображения таблицы, а также загружает DataSet. - Поток данных (data stream) обеспечивает высоко производительный (high-performance), forward-only доступ к данным. IdataReader читает forward-only поток записей из источника данных. - Объекты слоя Low-level соединяются с базой данных и выполняют определенные системные команды базы данных. Раньше, чаще всего использовалась двухъярусная архитектура обработки данных, которая основывалась на подключении. В настоящее время, поскольку обработка данных все более тяготеет к многоярусной архитектуре, программисты переключают своё внимание на офлайновые подходы, обеспечивающие лучшую масштабируемость для прикладных программ. Одним из самых важных компонент ADO.NET, обеспечивающих современные требования, является DataSetCommand. Он загружает DataSet и обеспечивает мост между объектом DataSet и его исходным источником данных для целей обработки и сохранения информации. Осуществляется это посредством вызовов соответствующих SQL команд на источнике данных. Объектная модель для Managed provider, отображающая уровни между DataSet и источником данных, представлена ниже. DataSetCommand |-SelectCommand |-InsertCommand |-UpdateCommand |-DeleteCommand └ TableMappings └ TableMapping └ ColumnMappings └ ColumnMapping Command Exception └ ADO Errors |-Connection | └ Properties DataReader |-Parameters | └ Parameter └ Properties └ Property Как Вы видите, объект DataSetCommand определяет четыре базовые метода. Эти методы/команды обеспечивают create, read, update и delete функции для определенного DataTable в пределах DataSet. Managed provider использует собственный OLEDB через COM Interop, чтобы получить доступу к данным. OLEDB - провайдер поддерживает, как ручные, так и автоматические транзакции. Для автоматических транзакций, OLEDB провайдер сам включится в транзакцию и будет получать необходимые детали об исполнении операций от Component Services Context. 3.3. Microsoft ADO.NET managed providers Микрософт выпускает два managed providers для ADO.NET. SQL managed provider. Является посредником между таблицей в пределах DataSet и таблицей или представлением базы данных SQL Server (версия 7.0 и выше). Объект SQLDataSetCommand является посредником связи между таблицей DataSet и таблицей или представлением сервера баз данных. ADO managed provider. Является посредником связи между таблицей в пределах DataSet и таблицей или представлением в любом источнике данных, для которого имеется провайдер OLEDB. Объект ADODataSetCommand поддерживает связь между таблицей DataSet и таблицей или представлением в источнике данных. Оба они поддерживают подключение, команды и параметры для соответствующих источников данных, а также инструментарий класса DataSetCommand, который обеспечивает уровень отображения между источником данных и DataSet. Также, они поддерживают IDataReader, который предоставляет средства high-performance доступа к данным. Managed provider использует собственный OLEDB через COM Interop, чтобы получить доступу к данным. OLEDB - провайдер поддерживает, как ручные, так и автоматические транзакции. Для автоматических транзакций, OLEDB провайдер сам включится в транзакцию и будет получать необходимые детали об исполнении операций от Component Services Context. ADO.NET SQL Server managed provider использует специальный протокол, называемый табличным потоком данных (tabular data stream), с помощью которого осуществляется связь с SQL Server. При этом OLEDB, ADO или ODBC не используются. Всё управление осуществляется кодом. 3.4. Поддерживаемые базы данных и OLE DB провайдеры ADO managed provider проектировался, чтобы работать с большинством OLEDB провайдеров. Следующие провайдеры были проверены и работают с ADO .NET: Driver Provider SQLOLEDB SQL OLE DB Provider MSDAORA Oracle OLE DB Provider JOLT Jet OLE DB Provider MSDASQL/SQLServer ODBC SQL Server ODBC Driver via OLE DB for ODBC Provider MSDASQL/Jet ODBC Jet ODBC Driver via OLE DB Provider for ODBC Provider Примечание: SQL managed provider поддерживает SQL Server версии 7.0 и выше. MSDASQL/Oracle ODBC DRIVER (Oracle ODBC драйвер через OLE провайдера базы данных для ODBC) в настоящее время ADO.NET не поддерживается. 4. Использование Managed provider Команды, подключение, и DataReader представляют основные элементы модели ADO.NET. 4.1. Управление подключениями Чтобы соединяться с источником данных, используйте любой из двух объектов Connection, входящий в состав .NET Framework, и который поддерживается Microsoft: SQLConnection или ADOConnection. SQLConnection осуществляет подключение к базам данных Microsoft SQL Server. ADOConnection позволяет устанавливать подключение через провайдера OLE DB. Чтобы использовать managed providers, которые идут вместе с .NET Framework, Вы должны включить следующие пространства имён: - SQL Managed Provider – System.Data.SQL - ADO Managed Provider – System.Data.ADO Следующий код демонстрирует создание и открытие подключения к базе данных SQL Server: SQLConnection [VB] Dim connectionString As String = _"server=localhost;uid=sa;pwd=;database=northwind" Dim myConnection As SQLConnection = New SQLConnection(connectionString) myConnection.Open() [C#] String connectionString = "server=localhost;uid=sa;pwd=;database=northwind"; SQLConnection myConnection = new SQLConnection(connectionString); myConnection.Open(); ADOConnection [VB] Dim connectionString As String = "Provider= SQLOLEDB.1; Data " & _ "Source=localhost; uid=sa; pwd=; Initial Catalog=northwind;" Dim myConnection As ADOConnection = New ADOConnection(connectionString) myConnection.Open() [C#] String connectionString = "Provider= SQLOLEDB.1; Data Source=localhost; uid=sa; pwd=; Initial Catalog=northwind;"; ADOConnection myConnection = new ADOConnection(connectionString); myConnection.Open(); Объекты ADO и SQL Connection обеспечивают многие из свойств, которые Вы привыкли использовать в ADO. 4.2. Формат строки подключения – ADOConnection Для ADO managed provider, формат строки подключения идентичен формату строки подключения, используемому в ADO. 4.3. Формат строки подключения – SQLConnection SQL managed provider поддерживает формат строки подключения, который подобен формату строки подключения ADO. Для определения допустимых имён и значений, см. свойства SQLConnection.ConnectionString в справочниках .NET Framework SDK. 4.4. Команды Обеспечив подключение, Вы должны затем иметь возможность выполнять инструкции, предназначенные базе данных. Самый простой и наиболее прямой способ использовать объекты ADO и SQL Command. Следующий код демонстрирует как это делается: ADOCommand [VB] Dim SQlStmt As String = "SELECT * FROM Customers" Dim myCommand As ADOCommand = New ADOCommand(SQLStmt, myConnection) [C#] String SQLStmt = " SELECT * FROM Customers"; ADOCommand myCommand = new ADOCommand(SQLStmt, myConnection); SQLCommand [VB] Dim SQlStmt As String = "SELECT * FROM Customers" Dim myCommand As SQLCommand = New SQLCommand(SQLStmt, myConnection) [C#] String SQLStmt = " SELECT * FROM Customers"; SQLCommand myCommand = new SQLCommand(SQLStmt, myConnection); Дополнительный объект должен быть создан до запроса метода команды Execute с объектом в качестве параметра. ADO [VB] Dim myReader As ADODataReader = Nothing myCommand.Execute(myReader) [C#] ADODataReader myReader = null; myCommand.Execute(out myReader); SQL [VB] Dim myReader As SQLDataReader = Nothing myCommand.Execute(myReader) [C#] SQLDataReader myReader = null; myCommand.Execute(out myReader); Пример объявляет переменную, которая будет принадлежать иллюстрируемому SQLDataReader, возвращенный как параметр метода execute последней команды. 4.5. DataReader Когда обрабатывается большое количество данных, отвлечение на это памяти может стать проблемой или узким местом производительности. Например, чтение 10000 строк из базы данных заставляет DataTable распределять и удерживать память для всех 10000 строк и на весь срок жизни таблицы. Если 1000 пользователей обратятся с таким запросом к одному серверу и в одно и то же время, использование памяти может стать проблемой. Также, если Вы не уверены в правильности или достоверности результатов выполненного запроса, и Вам необходимо предусмотреть в коде циклы, в рамках которых будут выполняться итерации, оперирующие потоком, DataTable тоже не будет идеальным решением, поскольку в каждой итерации будут загружаться все строки. Чтобы решить эту проблему, используйте DataReader, как read-only, forward-only поток, возвращаемый из базы данных. Это позволит Вам держать в памяти только одну строку. Вы пройдёте поток через возвращаемый объект DataReader достаточно простым способом: [VB] While myReader.Read ' Выполняйте необходимые действия с текущей строкой End While [C#] While (myReader.Read()) { //Выполняйте необходимые действия с текущей строкой } DataReader также поддерживает, ряд Get методов, которые позволяют обращаться к значениям полей, таким, как время. Например: GetDataTime, GetDouble, GetGuid, GetInt32, GetStream. 4.6. Использование хранимых процедур в команде Когда Вам нужно возвратить только одну строку, вероятно, самый быстрый путь использовать хранимую процедуру с выходными параметрами. При использовании Microsoft SQL Server, или других баз данных, имеющих процедуры, параметры могут использоваться, чтобы отобрать единственную строку. Использование параметров работает также как в ADO. Вы можете передавать строку в команде, или использовать совокупность параметров. При использовании параметров с SQLCommand, имена параметров, добавленных к совокупности параметров команды, должны соответствовать именам маркеров параметра в хранимой процедуре. SQL managed provider обрабатывает их как «named» параметры и будет искать соответствующий маркер. [VB] Dim myConnection As SQLConnection = New SQLConnection _ ("server=delphi;uid=sa;pwd=;database=northwind") Dim myCommand As SQLCOmmand = New SQLCommand _ ("GetCustomerListbyState 'WA'", myConnection) myCommand.CommandType = CommandType.StoredProcedure Try myConnection.Open() myCommand.Execute(myReader) While myReader.Read Console.WriteLine(myReader("CompanyName").ToString)) End While Catch e As Exception Console.WriteLine(e.ToString) Finally myReader.Close() myConnection.Close() End Try [C#] SQLConnection myConnection = new SQLConnection("server=delphi;uid=sa;pwd=;database=northwind"); SQLCommand myCommand = new SQLCommand("GetCustomerListbyState 'WA'", myConnection); myCommand.CommandType = CommandType.StoredProcedure; try { myConnection.Open(); myCommand.Execute(out myReader); while (myReader.Read()) { Console.WriteLine(myReader["CompanyName"].ToString()); } } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { myReader.Close(); myConnection.Close(); } При использовании следующего кода, будет создана команда, которая выполняет вызов хранимой процедуры по имени «GetCompanyName». Эта хранимая процедура объявлена в SQL Server как: CREATE PROCEDURE GetCompanyName @CustomerID nvarchar(5), @CompanyName nvarchar(40) output as Select @CompanyName = CompanyName from Customers where CustomerID = @CustomerID Чтобы выполнять эту хранимую процедуру, выполните следующие шаги: (1) Создайте команду. (2) Установите тип команды как Stored procedure. (3) Создайте и установите параметры. SQL [VB] Dim myConnection As SQLConnection = new _ SQLConnection("server=delphi;uid=sa;pwd=;database=northwind") Dim myCommand As SQLCommand = New SQLCommand("GetCompanyName", myConnection) myCommand.CommandType = CommandType.StoredProcedure Dim workPara As SQLParameter = Nothing workParam = myCommand.Parameters.Add (New SQLParameter("@CustomerID", SQLDataType.NChar, 5)) workPara.Direction = ParameterDirection.Input workParam.Value = "ALFKI" workParam.myCommand.Parameters.Add (New SQLParameter("@CompanyName", SQLDataType.NChar, 40)) workParam.Direction = ParameterDirection.Output Try myConnection.Open() myConnection.Execute() Console.WriteLine("CompanyName = " & _ myCommand.Parameters("@CompanyName").Value Catch e As Exception Console.WriteLine(e.ToString) Finally myConnection.Close() End Try [C#] SQLConnection myConnection = new SQLConnection("server=delphi;uid=sa;pwd=;database=northwind"); SQLCommand myCommand = new SQLCommand("GetCompanyName", myConnection); myCommand.CommandType = CommandType.StoredProcedure; SQLParameter workParam = null; workParam = myCommand.Parameters.Add(new SQLParameter("@CustomerID", SQLDataType.NChar, 5)); workParam.Direction = ParameterDirection.Input; workParam.Value = "ALFKI"; workParam = myCommand.Parameters.Add(new SQLParameter("@CompanyName", SQLDataType.NChar, 40)); workParam.Direction = ParameterDirection.Output; try { myConnection.Open(); myCommand.Execute(); Console.WriteLine("CompayName= " + myCommand.Parameters["@CompanyName"].Value); } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { myConnection.Close(); } ADO [VB] Dim myCommand As ADOCommand = New ADOCommand("GetCompanyName", myConnection) myCommand.CommandType = CommandType.StoredProcedure Dim workPara As ADOParameter = Nothing workParam = myCommand.Parameters.Add("@CustomerID", SQLDataType.NChar, 5) workPara.Direction = ParameterDirection.Input workParam.Value = "ALFKI" workParam.myCommand.Parameters.Add("@CompanyName", SQLDataType.NChar, 40) workParam.Direction = ParameterDirection.Output Try myConnection.Open() myConnection.Execute() Console.WriteLine("CompanyName = " & _ myCommand.Parameters("@CompanyName").Value Catch e As Exception Console.WriteLine(e.ToString) Finally myConnection.Close() End Try [C#] ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection); myCommand.CommandType = CommandType.StoredProcedure; ADOParameter workParam = null; workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5); workParam.Direction = ParameterDirection.Input; workParam.Value = "ALFKI" workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40); workParam.Direction = ParameterDirection.Output; try { myConnection.Open(); myCommand.Execute(); Console.WriteLine("CompayName= " + myCommand.Parameters["@CompanyName"].Value); } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { myConnection.Close(); } Создайте команду, как уже писалось выше, для организации Вашего подключения. Установите с помощью конструктора класса в свойстве команды commandtext - имя хранимой процедуры. ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection); myCommand.CommandType = CommandType.StoredProcedure; Другой вариант состоит в том, чтобы просто создать команду, затем назначать подключение и commandtext через их вспомогательные свойства. ADOCommand myCommand = new ADOCommand(); myCommand.ActiveConnection = myConnection; myCommand.CommandText = "GetCompanyName"; myCommand.CommandType = CommandType.StoredProcedure; Затем, создайте два параметра объекта, и добавьте их к совокупности параметров команды. SQL SQLParameter workParam = null; workParam = myCommand.Parameters.Add(new SQLParameter("@CustomerID", SQLDataType.NChar, 5)); workParam.Direction = ParameterDirection.Input; workParam.Value = "ALFKI"; workParam = myCommand.Parameters.Add(new SQLParameter("@CompanyName", SQLDataType.NChar, 40)); workParam.Direction = ParameterDirection.Output; ADO workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5); workParam.Direction = ParameterDirection.Input; workParam.Value = "ALFKI"; workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40); workParam.Direction = ParameterDirection.Output; Здесь, Вы делаете три вещи: создаёте и добавляете параметры, устанавливаете назначение параметра, и устанавливаете значение для входного параметра. Вы обратили внимание, что есть различие в вариантах исполнении для SQLCommand и ADOCommand. Это потому, что просто модели не были точно подобны в этот момент. Затем, откройте подключение, и выполните команду. После этого, просто найдите выходной параметр, и впишите его с консоли. [VB] myConnection.Open() myCommand.Execute() Console.WriteLine("CompanyName = " & _ myCommand.Parameters("@CompanyName").Value) [C#] myConnection.Open(); myCommand.Execute(); Console.WriteLine("CompanyName= " + myCommand.Parameters["@CompanyName"].Value) Последнее, важное замечание. Обратите внимание на последний блок. Последний блок инструкции, это уловка, которая всегда гарантированно будет выполнена, независимо от того, будет ли получено исключение. [VB] Finally myConnection.Close [C#] finally { myConnection.Close(); } Работая с подключением и DataReaders, Вы всегда должны использовать этот последний, завершающий соединение блок, чтобы гарантировать, что если что – ни будь потерпит неудачу, соединение всё равно будут закрыты. Обратите внимание, что в этом примере мы использовали перегрузку Execute метода команды, которая не возвращает DataReader. Если бы DataReader возвращался, и имелись выходные параметры, они не были бы заполнены, пока не закрыт DataReader. 4.7. Создание простых DataSetCommand Основная функция DataSetCommand состоит в получении данных из источника и загрузка их в DataTable из состава DataSet. Для этого, DataSetCommand необходимо всего две вещи: организация подключения (connection) и выполнение команды Select. В следующем примере мы построим ADODataSetCommand, содержащую текст команды Select и строку OLEDB подключения: [VB] Dim workDSCMD As ADODataSetCommand WorkDSCMD = New ADODataSetCommand _ ("Select * from Customers", _ "Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _ "Data Source=MyServer;User ID=sa;") [C#] ADODataSetCommand workDSCMD = new ADODataSetCommand("Select * from Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind; Data Source=MyServer;User ID=sa;"); Представленный выше код выполняет следующее: 1. Создает новый объект ADOConnection, использующий строку подключения. 2. Создает новый объект ADOCommand, использующий инструкцию Select и существующее подключение. 3. Соотносит новый объект ADOCommand с инструкцией Select на workDSCMD. Другая возможность получения необходимого результата могла состоять в том, чтобы создать экземпляры каждого из объектов и затем настроить DataSetCommand через её свойства. [VB] Dim workDSCMD As ADODataSetCommand Dim workCMD As ADOCommand Dim workConn As ADOConnection workConn = New ADOConnection _ ("Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _ "Data Source=MyServer;User ID=sa;") workCMD = New ADOCommand("SELECT * FROM Customers", workConn) workDSCMD = new ADODataSetCommand() workDSCMD.SelectCommand = workCMD [C#] ADODataSetCommand workDSCMD; ADOCommand workCMD; ADOConnection workConn; workConn = New ADOConnection("Provider=SQLOLEDB.1;Initial Catalog=Northwind; Data Source=MyServer;User ID=sa;"); workCMD = New ADOCommand("SELECT * FROM Customers", workConn); workDSCMD = New ADODataSetCommand() workDSCMD.SelectCommand = workCMD Эта DataSetCommand может использоваться, чтобы заполнить DataTable принадлежащую DataSet. Как ADO, так и SQL DataSetCommand объекты используют метод FillDataSet, имеющий два параметра: экземпляр DataSet и имя таблицы источника. Экземпляр DataSet представляет DataSet для заполнения, а имя таблицы источника идентифицирует таблицу внутри DataSet. В следующем примере, создаётся новый экземпляр DataSet, который используется в качестве аргумента при вызове метода FillDataSet. [VB] Dim workDS As DataSet = New DataSet("myDataSet") Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _ (SELECT * FROM Customers", _ "Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _ "Data Source=MyServer;User ID=sa;") workDSCMD.FillDataSet(workDS, "Customers") [C#] DataSet workDS = new DataSet("mydataset"); ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind; Data Source=MyServer;User ID=sa;"); workDSCMD.FillDataSet(workDS, "Customers"); Результатом вызова метода FillDataSet станет заполнение таблицы "Customers" в DataSet. Но Вы можете спросить: «Почему таблица Customers вошла в DataSet, хотя она фактически не была создана до выполнения метода FillDataSet?». Как для SQL, так и для ADO DataSetCommand объектов, справедливо утверждение, что если Вы не изменяете значение по умолчанию, любая схема (table/column/primary key definitions) из источника данных, которая не существуют внутри DataSet, будет создан автоматически. Так, в представленном выше примере, ни одна из схем таблицы Customers предварительно не существовала. Поэтому, если Вы создадите таблицу Customers до выполнения метода FillDataSet, DataSetCommand просто заполнит существующую таблицу. Поскольку нет никакой физической связи между DataSet и DataSetCommand, DataSetCommand может использоваться, чтобы заполнить любое число экземпляров DataSet. Просто создайте экземпляр DataSet, и передайте эго как аргумент в методе FillDataSet. 4.8. Обновление базы данных с помощью DataSetCommand и DataSet ADO и SQL DataSetCommand объекты обеспечивают метод Update, позволяющий переносить изменения в DataSet назад в базу данных. Метод Update, подобно методу FillDataSet, использует экземпляр DataSet и имя таблицы источника. Экземпляр DataSet – это DataSet, который содержит изменения, внесённые Вами, в то время как исходное имя таблицы идентифицирует DataTable, в которой содержатся изменения. Когда вызывается метод FillDataSet, осуществляется анализ изменений, которые были сделаны, и выполняется соответствующая команда. DataSet принимает изменения в том порядке, в каком они представлены. Так, если Вы хотите получить все модификации, сделанные до удаления, Вы будете должны применить для них фильтр, причём перед применением над ними метода Update. Следующий код показывает, как это выполнить: [VB] Dim workDS As DataSet = New DataSet("myDataSet") Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _ ("SELECT * FROM Customers", _ "Provider=SQLOLEDB.1;Initial Catalog=Northwind;" & _ "Data Source=MyServer;User ID=sa;") workDSCMD.FillDataSet(workDS, "Customers") ' Make some changes to the customer data in the DataSet. workDSCMD.Update(workDS, "Customers") [C#] DataSet workDS = new DataSet("mydataset"); ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM Customers", "Provider=SQLOLEDB.1;Initial Catalog=Northwind; Data Source=MyServer;User ID=sa;"); workDSCMD.FillDataSet(workDS, "Customers"); // Make some changes to the customer data in the DataSet. workDSCMD.Update(workDS, "Customers"); Пожалуйста помните, что "removing" отличается от "deleting". Если Вы фактически использовали "remove" для строки из выборки, DataSetCommand не будет видеть эту строку, когда Вы применяете метод Update для DataSet. Таким образом, не будет выполняться никакая команда для этой строки. Однако, если строка отмечена как "deleted", команда удаления будет исполнена. 4.9. Определение отображений таблиц и столбцов Вы можете вносить изменения в таблицу с целью её заполнения или переноса изменений и схемы в DataSet, для чего понадобится задать отображение таблицы или столбца. Такое отображение таблиц (master mapping) обеспечивает взаимосвязь между данными, возвращёнными из таблицы в базе данных, и DataTable внутри DataSet. Следующий пример показывает, как создать отображение таблицы, используя Table как исходное имя таблицы и MyAuthors как имя DataTable. [VB] workDSCMD.TableMappings.Add("Table", "MyAuthors") [C#] workDSCMD.TableMappings.Add("Table", "MyAuthors"); Это отображение таблицы создает виртуальную связь (ссылку) между таблицей в базе данных (указанной в инструкции Select и заданной по умолчанию с именем Table) и таблицы в DataSet, называемой MyAuthors. Отображение таблицы дает Вам возможность использовать имена столбцов в DataSet, которые теперь могут отличаться от тех, которые используются в базе данных. С помощью отображений, столбцы DataSet будут сопоставляться соответствующим столбцам базы, в то время, когда таблица будет модифицироваться. Следующий пример показывает, как отобразить столбцы таблицы Authors базы данных Pubs к набору более понятных и простых имен в DataSet. [VB] With workDSCMD.TableMappings.Item(0).ColumnMappings .Add("au_id", "AuthorID") .Add("au_lname", "lastname") .Add("au_fname", "firstname") .Add("phone", "phone") .Add("address", "address") .Add("city", "city") .Add("state", "state") .Add("zip", "zip") .Add("contract", "contract") End With [C#] workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( "au_id", "AuthorID"); "au_lname", "lastname"); "au_fname", "firstname"); "phone", "phone"); "address", "address"); "city", "city"); "state", "state"); "zip", "zip"); "contract", "contract"); Для ADO и SQL DataSetCommand объектов, когда методы Update или FillDataSet вызываются без указания таблицы источника, DataSetCommand ищет отображение к таблице источнику по имени Table. В таком случае, имя таблицы из DataSet запрашивается у отображения и далее используется это имя, причём, если таблица с таким именем не находится из числа принадлежащих объекту DataSet, к совокупности таблиц добавляется новая таблица, для которой используя указанное имя. [VB] Dim myDS As DataSet = New DataSet() Dim myConnection As ADOConnection = New ADOConnection _ ("Provider=SQLOLEDB;Data Source=Delphi;Initial Catalog=pubs;user id=sa") Dim workDSCMD As ADODataSetCommand = New ADODataSetCommand _ ("SELECT * FROM Authors", myConnection) workDSCMD.TableMappings.Add("Table", "Authors") With workDSCMD.TableMappings(0).ColumnMappings .Add("au_id", "AuthorID") .Add("au_lname", "lastname") .Add("au_fname", "firstname") .Add("phone", "phone") .Add("address", "address") .Add("city", "city") .Add("state", "state") .Add("zip", "zip") .Add("contract", "contract") End With workDSCMD.FillDataSet(myDS) [C#] DataSet myDS = new DataSet(); ADOConnection myConnection = new ADOConnection("Provider=SQLOLEDB;Data Source=Delphi;Initial Catalog=pubs;user id=sa"); ADODataSetCommand workDSCMD = new ADODataSetCommand("SELECT * FROM Authors", myConnection); workDSCMD.TableMappings.Add("Table", "Authors"); workDSCMD.TableMappings[0].ColumnMappings.Add( "au_id", "AuthorID"); workDSCMD.TableMappings[0].ColumnMappings.Add( "au_lname", "lastname"); workDSCMD.TableMappings[0].ColumnMappings.Add( "au_fname", "firstname"); workDSCMD.TableMappings[0].ColumnMappings.Add( "phone", "phone"); workDSCMD.TableMappings[0].ColumnMappings.Add( "address", "address"); workDSCMD.TableMappings[0].ColumnMappings.Add( "city", "city"); workDSCMD.TableMappings[0].ColumnMappings.Add( "state", "state"); workDSCMD.TableMappings[0].ColumnMappings.Add( "zip", "zip"); workDSCMD.TableMappings[0].ColumnMappings.Add( "contract", "contract"); workDSCMD.FillDataSet(myDS); В представленном выше примере, создаётся отображение таблицы, которая становится заданной по умолчанию с именем Table. Когда Вы вызываете методы Update или FillDataSet без имени таблицы источника, DataSetCommand будет использовать это отображение. В более широком смысле, Вам может потребоваться, чтобы тот же самый DataSetCommand поддерживал загрузку разных таблиц и с различными отображениями. Достигается это простым добавлением дополнительных отображений таблицы. В представленном ниже примере, создаётся отображение таблицы источника с именами таблиц DataSet: bob и King. [VB] workDSCMD.TableMappings.Add("bob", "King") With workDSCMD.TableMappings(0).ColumnMappings .Add("au_id", "AuthorID") .Add("au_lname", "lastname") .Add("au_fname", "firstname") .Add("phone", "phone") .Add("address", "address") .Add("city", "city") .Add("state", "state") .Add("zip", "zip") .Add("contract", "contract") End With workDSCMD.FillDataSet(myDS, "bob") [C#] workDSCMD.TableMappings.Add("bob", "King"); workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.TableMappings[0].ColumnMappings.Add( workDSCMD.FillDataSet(myDS, "bob"); "au_id", "AuthorID"); "au_lname", "lastname"); "au_fname", "firstname"); "phone", "phone"); "address", "address"); "city", "city"); "state", "state"); "zip", "zip"); "contract", "contract"); Вызов метода FillDataSet, применяется к экземпляру DataSet и использует ссылки к таблице источника. DataSetCommand просматривает совокупность TableMappings, чтобы определить, существует ли отображение к заданному имени таблицы источника. Если это так, используется найденное отображение, чтобы построить и заполнить таблицу. Но если исходное имя таблицы не существует в совокупности, эта таблица сразу же будет создана в совокупности TableMappings. В представленном выше примере, создано отображение, которое заставит записи, возвращаемые командой Select отображаться в таблице King. 4.10. Mapping/Changing отображение столбцов во время исполнения Даже притом, что TableMappings совокупность обеспечивает достаточно сильный механизм для отображения таблиц и столбцов в базе данных в таблицы и столбцы DataSet, может возникнуть необходимость динамического изменения этого отображения во время исполнения. Поддержкой такой возможности занимается SchemaMapping. Вы можете обращаться к внутренним структурам отображения DataSetCommand и делать там необходимые изменения. Например, хранимая процедура, которая возвращает столбец без имени, выполняя следующую инструкцию Select: SELECT @@IDENTITY Обычно, это породило бы ошибку при попытке загрузить результат в DataSet, так как DataSet требует, чтобы все столбцы имели имена. Чтобы отобразить столбец без имени в столбец DataTable, Вы можете использовать декларацию события SQLSchemaMapping для объекта SQLDataSetCommand (для события ADOSchemaMapping, это будет соответственно объект ADODataSetCommand) чтобы обойтись без имени столбца. Ниже приведенный пример анализирует событие SQLSchemaMapping, чтобы заменить поступающее, пустое имя столбца на «SomeUniqueName». [VB] Private Sub SchemaMappingEventHolder(ByVal sender As Object, ByVal e As _ SQLSchemaMappingEvent) Dim column As DataColumn = e.SchemaTable.Columns("DBCOLUMN_NAME") Dim row As DataRow For Each row in e.SchemaTable.Rows.All If row.RowState = DataRowState.Deleted Then row.RejectChanges() End If If row.IsNull(column) Or 0 = row(column).ToString Then column = e.SchemaTable.Columns("DataColumn") row(column) = New DataColumn("SomeUniqueName") End If Next End Sub [C#] private void SchemaMappingEventHandler(object sender, SQLSchemaMappingEvent e) { DataColumn column = e.SchemaTable.Columns["DBCOLUMN_NAME"]; foreach(DataRow row in e.SchemaTable.Rows.All) { if(DataRowState.Deleted == row.State) { row.RejectChanges(); } if (row.IsNull(column) || 0 == row[column].ToString()) { // unnamed column column = e.SchemaTable.Columns["DataColumn"]; row[column] = new DataColumn("SomeUniqueName"); } } } 4.11. Использование параметров DataSetCommands Для установки DataSetCommand, в целях использования параметризованного запроса или хранимой процедуры для метода Update, делайте то же самое, что Вы делали бы, чтобы использовать инструкции этого типа с Command Object. Единственное дополнение, которое Вы должны рассмотреть, это rowversion, который Вы будете использовать в каждом параметре. В представленном ниже примере, определяются параметризованные запросы, которые выполняют вставку, обновление, и удаление для ADODataSetCommand (именно поэтому маркеры параметров обозначены знаком «?»). string insertSQL = "INSERT INTO [Customers]([CustomerID], [CompanyName], [ContactName], [ContactTitle], [Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; string updateSQL = "UPDATE [Customers] SET [CustomerID] = ?, [CompanyName] = ?, [ContactName] = ?, [ContactTitle] = ?, [Address] = ?, [City] = ?, [Region] = ?, [PostalCode] = ?, [Country] = ?, [Phone] = ?, [Fax] = ? WHERE [CustomerID] = ? "; string deleteSQL = "DELETE FROM [Customers] WHERE [CustomerID] = ? "; Вы должны установить шесть элементов для каждого из параметров. Для трёх первых, используйте метод Parameters.Add или конструктор параметров. Ниже показано, как установить для имени параметра «CustomerID» из совокупности параметров - тип данных, и их размер: workParam = workCommand.Parameters.Add("CustomerID", ADODBType.WChar, 5); Для типов, определяющих свойства и выраженных целым числом, размер включить ненужно, или можно просто установить использование заданного по умолчанию размера. workParam = workCommand.Parameters.Add("FldItemID", ADODBType.Integer, 4); Также, для элемента должно быть установлено направление (Input – Output) параметра: workParam.Direction = ParameterDirection.Input; Направления параметра допускает следующие возможные варианты параметров настройки: Зарезервированное имя Направление параметра Input Входной параметр Output Выходной параметр InputOutput Возможны оба направления ReturnValue Параметр представлен, как возвращаемое значение Последние два элемента, которые должны быть установлены - SourceColumn и SourceVersion. SourceColumn сообщает DataSetCommand какой из столбцов в таблице будет обеспечивать её значение. Чтобы устанавливать его, назначьте имя столбца, как это поддерживается в таблице. workParam.SourceColumn = "CustomerID"; SourceVersion определяет, от какой версии DataRow - DataSetCommand должен передать значение. Параметры для этого из DataRowVersion перечислены в таблице: Зарезервировано Описание Current The current values Default The default values Original The original values Proposed A proposed value Заметьте, что в инструкции update (определенной выше) столбец CustomerID используется в двух местах (в update и в предложении where). При использовании этой инструкции, чтобы изменить первичное значение «ALFKI» на «AAAAA», Вы должны иметь возможность исполнить следующее действие: «Update строки, чей первичный ключ в настоящее время принимает значение ALFKI, а должно получится значение первичного ключа AAAAA» (Update the row whose primary key is currently ALFKI to have a primary key of AAAAA). Таким образом, в этом примере, Вы заставляете все параметры в update использовать DataRowVersion.Current. Но Вы должны устанавливать параметры предложения where, чтобы использовать DataRowVersion.Original. ADOParameter workParam = null; workParam = workCommand.Parameters.Add("CustomerID", ADODBType.WChar, 5); workParam.Direction = ParameterDirection.Input; workParam.SourceColumn = "CustomerID"; workParam.SourceVersion = DataRowVersion.Current; workParam = workCommand.Parameters.Add("CompanyName", ADODBType.VarWChar, 40); workParam.Direction = ParameterDirection.Input; workParam.SourceColumn = "CompanyName"; workParam.SourceVersion = DataRowVersion.Current; .. all the other parameters then workParam.SourceVersion = DataRowVersion.Current; workParam = workCommand.Parameters.Add("oldCustomerID", ADODBType.WChar, 5); workParam.Direction = ParameterDirection.Input; workParam.SourceColumn = "CustomerID"; workParam.SourceVersion = DataRowVersion.Original; 4.12. Параметры Input/Output и возвращаемые значения При использовании хранимых процедур, в дополнение к параметрам ввода и вывода, Вы можете иметь возвращаемые значения (return values). ADO.NET обрабатывает возвращаемые из хранимой процедуры значения точно так же, как входные значения. Следующая хранимая процедура получает customerID как входной параметр, возвращает companyname как выходной параметр, и имеет возвращаемое значение. CREATE PROCEDURE GetCompanyName @CustomerID nvarchar(5), @CompanyName nvarchar(40) output as Select @CompanyName = CompanyName from Customers where CustomerID = @CustomerIDGO Return 99 Представленный ниже пример показывает код, который вызывает хранимую процедуру GetCompanyName: [VB] Imports System Imports System.Data Imports System.Data.ADO Namespace ConsoleApplication1 Module Module1 Shared Sub Main() Console.WriteLine("Requires a CustomerID to be passed in as a argument") Console.WriteLine("example: OutParamsWithACommand ALFKI") ' Create a new Connection and DataSetCommand Dim myConnection As ADOConnection = New ADOConnection _ ("provider=SQLOLEDB;database=northwind;data source=delphi;user id=sa;") Dim myCommand As ADOCommand = New ADOCommand("GetCompanyName", myConnection) myCommand.CommandType = CommandType.StoredProcedure; Dim workParam As ADOParamater = Nothing workParam = myCommand.Parameters.Add("RETURN VALUE", ADODBType.Integer) workParam.Direction = ParameterDirection.ReturnValue workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5) workParam.Direction = ParameterDirection.Input workParam.Value = args(0) workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40) workParam.Direction = ParameterDirection.Output Try myConnection.Open() myCommand.Execute() Console.WriteLine("CompanyName= " & _ myCommand.Parameters("RETURN_VALUE").Value) Console.WriteLine("CompanyName= " & _ myCommand.Parameters("@CompanyName").Value) Catch e As Exception Console.WriteLine(e.ToString) Finally myConnection.Close End Sub End Module End Namespace [C#] using System; using System.Data; using System.Data.ADO; class ConsoleApplication { public static void Main(String[] args) { if (args.Length < 1 ) { Console.WriteLine("Requires a CustomerID to be passed in as a argument"); Console.WriteLine("example: OutParamsWithACommand ALFKI"); return 0; } // Create a new Connection and DataSetCommand ADOConnection myConnection = new ADOConnection("provider=SQLOLEDB;database=northwind;data source=delphi;user id=sa;"); ADOCommand myCommand = new ADOCommand("GetCompanyName", myConnection); myCommand.CommandType = CommandType.StoredProcedure; ADOParameter workParam = null; workParam = myCommand.Parameters.Add("RETURN_VALUE", ADODBType.Integer); workParam.Direction = ParameterDirection.ReturnValue; workParam = myCommand.Parameters.Add("@CustomerID", ADODBType.Char, 5); workParam.Direction = ParameterDirection.Input; workParam.Value = args[0]; workParam = myCommand.Parameters.Add("@CompanyName", ADODBType.Char, 40); workParam.Direction = ParameterDirection.Output; try { myConnection.Open(); myCommand.Execute(); Console.WriteLine("CompanyName= " + myCommand.Parameters["RETURN_VALUE"].Value); Console.WriteLine("CompanyName= " + myCommand.Parameters["@CompanyName"].Value); } catch(Exception e) { Console.WriteLine(e.ToString()); } finally { myConnection.Close(); } } } Единственное требование, для получения возвращаемое из хранимой процедуры значение необходимо указать направление первого параметра (в совокупности параметров), которое должно обозначать вывод (Output). 4.13. Объединённое подключение (Connection Pooling) Для разработки высокопроизводительных приложений, Вы должны использовать объединенное подключение. При использовании ADO Managed Provider, объединенное подключение не используется, т.к. ADO Managed Provider создаёт его автоматически. Для организации объединенного подключения SQL Managed Provider использует службу Windows 2000 Component Services, и Вы должны для этого следовать указанным ниже шагами: 1. Определите подкласс SQLConnection, используя код, представленный ниже. 2. Включите в код строки, которые организуют объединенное подключение. 3. Подключите Microsoft.ComServices.dll. 4. Произвольно конфигурируйте характеристики объединения компонент, используя пользовательский интерфейс Component Services. 4.13.1. Определение подкласса SQLConnection Следующий код определяет подкласс для класса SQLConnection. Используйте Guidgen.exe, чтобы получить новый Guid (FD5ABffd-D026-4684-8515-2BB5184E8991 в представленной ниже выборке). [ConstructionEnabled(default="Server=servername …"] // required here or in gui [ObjectPooling(true)] // required [Transaction(TransactionOption.Supported)] // optional public class myConnection : SQLPooledConnection { // class is empty unless adding SetComplete and SetAbort methods, e.g. // public void SetComplete() { ContextUtil.SetComplete(); } // public void SetAbort() { ContextUtil.SetAbort(); } } 4.13.2. Включение в код строк, которые организуют объединенное подключение. Класс объединённого подключения используется очень похоже на обычный SQLConnection, как это показано в следующем примере кода: IReader iReader; myConnection conn = new myConnection(); conn.Open SQLCommand cmd = new SQLCommand("SELECT * FROM Orders", conn); cmd.Execute(ref iReader); conn.Close(); Модель объединённого подключения та же самая, что и у не объединенных подключений. Однако, очень важно вызывать Close, при завершении объединённого подключения и возврате назад в объединение. 4.13.3. Подключение Microsoft.ComServices.dll COM+ Services DLL должен быть подключён, чтобы обеспечить поддержку объединения. 4.13.4. Конфигурирование объединения компонент Хотя объединенный объект подключения может быть жёстко конфигурирован, через установку атрибут во времени компиляции, часто желательно изменять поведение объекта после того, как приложение было развернуто. Чтобы это обеспечить, такие атрибуты, как строка подключения (также называемая строкой конструкции) и транзакционные ограничения могут быть установлены в Component Services из Administrative Tools. 4.13.5. Поддержка транзакций Одно из преимуществ механизма объединения это то, что обеспечивается поддержка управления поведением транзакционного механизма. SQL Managed provider определяет, имеет ли текущий контекст распределенную транзакцию, связанную с задачей, и если это так, обеспечит автоматическую организацию транзакции. Вы можете управлять поведением транзакционным механизмом объединенного подключения во времени компиляции с помощью TransactionAttribute или во время выполнения с помощью Component Services из Administrative Tools. Для получения большей информации о распределенных транзакциях, см. раздел, Working with Transactions. 4.14. Команды генерируемые автоматически Для простых модификаций, SQL и ADO Managed provider обеспечивают автоматическую генерацию команд (AutoGen). Если свойства команды delete, insert или update не установлены пользовательским, это механизм автоматически генерирует отсутствующие инструкции, чтобы создать полнофункциональный запрос. Автоматическая генерация команд вызывается, если для объекта DataSetCommand любое из указанных свойств не установлено. Для работы автоматической генерации, как минимум, для команды должно быть определено свойство SelectCommand. Механизм Авто автоматической генерации (AutoGen) команд генерирует другие типы команд (insert, update, и delete) когда они установлены в Null. Также требуется присутствие по крайней мере одного первичного ключа или уникального столбца. Если ничего из этого нет, будет получено исключение InvalidOperation, и команда не будет сгенерирована. Команда Select должна возвратить ключевой столбец, который создаётся во время авто-генерации, а не во время исполнения метода FillDataSet. Данные, заданные свойствами SelectCommand, определяют форму последующих автоматически сгенерированных команд insert, update и delete. В дополнение к использованию полученной информации о ключевом столбце, возвращенные имена таблицы и столбцов также используются логикой авто-генерации 4.14.1. Правила организации команды Update Автоматически сгенерированная команда update обновляет значения всех столбцов кроме столбцов non-updateable (например, в случае тождеств или выражений). Команда имеет следующий формат: UPDATE table SET pkCol1=@pkCol1Alias ... pkColN=@pkColNAlias Col1=@Col1Alias ... ColN=@ColNAlias WHERE pkCol1=@oldpkCol1Alias AND ... pkColN=@oldpkColNAlias Rules for Generating Delete Commands When auto-generated, the DELETE command takes the form: DELETE FROM table WHERE pkCol1=@oldpkCol1Alias AND ... pkColN=@oldpkColNAlias 4.14.2. Правила организации команды Insert При автоматической генерации, команда insert вставляет значения во всех столбцах, которые являются updateable (исключая столбцы identifies, expressions или timestamps). Формат команды будет следующий: INSERT INTO table ( Col1, ... ColN ) VALUES ( @Col1, ... @ColN ) 4.14.3. Ограничения логики механизма AutoGen Следующие ограничения относятся к автоматически генерируемым командам: Только одна таблица. Автоматически генерируемые команды не генерирует insert, update или delete команды для таблиц, которые имеют отношения с другими таблицами в пределах одного набора данных DataSet. AutoGen работает только для одной таблицы. Автоматически генерируемые команды полагаются на информацию первичного ключа, чтобы генерировать предложение «where» для различных инструкций. Этой информации будет недостаточно, когда используются объединённые (join) таблицы. При таком подходе, пользователь будет иметь однозначную информацию, для каких данных была исполнена инструкция update. Ограничения на таблицу и названия(имена) столбца Возможны сбои в логике AutoGen, если имена столбцов или имена таблиц будут содержать специальные символы (пробелы, точки, кавычки или другие не алфавитноцифровые символы) даже если они заключены в скобки. Поддерживаются только полностью квалифицированные названия таблицы, например, schema.owner.table. 4.15. Добавление и удаление событий SQL Managed provider Ниже перечислены события ADODataSetCommand класса. Для дополнительной информации, см. ссылку на ADODataSetCommand класс. Событие (Event) Описание SQLRowUpdating Будет начата операция update для строки (запрос к одному из методов update) SQLRowUpdated Завершение операции update для строки (запрос к одному из методов update) SchemaChanging Будут изменены установки DataSetCommand. Эти изменения могут затрагивать возвращаемые данные или отображение этих данных. SchemaChanged Завершение изменений установок DataSetCommand. Эти изменения могли затронуть возвращаемые данные или отображение этих данных. SchemaMapping Отображение таблицы источника на таблицу DataSet закончено. 4.15.1. Обвальные исключения, порождённые обработчиком событий Избегите обвальных исключений (throwing exceptions) при обработке методов в коде, вызывающем событие. Если исключение распространяется от обработчика до кода вызова, ни один ожидающий это событие процесс не будет уведомлен относительно этого события. Эта проблема скрыта внутри .NET Framework. 4.15.2. Семантика добавления и удаления событий Вы в любое время можете добавлять или удалять делегата из списка делегатов, получающих специальное событие. Это будет происходить непосредственно в течение выполнения функции отзыва. Отзыв может быть добавлен в список любое количество раз. Если он добавлен больше чем единожды, он будет вызываться больше чем один раз. Не будет считаться ошибкой, и не будет выполнено никаких действий, если Вы попытаетесь разрегистрировать делегата, который не был добавлен в список. Всё это определяется поведением Delegate.Remove. Все выполняемые SQL Managed provider события являются многоадресными (multicast). 4.15.3. Сравнение делегатов при добавление и удаление событий Эквивалентность делегатов при добавлении и удалении событий полностью зависит от выполнения System.Delegate. При добавлении и удалении делегатов к событиям, нет необходимости поддержать ссылку делегата. Следующий код демонстрирует это: conn.InfoMessageEvent += new InfoMessageEventHandler(myStaticFunc); ... ... conn.InfoMessageEvent -= new InfoMessageEventHandler(myStaticFunc); Два делегата рассматриваются равными даже в том случае, если они представляют два различных экземпляра. Равенство/эквивалентность двух делегатов А и B достигается: - если скрытая (encapsulated) функция является статической, функции для делегатов А и B равны; - если скрытые функции (например, методы), имена методов и экземпляры одинаковы для делегатов А и B; - перечисленные скрытые функции, содержащиеся в обоих делегатах эквивалентны. 5. Использование DataSet Чтобы появилась возможность использования DataSet, Вы должны включить System.Data: using System.Data Есть два основные способа использования объекта DataSet: в рамках существующей системы управления базой данных (database management system) или создание нового DataSet программным путём. - Используйте существующую систему управления базы данных, например SQL Server, для заполнения таблицы в DataSet. В этом методе, используйте одну SQLDataSetCommand для таблицы, чтобы заполнить объект DataTable данными. Однако, Вы должны создать объекты DataRelation между всеми таблицами. - Вы можете создавать DataSet программным путём, добавлять объекты DataTable к нему и затем создавать объекты DataRelation, чтобы организовать связи для каждой таблицы. Используйте программный путь, когда Вам необходимо создать DataSet в процессе выполнения программы, чтобы увидеть на экране данные, сгенерированные приложением. В типовом решении, данные запрашиваются приложением клиента посредством URL. Когда запрос достигает соответствующего компонента промежуточного слоя, DataSet будет создан с помощью специального адаптера. После этого, DataSet конвертируется в XML документ для транспортировки назад к инициатору запроса. В клиентском приложении данные отображаются с использованием комбинации средств управления, например, DataGrid. Пользователь может добавлять, удалять, или редактировать данные. После окончания работы с данными пользователем, DataSet снова конвертируется в документ XML для передачи назад к компоненту сервера. Компонент промежуточного слоя конвертирует документ XML назад в DataSet, и использует адаптер, чтобы попытаться применить изменения данные в системе управления базой данных. Если будут обнаружены коллизии данных, компонент сервера может урегулировать их, используя встроенные бизнес-правила. Урегулированный DataSet будет возвращён назад клиенту. После этого, DataSet может быть применён в существующий DataSet, и пользователь может продолжить с ним работу, причём, DataSet после этого будет отражать самую последнюю версию. Потенциальная потеря точности для десятичного типа данных (Decimal) DataSet хранит данные, используя типы, имеющиеся у COM+. Для большинства прикладных программ, они обеспечивают удобное представление информации источника данных. Однако, это представление может причинять проблему, когда тип в источнике данных - SQL decimal. Тип COM + decimal позволяет иметь максимум 28 знаков цифр, в то время как тип SQL decimal позволяет иметь 38 знаков цифр. В настоящее время, FillDataSet метод вызывает исключение, если он сталкивается с правильным типом SQL decimal, но большим, чем 28 знаковых цифр. Пока нет никакого способа получить SQL decimal больших чем 28 знаковых цифр в объекте DataSet. Если ваше приложение требует большей точности, используйте объект SQLDataReader и запрос метода GetSQLNumeric, что бы получить необходимое значение SQL decimal. 5.1. Использование DataSet с существующими данными Рассмотрим общепринятые способы использования DataSet с существующими данными: 1. Создайте и заполните каждую таблицу в DataSet данными системы управления базой данных, используя SQLDataSetCommand или ADODataSetCommand. См. Создание простой DataSetCommand. 2. Измените данные в индивидуальных объектах DataTable, добавляя, модифицируя или удаляя объекты DataRow. 3. Вызовите метод GetChanges для создания второго DataSet, который будет показывать только изменения в данных. Метод требует один параметр, DataRowState для изменений, в которых Вы заинтересованы. Приведённый ниже пример использует метод возвращающий DataSet, содержащий только изменённые строки: [VB] Dim changedDataSet As DataSet changedDataSet = ds.GetChanges(DataRowState.Modified) [C#] System.Data.DataSet changedDataSet; changedDataSet = ds.GetChanges(DataRowState.Modified); 4. Проверьте ошибки во втором DataSet, исследуя его свойство HasErrors, посредством которого можно узнать, имеет ли ошибки какая-либо из таблиц в наборе. 5. Если ошибки присутствуют, проверьте свойство HasErrors каждой DataTable. Если таблица имеет ошибки, вызовите метод GetErrors класса DataTable, который возвратит массив объектов DataRow с ошибками. 6. Для каждого DataRow, исследуйте свойство RowError для того, что бы определить к каким данным это относится. 7. Урегулируйте ошибки, если это возможно. 8. Вызовите метод Merge, что бы объединить изменения во втором DataSet с первым. [VB] ds.Merge(changedDataSet) [C#] ds.Merge(changedDataSet); 9. Вызовите метод Update для SQLDataSetCommand (или ADODataSetCommand), используя объединенный DataSet как параметр. [VB] workDSCMD.Update (ds) [C#] workDSCMD.Update (ds); 10. Вызовите метод AcceptChanges для DataSet, что бы принять изменения, или альтернативный метод RejectChanges, чтобы отменить изменения. ds.AcceptChanges 5.2. Создание DataSet программным путём DataSet обеспечивается двумя основными конструкциями: public DataSet() public DataSet(string DataSetName) Первая конструкция для пустого DataSet, вторая создаёт DataSet с указанным именем. Следующий пример создает экземпляр DataSet: [VB] Dim workDataSet as DataSet Set workDataSet As New DataSet() ' Empty DataSet. ' Or Set workDataSet As New DataSet("CustomerOrders") [C#] DataSet workDataSet = new DataSet(); // Empty DataSet. // Or DataSet workDataSet = new DataSet("CustomerOrders"); 5.3. Добавление DataTable в DataSet ADO.NET дает Вам возможность вначале создать внешние таблицы, а затем добавлять их к существующему набору данных DataSet. (чтобы создать DataTable, см. .Net Framework SDK Создание DataTable) Обратите внимание, для текущей версии, Вы должны добавить недавно созданную таблицу к DataSet до загрузки её данными. Следующий пример создает DataSet (ds), добавляет новый DataTable с именем Orders и добавляет три столбца (OrderID, OrderQuantity, и CustID) к объекту DataTable. Наконец, этот код определяет столбец OrderID как первичный ключ. [VB] ds.Tables.Add(New DataTable("Orders")) ds.Tables("Orders").Columns.Add("OrderID", System.Type.GetType("System.Int32")) ds.Tables("Orders").Columns.Add("OrderQuantity", _ System.Type.GetType("System.Int32")) ds.Tables("Orders").Columns.Add("CustID", _ System.Type.GetType("System.Int32")) ds.Tables("Orders").PrimaryKey = New DataColumn(ds.Tables.Columns("Orders"), _ ds.Tables.Columns("OrderID")) ' Or Dim workTable As DataTable = New DataTable("Orders") workTable.Columns.Add("OrderID", System.Type.GetType("System.Int32")) workTable.Columns.Add("OrderQuantity", System.Type.GetType("System.Int32")) workTable.Columns.Add("CustID", System.Type.GetType("System.Int32")) ds.Tables("Orders").PrimaryKey = _ New DataColumn(ds.Tables("Orders").Columns("OrderID")) ds.Tables.Add(workTable) [C#] ds.Tables.Add(new DataTable("Orders")); ds.Tables["Orders"].Columns.Add("OrderID", System.Type.GetType("System.Int32")); ds.Tables["Orders"].Columns.Add("OrderQuantity", System.Type.GetType("System.Int32")); ds.Tables["Orders"].Columns.Add("CustID", System.Type.GetType("System.Int32")); ds.Tables["Orders"].PrimaryKey = new DataColumn[]{ds.Tables["Orders"].Columns["OrderID"]}; // Or DataTable workTable = new DataTable("Orders"); workTable.Columns.Add("OrderID", System.Type.GetType("System.Int32")); workTable.Columns.Add("OrderQuantity", System.Type.GetType("System.Int32")); workTable.Columns.Add("CustID", System.Type.GetType("System.Int32")); ds.Tables["Orders"].PrimaryKey = new DataColumn[]{ds.Tables["Orders"].Columns["OrderID"]}; ds.Tables.Add(workTable); 5.4. Добавление отношений между двумя таблицами Поскольку DataSet может содержать множество объектов DataTable, также должен быть способ связать все таблицы друг с другом. Это может быть необходимо для организации навигации по таблицам, и для возвращения связанных значений. Необходимыми параметрами DataRelation являются два столбца, которые служат как первичные ключи и foreign ключи в отношениях таблиц, и имя DataRelation. Имя может использоваться для навигации, или при поиске значений. Примечание: Отношения также могут быть построены не только по одному столбцу в таблице. Вы можете использовать массив объектов DataColumn для первичных и foreign ключей. Представленный ниже пример предполагает, что в DataSet существуют два объекта DataTable. Обе таблицы имеют столбец по имени CustID, который служит для связи между таблицами. Пример добавляет один DataRelation к совокупности отношений объекта DataSet. Первый параметр (CustOrders) определяет имя отношений. Второй и третий параметры - объекты DataColumn, которые связывают две таблицы. [VB] Dim ds As DataSet = New DataSet("CustomerOrders") ' With ds do the following: ' - add a customer table with CustID and CustName as columns. ' - set Customers.CustID as the PrimaryKey column. ' - add an Orders table with three columns: OrderID, CustID, and Quantity ' - set Orders.OrderID as the PrimaryKey column. ' Add a DataRelation to the Relations collection specifying its name, and the ' appropriate DataColumn objects as arguments. ds.Relations.Add("CustOrders", ds.Tables("Customers").Columns("CustID"), _ ds.Tables("Orders").Columns("CustID")) ' Or Dim dr As DataRelation = _ New DataRelation("CustOrders", ds.Tables("Customers").Columns("CustID"), _ ds.Tables("Orders").Columns("CustID")) ds.Relations.Add(dr) [C#] DataSet ds = new DataSet("CustomerOrders"); // With ds do the following: // - add a customer table with CustID and CustName as columns // - set Customers.CustID as Primary Key // - add an Orders table with three columns: OrderID, CustID, and Quantity // - set Orders.OrderID as primary key // Add a DataRelation to the Relations collection specifying its name, // and the appropriate DataColumn objects as arguments. ds.Relations.Add("CustOrders",ds.Tables["Customers"].Columns["CustID"], ds.Tables["Orders"].Columns["CustID"]); // Or DataRelation dr; dr = new DataRelation("CustOrders",ds.Tables["Customers"].Columns["CustID"], ds.Tables["Orders"].Columns["CustID"]); ds.Relations.Add(dr) 5.5. Установка отношений между таблицами В начале, функция DataRelation должна обеспечить навигацию от одной таблицы к другой в пределах DataSet. Это позволит привязать все связанные объекты DataRow одной таблицы, по заданной, единственной DataRow из связанной с ней таблицы. Например, если дана DataRow таблицы Customers, Вы можете получить все orders относящиеся к клиенту из таблицы Orders. Представленный ниже пример кода возвращает множество объектов DataRow (orders) из одной таблицы, используя DataRelation и единственную DataRow из другой (customers). [VB] Dim x() As DataRow = ds.Tables("Customers").ChildRelations("CustOrders"). _ GetChildRows(ds.Tables("Customers").Rows(0)) Console.WriteLine("") Console.WriteLine("Total Child records for CustOrders Relationship = " & _ x.Count.ToString) Console.WriteLine("Total Tables in DataSet = " & ds.Tables.Count.ToString) [C#] DataRow[] x = ds.Tables["Customers"].ChildRelations["CustOrders"] .GetChildRows(ds.Tables["Customers"].Rows[0]); Console.WriteLine(""); Console.WriteLine("Total Child records for CustOrders Relationship = " + x.Count.ToString()); Console.WriteLine("Total Tables in DataSet = " + ds.Tables.Count.ToString()); 5.6. Добавление ограничений для DataSet В реляционной базе данных должна обеспечиваться целостность данных. Один из путей поддержания такой целостности в ADO.NET, это добавление ограничений к таблицам. Ограничение, это автоматически исполняемое правило, которое определяет порядок действий, когда содержание строки так или иначе изменено. Есть два вида ограничений, доступных в ADO.NET: ForeignKeyConstraint и UniqueConstraint. 5.6.1. ForeignKeyConstraint Когда значение в строке удалено или обновлено, и это значение также используется в одной или более связанных таблиц, ForeignKeyConstraint определяет то, что случается во второй таблице, которая связана с той же самой колонкой через DataRelation. Возможные действия включают: Действие Описание Cascade Удаление или обновление связанных строк. SetNull Установка значений в связанных строках к null. SetDefault Установка значений в связанных строках к значениям по умолчанию. None No action — значения в связанных строках не затронуты. Default Заданное по умолчанию действие для свойства, которое должно применятся при каскадном действии. Используя ForeignKeyConstraint сначала создают ограничение, а затем устанавливают свойства DeleteRule и UpdateRule к одному из соответствующих значений, как показано в примере ниже: [VB] Dim fk As ForeignKeyConstraint = _ New ForeignKeyConstraint(ds.Tables("Customers").Columns("CustID"), _ ds.Tables("Orders").Columns("CustID")) fk.DeleteRule = Cascade fk.UpdateRule = SetDefault ' Add the constraint to the Customers table. ds.Tables(0).Constraints("Customers").Constraints.Add(fk) [C#] ForeignKeyConstraint fk = new ForeignKeyConstraint(ds.Tables ["Customers"].Columns["CustID"], ds.Tables["Orders"].Columns["CustID"]); fk.DeleteRule = Cascade; fk.UpdateRule = SetDefault; ds.Tables["Customers"].Constraints.Add(fk); 5.6.2. UniqueConstraint Когда обязательно, чтобы все значения в столбце были уникальны, Вы можете добавить к DataTable UniqueConstraint. UniqueConstraint может быть назначен или для отдельного столбца, или на массив столбцов того же самого объекта DataTable. [VB] Dim uc As UniqueConstraint = _ New UniqueConstraint(ds.Tables("Customers").Columns.("CustID")) ' Add the constraint to the Customers table. ds.Tables("Customers").Constraints.Add(uc) [C#] Data.UniqueConstraint uc = new UniqueConstraint(ds.Tables("Customers").Columns.("CustID"); // Add the constraint to the Customers table. ds.Tables["Customers"].Constraints.Add(uc); 5.7. Работа с событиями DataSet DataSet обеспечивает ряд событий, которые могут быть использованы в коде пользователя. Этот события включают: - PropertyChange - MergeFailed - RemoveTable - RemoveRelation - Добавление обработчика события (event handler) к событию. [VB] ds.AddOnPropertyChange(new System.ComponentModel.PropertyChangeEventHandler _ (AddressOf me.DataPropertyChange)) ds.AddOnMergeFailed(new System.Data.MergeFailedEventHandler _ (AddressOf me.DataSetMergeFailed)) Private Sub DataSetPropertyChange _ (ByVal sender As Object, ByVal e As System.PropertyChangeEventArgs) MessageBox.Show("DataSet property change") End Sub Private Sub DataSetMegeFailed _ (ByVal sender As Object, ByVal e As System.Data.MergeFaileedEventArgs) MessageBox.Show("Merge failed!") End Sub [C#] ds.AddOnPropertyChange (new System.ComponentModel.PropertyChangeEventHandler(DataSetPropertyChange)); ds.AddOnMergeFailed (new System.Data.MergeFailedEventHandler(DataSetMergeFailed)); private void DataSetPropertyChange (object sender, System.PropertyChangeEventArgs e){ MessageBox.Show("DataSet property change")} private void DataSetMergeFailed (object sender, System.Data.MergeFailedEventArgs e){ MessageBox.Show("Merge failed!")} 5.8. Использование Typed DataSet ADO обеспечивает отложенно-связанный доступ (late bound access) к значениям в пределах его рекордсета через слабо типизированные переменные. ADO.NET обеспечивает способность обратиться к данным, содержащимся в DataSet через метафору "Strongly-Typed". Это означает, что Вы можете обращаться к таблицам и столбцам, которые являются частью DataSet через простые имена и strongly typed переменные. Typed DataSet - класс, который происходит от DataSet. Также, он наследует все методы и свойства DataSet. Однако, в дополнение, typed DataSet обеспечивает методы "strongly-typed". На практике это означает, что Вы можете обращаться к таблицам и столбцам по имени, вместо использования collection based метода. Например, в ADO Вы могли использовать следующий код Visual Basic, чтобы найти определенную запись в рекордсете, и изменить значение поля: AdoRecordSet.Find("Customerid = 'ALFKI'") AdoRecordSet.Fields("FirstName").Value = "Bob" Для typed DataSet в ADO.NET, тот же самый результат был бы получен с помощью следующего кода: CustomerDataSet.Customers("ALFKI").CustomerName = "Bob" Кроме удобочитаемости кода, typed DataSet позволяет транслятору автоматически завершать строки, которые Вы пишете. Кроме того, strongly-typed DataSet обеспечивает доступ к значениям строки как правильным strongly typed значениям. В ADO Вы работали через варианты при назначении и поиске данных. Если бы значение, которое Вы назначили, имело бы неправильный тип, ADO выдал бы ошибку во время выполнения программы. В ADO.NET, если значение - целое число, и Вы пытаетесь назначать строку, Вы получите статическую ошибку. Учитывая схему XML, которая поддерживает XSD стандарт, Вы можете генерировать typed DataSet используя программу XSD.EXE, поставляемую с .NET Framework SDK. Синтаксис генерации DataSet следующий: xsd.exe /d /l:C# {XSDSchemaFileName.xsd} Где: /D – директива, сообщающая программе о необходимости генерировать DataSet; /l: - сообщает программе какой язык использовать. Для обозначения языка Вы можете использовать C# или VB. Представленный ниже пример использует typed DataSet с именем customerDataSet, чтобы загрузить список customers (заказчиков) из базы данных Northwind. Как только данные загружены, используя метод FillDataSet, организуется цикл по каждому заказчику в таблице customers, используя объект typed customerRow. Здесь, Вы имеете прямой доступ к полю customerid, в противоположность доступу к этому полю через совокупность полей (fields collection). [C#] customerDataSet myCustDS = new customerDataSet(); SQLDataSetCommand myDSCommand = new SQLDataSetCommand("SELECT * FROM Customers", "server=localhost;uid=sa;pwd=;database=northwind"); myDSCommand.FillDataSet(myCustDS, "Customers"); foreach(customersRow myCustomer in myCustDS.customers) { Console.WriteLine(myCustomer.CustomerID.ToString()); } [VB] Dim myCustDS as DataSet = new dataset Dim myDSCommand as SQLDataSetCommand myDSCommand = new SQLDataSetCommand("Select * from customers", "server=localhost;uid=sa;pwd=;database=northwind") myDSCommand.FillDataSet(myCustDS, "Customers") Dim myCustomer as DataRow For Each myCustomer in myCustDS.Tables(0).Rows Console.WriteLine(myCustomer(0).ToString()) Next 6. Использование DataTable Чтобы использовать DataTable, Вы должны подключить System.Data. 6.1. Создание DataTable Объект DataTable имеет две конструкции: public DataTable() public DataTable(string tableName) Следующий пример создает экземпляр объекта DataTable: [VB] Dim workTable as DataTable workTable = New DataTable("Customer") workTable.CaseSensitive = false workTable.MinimumCapacity = 100 [C#] DataTable workTable = new DataTable("Customer"); workTable.CaseSensitive = false; workTable.MinimumCapacity = 100; В этом примере создаётся объект DataTable и устанавливается его имя. Также Вы можете создавать объект DataTable без того, чтобы сразу задавать его имя, а имя присвоить позже, воспользовавшись свойством TableName объекта DataTable: DataTable worktable = new DataTable(); worktable.TableName = "Customers"; Объект DataTable создаётся с заданными по умолчанию параметрами свойств. Среди прочих свойств DataTable, свойство CaseSensitive указывает, является ли схема, которая добавляет строки к таблице зависимой от регистра. Вы можете изменять установку CaseSensitive для сравнений строки при сортировке, поиске и фильтрации (sorting, searching и filtering). Свойство MinimumCapacity определяет число строк, которое система должна создать перед получением данных. Если для вашей системы критична эффективность, Вы можете изменить установленное по умолчанию начальное значение параметра, в целях оптимизации эффективности. 6.2. Добавление столбцов в DataTable DataTable содержит совокупность объектов DataColumn. Эта совокупность определяет структуру таблицы. Чтобы добавить новый столбец в эту совокупность, используйте метод Add. Представленный ниже пример добавляет три столбца к DataTable, используя метод Add из ColumnsCollection. Метод определяет свойства DataType и ColumnName. [VB] Dim workTable as DataTable Dim workColumn as DataColumn WorkTable = new DataTable("Customers") workColumn = workTable.Columns.Add("CustID", System.Type.GetType("System.Int32") ) workColumn.AllowNull = false workColumn.Unique = true workColumn = workTable.Columns.Add("CustomerNameLast", System.Type.GetType("System.String") ) workColumn = workTable.Columns.Add("CustomerNameFirst", System.Type.GetType("System.String") ) workColumn = workTable.Columns.Add("Purchases", System.Type.GetType("System.Double")) [C#] DataColumn workColumn = null; DataTable workTable = new DataTable("Customers"); workColumn = workTable.Columns.Add("CustID", System.Type.GetType("System.Int32") ); workColumn.AllowNull = false; workColumn.Unique = true; workColumn = workTable.Columns.Add("CustomerNameLast", System.Type.GetType("System.String") ); workColumn = workTable.Columns.Add("CustomerNameFirst", System.Type.GetType("System.String") ); workColumn = workTable.Columns.Add("Purchases", System.Type.GetType("System.Double") ); В этом примере, сначала объявляется переменная типа DataColumn с именем "workColumn". Для C#, переменная инициализируется null. DataColumn workColumn = null; Затем, объявляется экземпляр таблицы, чтобы добавить новые столбцы, и называем её Customers. DataTable worktable = new DataTable("Customers"); После создания таблицы, начните добавлять в неё новые столбцы. Типы данных, назначаемые столбцам должны быть типами данных, принятыми в .NET FRAMEWORK, а не у OLE DB или, как определенные в базе данных типы. Чтобы установить тип, используйте System функцию GetType, которая возвращает название для требующегося типа данных. В этом примере добавляется три столбца: CustID (type: Int32), CustomerNameLast (type: String), и CustomerNameFirst (type: String). workColumn = workTable.Columns.Add("CustID", System.Type.GetType("System.Int32") ); System.Type.GetType("System.Int32") Метод GetType возвращает null, если указанный тип не может быть загружен. ColumnsCollection в DataTable имеет две перегрузки метода Add: Public DataColumn Add(String columnname, Type type) Public DataColumn Add(String columnname) 6.3. Создание Expression Columns ADO.NET позволяет также создавать и определять Expression (выражение) столбцы, которые содержат вычисляемые значения. Выражения в ADO.NET используются для: - Фильтрации; - Вычислений; - Агрегирования информации столбцов. Чтобы создавать Expression столбец, установите свойство DataType на тип, соответствующий возвращаемому выражением значения. После этого, установите свойство Expression на допустимое выражение. Следующий пример показывает, как создать Expression столбец, который вычисляет налог с продаж 8.6 процентов (.086): [VB] Dim sales_tax As DataColumn = New DataColumn sales_tax.DataType = System.Type.GetType("System.Currency") sales_tax.Expression = "total * .086" [C#] DataColumn sales_tax = New DataColumn; sales_tax.DataType = System.Type.GetType("System.Currency"); sales_tax.Expression = "total * .086"; Вы также можете создать Expression столбец, используя метода Add. Например, показанный ниже код добавляет столбец, который вычисляет для заказчика скидку в 10 процентов от закупки. Выражение умножает столбец "Purchases" на 10 процентов [VB] Dim workColumn As DataColumn workColumn = workTable.Columns.Add("Rebates", _ System.Type.GetType("System.Double"), "Purchases * .1" ) [C#] System.Data.DataColumn workColumn; workColumn = workTable.Columns.Add("Rebates", System.Type.GetType("System.Double"), "Purchases * .1"); Выражения DataSet могут ссылаться на другие Expression столбцы. Однако, циклическая зависимость, в которой два выражения ссылаются друг на друга, вызовет ошибку. Для выяснения правил использования выражений, см. соответствующую тему справочника по .NET Framework SDK, для свойств Expression класса DataColumn. 6.4. Создание AutoIncrement столбцов Другая особенность DataColumn, это возможность автоприращения (auto-incrementation). Autoincrementing столбец автоматически увеличивает значение, содержащееся в столбце, когда добавляются новые строки. Чтобы создавать Auto-incrementing столбец, установите свойство AutoIncrement этого столбца к истине. После этого, отсчёт значений столбца начнётся со значения, определенного в свойстве столбца AutoIncrementSeed. При добавлении каждой строки, значение столбца AutoIncrement будет увеличиваться на величину, указанную в свойстве столбца AutoIncrementStep. Например, чтобы создать столбец, который начинается с 200 и увеличивается с приращением в 3 шага: [VB] workColumn = workTable.Columns.Add _ ("CustID", System.Type.GetType("System.Int32") workColumn.AutoIncrement = True workColumn.AutoIncrementSeed = 200 workColumn.AutoIncrementStep = 3 [C#] workColumn = workTable.Columns.Add("CustID", System.Type.GetType("System.Int32") ); workColumn.AutoIncrement = true; workColumn.AutoIncrementSeed = 200; workColumn.AutoIncrementStep = 3; По аналогии, можно заставить столбец быть read-only (только для чтения), установив его свойство ReadOnly в истину. 6.5. Создание первичного ключа для таблицы Общее принятым правилом для базы данных является то, что каждая таблица имеет столбец, или группу столбцов, которые уникально идентифицирует каждую строку в таблице. Этот столбец (или столбцы) называется первичным ключом (primary key). Для таблицы Customers, столбец CustID будет первичным ключом. Чтобы определить этот столбец, как первичный ключ, установите некоторые дополнительные свойства для столбца CustID, которые заставят его работать подобно первичному ключу в базе данных. Первое из этих свойств AllowNull, которое определят, допустимо ли значение null. Как это сделано у первичного ключа в базе данных, установите свойство в false. workColumn.AllowNull = false; Свойство Unique определяет, должны ли значения в столбце быть уникальными. Другими словами, никакие два значения в столбце не могут быть идентичны. Так как столбец будет первичным ключом, устанавливать это значение в true. workColumn.Unique = true; Теперь, когда столбец имеет всё необходимое для выполнения роли первичного ключа, ему может быть установлено свойство PrimaryKey объекта DataTable: [VB] Dim myColArray As DataColumn() myColArray = workTable.Columns("CustID") workTable.PrimaryKey = myColArray [C#] workTable.PrimaryKey = new DataColumn[] {worktable["CustID"]}; Если необходимо определить дополнительные столбцы, как часть первичного ключа, это будет выглядеть следующим образом: [VB] myColArray(0) = workTable.Columns("Col1") myColArray(1) = workTable.Columns("Col2") workTable.PrimaryKey = myColArray [C#] workTable.PrimaryKey = new DataColumn[] {worktable["Col1"], worktable["Col2"]}; 6.6. Порядок применения Insert, Update и Delete Вообще, все Insert, Update и Delete передаются к SQL Server в том же самом порядке строки, в каком он был возвращён первоначальной инструкцией Update. Порядок, в котором исполняются запланированные инструкцией Update изменения, при некоторых обстоятельствах, может вызвать проблемы при модификации. Например, рассмотрим следующий алгоритм: - Заполним объект DataSet, включая ключевой столбец. - Изменим ключевое значение одной строки (например, 3 заменим на 26). - Вставим новую строку, используя старое ключевое значение. Вышеупомянутая последовательность операций приведёт к ошибке. Это произойдёт потому, что новая строка вставлена в набор данных в порядке сортировки. Все Insert, Update и Delete обрабатываются последовательно, так что вставка новой строки произойдёт раньше, чем будет выполнена модификация старой строки. 7. Работа с данными в таблицах ADO.NET 7.1. Добавление данных в таблицу После того, когда создана таблица со столбцами и ключами, можно добавлять данные. Представленный ниже код добавляет десять строк в таблицу Customers. [VB] Dim iCounter as integer Dim workRow as DataRow For i = 0 to 9 workRow = worktable.NewRow() workRow("CustID") = iCounter workRow("CustomerNameLast") = "CustName" & i.ToString() worktable.Rows.Add(workRow) Next [C#] DataRow workRow = null; for (int i = 0; i <= 9; i++) { workRow = workTable.NewRow(); workRow[0] = i; workRow[1] = "CustName" + i.ToString(); workTable.Rows.Add(workRow); } В этом коде объявляется новая переменная с типом DataRow. Новый объект DataRow возвращается при вызове метода NewRow. DataTable создаёт объект DataRow, основанный на структуре таблицы (как это определено в ColumnsCollection). [VB] Dim workRow As DataRow workRow = workTable.NewRow() [C#] Data.DataRow workRow; workRow = workTable.NewRow(); После этого, Вы можете работать с недавно созданными строками через индекс или через имя столбца. Обе из ниже следующих строк кода допустимы: [VB] workRow("CustID") = "CustName" & i workRow(1) = "CustName" & i [C#] workRow["CustID"] = "CustName" + i.ToString(); workRow[1] = "CustName" + i.ToString(); После того, как строка заполнена значениями, её можно добавить в RowsCollection таблицы, используя метод Add. [VB] workTable.Rows.Add(workRow) [C#] workTable.Rows.Add(workRow); В дополнение к добавлению строк этим путём, мы можем также добавлять новую строку, получая значения из массива (типизированного, как объект) и используя метод Add. [VB] workTable.Rows.Add(new Object() {1, "CustName1"}) [C#] workTable.Rows.Add(new Object[] {1, "CustName1"}); Этот способ просто создаёт новую строку внутри таблицы и устанавливает для значения столбца, значение из объекта массив. Обратите внимание, что последовательность значений в массиве согласована с последовательностью значений столбца в таблице. 7.2. Состояния строки Каждая DataRow имеет свойство RowState, которое определяет её состояние. В приведённом выше примере, RowState недавно созданной строки установится в состояние Detached. Однако, сразу после того, как строка будет добавлена в RowsCollection методом Add, свойство RowState установится в New. Приведённая ниже таблица содержит возможные состояния, которые строка может принимать. RowState Описание Unchanged Никакие изменения не делались, начиная с последнего запроса AcceptChanges New Строка была добавлена в таблицу, но AcceptChanges не вызывался Modified Некоторый элемент строки был изменены. Deleted Строка была удалена из таблицы с помощью метода Delete Detached Либо строка была удалена, но AcceptChanges не вызывался, либо строка была создана, но не добавлена к таблице. 7.3. Removing или Deleting DataRow из таблицы При выполнении операций удаления одной или более строк из таблицы, обратите внимание на то, что removing не то же, что deleting. Важность этого различия будет рассмотрена позже в обсуждении способов сохранения значений таблицы в источнике данных. Чтобы удалить строку из таблицы, вызовите метод Remove из RowsCollection таблицы (доступный через свойство Rows объекта DataTable), воспользовавшись индексом или непосредственно экземпляром строки. [VB] workTable.Rows.Remove(3) [C#] workTable.Rows.Remove(3); ИЛИ [VB] workTable.Rows.Remove(workTable.Rows(3)) [C#] workTable.Rows.Remove(workTable.Rows[3]); После вызова этого метода, указанная строка будет удалена из совокупности строк. В отличии от этого, Вы можете вызвать метод Delete для DataRow, чтобы изменить её состояние на Deleted: [VB] workTable.Rows(3).Delete [C#] workTable.Rows[3].Delete Отметка строки, как удаленной, говорит о том, что когда будет вызван метод AcceptChanges объекта DataTable, строка будет удален из таблицы. Это приведёт к изменению RowState для Deleted строки. Напротив, если вызван RejectChanges, RowState этой строки возвратится к тому состоянию, которое до этого соответствовало состоянию удалено (Deleted). Различие между методами Remove и Delete в том, когда имеет место фактическое удаление. Обратите внимание: Если RowState строки установлен в New, это означает она только что была добавлен в таблицу, а если состояние отмечено как Deleted, она будет удалена из таблицы. 7.4. Работа с данными в таблице Теперь, когда создана таблица содержащая столбцы, первичный ключ и данные, можете заняться отображением её содержания. В этом примере, DataSet фильтруется так, чтобы Вы работали только с текущей версией строки. [VB] Dim CurrRows() As DataRow = workTable.Select(Nothing, Nothing, _ System.Data.DataViewRowState.CurrentRows) Dim list As String = System.String.Empty Console.WriteLine("") Console.WriteLine("Writing current rows for " & workTable.Tablename) Dim RowNo As Integer Dim ColNo As Integer For RowNo = 0 to CurrRows.Count - 1 For ColNo = 0 To workTable.Columns.Count – 1 list = "" list &= workTable.Columns(colNo).ColumnName & " = " & _ CurrRows(RowNo)(ColNo).ToString Console.WriteLine(list) Next Next If CurrRows.Count < 1 Then Console.WriteLine("No CurrentRows Found") [C#] DataRow[] CurrRows = workTable.Select(null, null, System.Data.DataViewRowState.CurrentRows ); string list = System.String.Empty; Console.WriteLine(""); Console.WriteLine("Writing Current rows for " + workTable.TableName ); for (int RowNo = 0; RowNo <= CurrRows.Count -1; RowNo++) { for (int ColNo = 0; ColNo <= workTable.Columns.Count - 1; ColNo++) { list = System.String.Empty; list += workTable.Columns[ColNo].ColumnName + " = " + CurrRows[RowNo][ColNo].ToString(); Console.WriteLine(list); } } if (CurrRows.Count < 1 ) { Console.WriteLine("No Current Rows Found"); } Метод Select класса DataTable дает Вам возможность отыскать набор строк, соответствующий указанным критериям. Метод возвращает массив объектов DataRow. В примере метод отыскивает все Current строки. Что такое строка Current? DataTable поддерживает три версии каждой строки: Original, Current и Proposed. Первоначальная (Original) строка, это такая версия строки, когда она была только что добавлено к таблице. Текущая (Current) версия строки содержит любые изменения, которые были сделаны к строке. Если Вы выполняете изменение в строке к необходимому значению, новое значение будет текущим значением, в то время как значение до изменений можно считать первоначальным значением. Промежуточная (Proposed) версия возможна при особых обстоятельствах и в течение короткого периода времени. Proposed версия представляет состояние, промежуточное между Original и Current. Строка переходит в промежуточное состояние, когда система или пользователь вызывают BeginEdit и редактируют строку. [VB] workRow.BeginEdit ' Make some changes to the row. If ValidateColValue(proposedValue) workRow.EndEdit Else workRow.CancelEdit End If [C#] workRow.BeginEdit // make some changes to the row if (ValidateColValue(proposedValue)) workRow.EndEdit; else workRow.CancelEdit; Это означает, что в течение состояния строки Proposed, Вы можете применять логику проверки правильности значений отдельных столбцов, анализируя результаты события ColumnChange объекта DataTable. Таблица передаёт ссылку на изменяемый столбец и состояние Proposed в событии ColumnChange. Пользователь может изменять состояние Proposed или генерировать исключение, для отмены редактирования. Строка выводится из состояния Proposed, когда вызывается EndEdit. Вы можете также отменять любое изменение при редактировании, в тот момент, когда строка находится в состоянии Proposed, с помощью вызова CancelEdit. 7.5. Добавление и чтение информации об ошибках строки Поскольку конечные пользователи редактируют значения в таблице, они могут допускать при этом ошибки. В этих случаях, Вы не можете прервать действия пользователя, когда он находятся в процессе редактирования данных, а просто выводите информацию об ошибке в строке на его дисплей, когда редактирование завершено. Объект DataRow поддерживает эту возможность через свойство RowError для каждой строки. Представленные ниже два примера показывают как выводить/читать (Add/Read) информацию об ошибке, связанной с объектом DataRow. Пример 1: [VB] Public Sub AddErrorToRow(ByVal workTable As DataTable, ByVal Row As Integer, _ ByVal ErrorString As String) workTable.Rows(Row).RowError = ErrorString End Sub [C#] public void AddErrorToRow(DataTable workTable, int Row, string ErrorString) { workTable.Rows[Row].RowError = ErrorString; } Пример 2: [VB] Public Sub WriteTableErrors(ByVal workTable As DataTable) If workTable.HasErrors Then Dim ErrorRows() As DataRow = workTable.GetErrors() Console.WriteLine("") Console.WriteLine("=================") Console.WriteLine("DataTable " & workTable.TableName & " has " & _ ErrorRows.Count.ToString & " Errors!") Dim i As Integer For i = 0 to Errors.Count – 1 Console.WriteLine("Row Error for row " & _ ErrorRows(i)("CustID").ToString & "Error Msg =" & _ ErrorRows(i).RowError) Next Else Console.WriteLine("") Console.WriteLine("=================") Console.WriteLine("DataTable " + workTable.TableName + " Has no errors") End If End Sub [C#] public void WriteTableErrors(DataTable workTable) { if ( workTable.HasErrors ) { DataRow[] ErrorsRows = workTable.GetErrors(); Console.WriteLine(""); Console.WriteLine("================="); Console.WriteLine("DataTable " + workTable.TableName + " has " + ErrorsRows.Count.ToString() + " Errors!"); for (int i = 0; i <= ErrorsRows.Count -1; i++) { Console.WriteLine("Row Error for row " + ErrorsRows[i]["CustID"].ToString() + " Error Msg=" + ErrorsRows[i].RowError); } } else { Console.WriteLine(""); Console.WriteLine("================="); Console.WriteLine("DataTable " + workTable.TableName + " Has no errors"); } } Первый пример берет номер строки (Row) и добавляет сообщение об ошибках (ErrorString). Второй пример печатает RowError для каждой строки workTable, используя ассоциируемый с ошибками текст. 7.6. Принятие или отклонение изменений строк Если Вы уверены относительно внесённых изменений, необходимо принять эти изменения и передать текущие значения строки, чтобы быть установить состояние Current. (Обратите внимание, что всё это относится к данным, находящимся DataSet, который размещён в памяти). Принятие или отклонение изменений в DataSet вызовет обновление данных в источнике (для получения дополнительной информации, прочтите ADO.NET Managed Providers). Представленный ниже код показывает два пути принятия изменений строки. Пример 1: [VB] workTable.AcceptChanges() [C#] workTable.AcceptChanges(); Пример 2: [VB] Public Function AcceptChanges(ByVal workTable As DataTable) As Integer Dim acceptedRows As Integer = 0 Dim i As Integer For i = 0 To workTable.Rows.Count – 1 If Not workTable.Rows(i).HasErrors Then acceptedRows += 1 workTable.Rows(i).AcceptChanges() Else workTable.Rows(i).RejectChanges() End If End If AcceptChanges = acceptedRows End Function [C#] public int AcceptChanges(DataTable workTable) { int acceptedRows = 0; for (int i = 0; i <= workTable.Rows.Count -1 ; i++) { if (!workTable.Rows[i].HasErrors ) { acceptedRows++; workTable.Rows[i].AcceptChanges(); } else { workTable.Rows[i].RejectChanges(); } } return acceptedRows; } Первый пример принимает все изменения, которые были сделаны к таблице. Второй пример вносит изменения в цикле и принимает каждое с помощью приёма Row-by-Row. Для исполнения AcceptChanges, код примера исследует свойство HasErrors. [VB] If Not workTable.Rows(i).HasErrors) Then [C#] if (!workTable.Rows[i].HasErrors ) DataRow позволяет Вам вводить строку ошибки и ассоциировать её со строкой в таблице. Если любой строке в таблице ассоциировали строку ошибки для этой строки, свойство HasErrors возвратит true. Во втором примере выполняется исследование строк, и изменения принимаются для тех, которые не имеют ошибок. Как только изменения в строке приняты, первоначальные состояние строки заменяется на текущее состояние. Строка, которая рассматривалась, будет окончательно изменена. 7.7. Работа с событиями DataTable Объект DataTable обеспечивает ряд событий, которые могут быть обработаны кодом пользователя. Эти события включают: - ColumnChange RowChanged RowChanging RowDeleting RowDeleted В представленном ниже примере, создаются три метода: MyColChanging, MyRowChanging и MyRowChanged. Каждый из этих методов вызывается, когда столбец или строка будет изменена. [VB] workTable.AddOnColumnChanging _ (New System.Data.DataColumnChangeEventHandler(AddressOf me.MyColChanging)) workTable.AddOnRowChanging _ (New System.Data.DataRowChangeEventHandler(AddressOf me.MyRowChanging)) workTable.AddOnRowChanged _ (New System.Data.DataRowChangeEventHandler(AddressOf me.MyRowChanged)) Private Sub MyColChanging _ (ByVal sender As Object, ByVal e As DataColumnChangeEventArgs) End Sub Private Sub MyRowChanging _ (ByVal sender As Object, ByVal e As DataRowChangeEventArgs) 'Add this to capture the row changing event Console.WriteLine("Adding row " + e.Row[0].ToString()) End Sub Private Sub MyRowChanged _ (ByVal sender As Object, ByVal e As DataRowChangeEventArgs) If e.Action = Delete Then Console.WriteLine("deleting row " & e.Row(0).ToString) End If End Sub [C#] workTable.AddOnColumnChanging(new DataColumnChangeEventHandler(MyColChanging)); workTable.AddOnRowChanging(new DataRowChangeEventHandler(MyRowChanging)); workTable.AddOnRowChanged(new DataRowChangeEventHandler(MyRowChanged)); Public void MyColChanging(object sender, DataColumnChangeEventArgs e) { } public void MyRowChanging(object sender, DataRowChangeEventArgs e) { //Add this to capture the row changing event Console.WriteLine("Adding row " + e.Row[0].ToString()); } public void MyRowChanged(object sender, DataRowChangeEventArgs e){ if (e.Action == Delete) { Console.WriteLine("deleting row " + e.Row[0].ToString()); } } 7.8. Фильтрация и сортировка строк в таблице Метод Select дает возможность пользователям отбирать строки по трём критериям: выражению фильтра, порядку сортировки и DataViewRowState. В представленном ниже примере, Вы используете значение null для критериев инструкции, которые Вы хотите игнорировать. В этом примере метод Select возвратит все текущие строки. [VB] Dim CurrRows() As DataRow = workTable.Select(Nothing, Nothing, _ System.Data.DataViewRowState.CurrentRows) [C#] DataRow[] CurrRows = workTable.Select(null, null, System.Data.DataViewRowState.CurrentRows ); Вы можете запросить все строки с фамилией Smith, упорядоченные по столбцу CustomerNameFirst. [VB] Dim myNames() As DataRow = workTable.Select("CustomerLastName = 'Smith'"), _ "CustomerNameFirst", System.Data.DataViewRowState.CurrentRows) [C#] DataRow[] myNames = worktable.Select("CustomerNameLast = 'Smith'", "CustomerNameFirst", System 7.9. Optimistic Concurrency В многопользовательской среде, есть два основных способа обновления данных в СУБД: Optimistic и Pessimistic Concurrency (параллелизм). Pessimistic Concurrency - система блокировок, которая не позволяет пользователю изменять данные, которые используют другие пользователи. В Pessimistic модели, когда пользователь исполняет действие, которое устанавливает блокировку, другие пользователи не могут исполнять действия, которые вошли бы в противоречие с блокировкой, пока владелец блокировки не снимет её. Эта модель главным образом используется в средах с высокой конкуренцией использования данных, где стоимость защиты данных с помощью блокировок меньше, чем стоимость обслуживания отката транзакций, если происходят конфликты параллелизма. Таким образом в Pessimistic модели, когда пользователь читает отчет с намерением его изменения, он устанавливает блокировку. Пока этот пользователь не закончил модификацию и не освободит блокировку, никто не сможет изменять этот отчет. Напротив, в Optimistic Concurrency модели пользователи не блокируют данные при чтении. Когда пользователь хочет модифицировать отчет, приложение должно проверить, изменил ли другой пользователь отчет. Optimistic Concurrency вообще используется в средах с низкой конкуренцией за данные. В Optimistic Concurrency есть традиционно три пути идентификации изменений сделанных в отчёте. Первый способ просто сравнивает все значения столбца, которые считаны из базы данных и текущие значения. Например: 13:00. User1 читает строку в базе данных со следующими значениями: CustID 101 LastName Smith FirstName Bob Column Name Original Value Current Value Value in DB CustID 101 101 101 LastName Smith Smith Smith FirstName Bob Bob Bob 13:01. User2 читает ту же самую строку 13:03. User2 изменяет имя Bob на Robert и обновляет базу данных. Column Name Original Value Current Value Value in DB CustID 101 101 101 LastName Smith Smith Smith FirstName Bob Robert Bob Так как во время модификации значения в базе данных соответствуют оригиналу, модификация для user2 проходит успешно. 13:05. User1 изменяет имя Bob на James и пробует обновить строку. Column Name Original Value Current Value Value in DB CustID 101 101 101 LastName Smith Smith Smith FirstName Bob James Robert Так как первоначальное значение для user1 было Bob, а значение в базе данных из-за модификации user2 стало Robert, попытка модификации имени для user1 окончится сбоем. Второй и третий путь Optimistic Concurrency очень похожи друг на друга: используется или date-time stamp столбец или столбец версии строки. Всякий раз, когда пользователь успешно модифицирует строку, date-time stamp (или версия строки) столбец устанавливается на новое, уникальное значение. Таким образом, везде, где пользователь пытается модифицировать строку, приложение сравнивает оригинальное значение date-time stamp (или версия строки) и текущую версию в базе данных. Если значения идентичны, значит строка не изменилась и модификация может быть выполнена (с присвоением нового значения для date-time stamp или столбца версии строки). Исполнение такого SQL запроса достаточно простое: Update Table1 set x = newx1, y = newy where datetimestamp = originaldatetime ИЛИ Update Table1 set x = newx1, y = newy where rowversion = originalrowversion Чтобы сохранять date-time stamp или точную версию строки, используйте триггер для таблицы, чтобы значения модифицировались при любых изменениях в строке. Тем не менее, другой способ отслеживания модификаций состоит в применении хранимых процедур. В VS7 и DataSetCommand Configuration Wizard (DCW) включена автоматическая поддержка этих задач, что весьма удобно, если Вы используете их для создания хранимых процедур. Представленный ниже пример отслеживает модификацию средствами DCW, через хранимую процедуру, сгенерированную для таблицы авторов базы данных pubs. Обратите внимание, что список параметров этой хранимой процедура фактически содержит два набора параметров. Второй набор (@param1 - @param9), это просто копия первых девяти параметров. CREATE PROCEDURE AuthorUpdate ( @au_id id, @au_lname varchar(40), @au_fname varchar(20), @phone char(12), @address varchar(40), @city varchar(20), @state char(2), @zip char(5), @contract bit, @Param1 id, @Param2 varchar(40) /* Optimistic Concurrency Check */, @Param3 varchar(20) /* Optimistic Concurrency Check */, @Param4 varchar(40) /* Optimistic Concurrency Check */, @Param5 varchar(20) /* Optimistic Concurrency Check */, @Param6 bit /* Optimistic Concurrency Check */, @Param7 char(12) /* Optimistic Concurrency Check */, @Param8 char(2) /* Optimistic Concurrency Check */, @Param9 char(5) /* Optimistic Concurrency Check */ ) AS SET NOCOUNT OFF; UPDATE authors SET au_id = @au_id, au_lname = @au_lname, au_fname = @au_fname, phone = @phone, address = @address, city = @city, state = @state, zip = @zip, contract = @contract WHERE (au_id = @Param1) AND (address = @Param2) AND (au_fname = @Param3) AND (au_lname = @Param4) AND (city = @Param5) AND (contract = @Param6) AND (phone = @Param7) AND (state = @Param8) AND (zip = @Param9); Второй набор параметров содержит первоначальные значения полей, которые были загружены из источника данных. Это позволяет выполнить сравнение в предложении WHERE, используемом в инструкции модификации. После загрузки DataSet, отчет Authors будет походить на следующее: Au_id = "999-99-9999" Au_Lname = "Smith" Au_Fname = "Jane" Phone = "111 111-1111" Address = "123 Main" City = "Redmond" State = "WA" Zip = "98052" Contract = 1 Предположим, что в результате выполненной Вами модификации изменится телефонный номер на 222 111-1111. Однако, ели до вашей модификации, но после того, как Вы считали данные, другой пользователь изменит фамилию Jane со Smith на Doe, при попытке выполнения Вами модификации, она не сможет быть выполнена, потому что столбец Au_Lname больше не соответствует первоначальному значению. Таким образом, в коде, обслуживающем Вашу форму или объект, Вы можете устроить проверку строк, на которые воздействуют другие пользователи, чтобы определить, исполнена ли ваша транзакция или нет. 7.10. Исполнение транзакций Транзакции используются, чтобы управлять блокировками в базах данных. Например, в стандартных бухгалтерских проводках, необходимо дебетовать один счёт и в то же самое время кредитовать другой. Из-за возможных ошибок компьютера, вызванных сбоем в его работе (отключение электропитания, потеря сети и т.д.), есть вероятность того, что одна запись изменена/добавлена, а другая нет. Транзакции дают возможность избежать таких ситуаций. Так же, как в ADO, транзакции в ADO.NET обрабатываются на уровне базы данных: т.е. ваша база данных должна поддержать транзакции. Есть три основных команды для транзакций: begin, commit и rollback. Begin отмечает начало транзакции. Все процедуры, используемые между Begin и следующей командой (или Rollback или Commit) рассматриваются частью транзакции. Рассмотрим следующий пример команды T-SQL: begin transaction insert into account (account,amount,debitcredit) values (100,100,'d') insert into account (account,amount,debitcredit) values (300,100,'c') if (@@ERROR > 0) rollback else commit В ADO.NET (как и в ADO) Вы можете управлять транзакциями через объект Connection. Фактически, когда Вы используете ADOConnection, используется та же самая основная модель транзакций OLEDB. Поэтому, если Вы могли определять транзакции для вашей базы данных используя ADO, Вы также можете определять их и средствами ADO.NET. Важно! Объект DataSet также имеет commit модель (AcceptChanges, RejectChanges), но это не затрагивает базу данных. Эта commit модель применяется только для кэша данных в одном DataSet. 8. Свод событий ADO.NET Следующие таблицы содержат события, генерируемые компонентами ADONET. DataSetCommand Events Method/Property Sequence of events Update( ) OnStateChange, if connection is closed. OnRowUpdating, one event per row. OnRowUpdated, one event per row. OnStateChange, if connection was initially closed. InsertCommand property is changed OnSchemaChanging OnSchemaChanged SelectCommand property is changed OnSchemaChanging OnSchemaChanged UpdateCommand property is OnSchemaChanging changed OnSchemaChanged DeleteCommand property is changed OnSchemaChanging OnSchemaChanged TableMappings or ColumnMappings are changed OnSchemaChanging (SchemaType.Mapped) OnSchemaChanged FillDataSetSchema(...) OnStateChange (if connection is closed) OnSchemaMapping — Issued once per result set when the mapping between result set and DataTable is complete. OnStateChange (if connection is closed initially) FillDataSet(...) OnStateChange (if connection is closed) OnSchemaMapping — Issued once per result set when the mapping between result set and DataTable is complete. OnStateChange (if connection is closed initially) Connection Events Method/Property Sequence of events Open() OnStateChange Close() OnStateChange BeginTransaction() OnStateChange CommitTransaction() OnStateChange RollbackTransaction() OnStateChange Informational message set from the server OnInfoMessage Command Events Method/Property Sequence of events ActiveConnection property is changed OnSchemaChanging OnSchemaChanged CommandType property is changed OnSchemaChanging OnSchemaChanged CommandText property is changed OnSchemaChanging OnSchemaChanged Parameters property is changed OnSchemaChanging OnSchemaChanged ResetParameters OnSchemaChanging OnSchemaChanged