ADO .NET. Доступ к данным
Предполагается, что к моменту написания приложения соответствующая база данных уже создана.
Объектная модель ADO .NET реализует отсоединенный доступ к данным. При этом в Visual Studio .NET существует множество ВСТРОЕННЫХ мастеров и дизайнеров, которые позволяют реализовать механизмы доступа к БД еще на этапе разработки программного кода.
С другой стороны, задача получения доступа к данным может быть решена непосредственно во время выполнения приложения.
Концепция доступа к данным в ADO .NET основана на использовании двух компонентов:
НАБОРА ДАННЫХ (представляется объектом класса DataSet) со стороны клиента. Это локальное временное хранилище данных;ПРОВАЙДЕРА ДАННЫХ (представляется объектом класса DataProvider). Это посредник, обеспечивающий взаимодействие приложения и базы данных со стороны базы данных (в распределенных приложениях – со стороны сервера).
ADO .NET. Объектная модель
Объектная модель ADO .NET предполагает существование (при написании приложения для работы с базой данных — использование) двух множеств классов, выполняющих четко определенные задачи при работе с базой данных:
Классы подсоединенных объектов обеспечивают установление соединения с базой данных и управление базой со стороны приложения; классы отсоединенных объектов обеспечивают сохранение, использование и преобразование полученной от базы данных информации на стороне приложения.
Далее рассматриваются классы отсоединенных объектов объектной модели ADO .NET. Их подробному описанию посвящаются следующие разделы пособия. При этом классы отсоединенных объектов могут быть самостоятельно использованы в приложении наряду с обычными компонентами и элементами управления, даже если в приложении и не предполагается организовывать работу с базами данных.
Command
Команда – объект, представляющий один из двух классов: либо класс OleDbCommand, либо класс SqlCommand. Основное назначение объекта "Команда" – выполнение различных действий над Базой Данных (ИСТОЧНИКЕ ДАННЫХ) при использовании ОТКРЫТОГО СОЕДИНЕНИЯ. Сами же действия обычно кодируются оператором SQL или хранимой процедурой. Закодированная информация фиксируется с использованием объектов – представителей класса Parameter, специально разработанных для "записи" кодируемой в команде информации.
То есть после установления соединения с БД для изменения состояния этой базы может быть создан, соответствующим образом настроен и применен объект – представитель класса Command.
Объект "Команда" – стартовый стол для запуска непосредственно из приложения команд управления БД, которыми и осуществляется непосредственное управление БД. Команда в приложении обеспечивает взаимодействие приложения с базой данных, позволяя при этом:
сохранять параметры команд, которые используются для управления БД;выполнять специфические команды БД INSERT, UPDATE, DELETE, которые не возвращают значений;выполнять команды, возвращающие единственное значение;выполнять команды специального языка определения баз данных DataBase Definition Language (DDL), например CREATE TABLE;работать с объектом DataAdapter, возвращающим объект DataSet;работать с объектом DataReader;для класса SqlCommand – работать с потоком XML;создавать результирующие наборы, построенные на основе нескольких таблиц или в результате исполнения нескольких операторов.
Объект Command обеспечивает управление источником данных, которое заключается:
в выполнении DML (Data Manipulation Language) запросов – запросов, не возвращающих данные (INSERT, UPDATE, DELETE);в выполнении DDL (Data Definition Language) запросов – запросов, которые изменяют структуру Базы Данных (CREATE);в выполнении запросов, возвращающих данные через объект DataReader (SELECT).
Объект представлен двумя классами – SqlCommand и OleDb Command. Позволяет исполнять команды на БД и при этом использует установленное соединение. Исполняемые команды могут быть представлены:
хранимыми процедурами;командами SQL;операторами, возвращающими целые таблицы.
Объектa – представитель класса Command поддерживает два варианта (варианты методов определяются базовым классом) методов:
ExecuteNonQuery – обеспечивает выполнение команд, не возвращающих данные, например INSERT, UPDATE, DELETE;ExecuteScalar – исполняет запросы к БД, возвращающие единственное значение;ExecuteReader – возвращает результирующий набор через объект DataReader.
Доступ к данным в ADO .NET с помощью Data Provider'а осуществляется следующим образом:
Объект – представитель класса Connection устанавливает соединение между БД и приложением.Это соединение становится доступным объектам Command и DataAdapter.При этом объект Command позволяет исполнять команды непосредственно над БД.Если исполняемая команда возвращает несколько значений, Command открывает доступ к ним через объект DataReader.Результаты выполнения команды обрабатываются либо напрямую, с использованием кода приложения, либо через объект DataSet, который заполняется при помощи объекта DataAdapter.Для обновления БД применяют также объекты Command и DataAdapter.
Итак, в любом случае, независимо от выбранного поставщика данных, при работе с данными в ADO .NET используем:
Connection Object – для установки соединения с базой данных;Dataset Object – для представления данных на стороне приложения;Command Object – для изменения состояния базы.
Способы создания объекта Command:
с использованием конструкторов и с последующей настройкой объекта (указание строки запроса и объекта Connection);вызов метода CreateCommand объекта Connection.
Листинг 10: ,
Connection
Объект – представитель класса Connection представляет соединение с источником (базой) данных и обеспечивает подключение к базе данных. Visual Studio .NET поддерживает два класса:
SQLConnection (обеспечивает подключение к SQL Server 7.0 и выше),OleDbConnection (обеспечивает подключение к прочим вариантам БД).
Компонента Connection (независимо от того, представителем какого класса она является) имеет свойство ConnectionString, в котором фиксируется вся необходимая для установления соединения с БД информация. Кроме того, поддерживается ряд методов, позволяющих обрабатывать данные с применением транзакций.
Свойства объекта Connection позволяют:
задавать реквизиты пользователя;указывать расположение источника данных.
Методы объекта позволяют управлять соединением с источником данных.
В процессе соединения с помощью объекта – представителя класса OleDbConnection (аналогично SQLConnection) создается и инициализируется соответствующий объект с использованием одного из вариантов конструктора и строки соединения.
Формирование строки и последовательность действий при инициализации объекта соединения – дело техники. Главное – это чтобы свойство ConnectionString в результате получило бы ссылку на строку символов, содержащую необходимую для установления соединения информацию.
Листинг 9: ,
Constraints
Объекты – представители класса Constraint в наборе Constraints объекта DataTable позволяет задать на множестве объектов DataTable различные ограничения. Например, можно создать объект Constraint, гарантирующий, что значение поля или нескольких полей будут уникальны в пределах DataTable.
DataAdapter
DataAdapter – составная часть провайдера данных. То есть подсоединенная компонента объектной модели ADO .NET. Используется для заполнения объекта DataSet и модификации источника данных. Выполняет функции посредника при взаимодействии БД и объекта DataSet.
Обеспечивает связь между источником данных и объектом DataSet. С одной стороны, база данных, с другой – DataSet. Извлечение данных и заполнение объекта DataSet – назначение DataAdapter'а.
Функциональные возможности DataAdapter'а реализуются за счет:
метода Fill, который изменяет данные в DataSet. При выполнении метода Fill объект DataAdapter заполняет DataTable или DataSet данными, полученными из БД. После обработки данных, загруженных в память, с помощью метода Update можно записать модифицированные записи в БД;метода Update, который позволяет изменять данные в источнике данных с целью достижения обратного соответствия данных в источнике данных по отношению к данным в DataSet.
Фактически, DataAdapter управляет обменом данных и обновлением содержимого источника данных.
DataAdapter представляет набор команд для подключения к базе данных и модификации данных.
Три способа создания DataAdapter:
с помощью окна Server Explorer;с помощью мастера Data Adapter Configuration Wizard;ручное объявление и настройка в коде.
Достойны особого внимания ЧЕТЫРЕ свойства этого класса, фактически представляющие команды БД. Через эти команды объект DataAdapter и воздействует на DataSet и Базу.
SelectCommand – содержит текст (строку sql) или объект команды, осуществляющей выборку данных из БД. При вызове метода Fill эта команда выполняется и заполняет объект DataTable или объект DataSet.InsertCommand – содержит текст (строку sql) или объект команды, осуществляющий вставку строк в таблицу.DeleteCommand – содержит текст (строку sql) или объект команды, осуществляющий удаление строки из таблицы.UpdateCommand – содержит текст (строку sql) или объект команды, осуществляющий обновление значений в БД.
DataColumns
DataColumnCollection задает схему таблицы, определяя тип данных каждой колонки.
В классе DataTable объявлено get-свойство DataColumns, с помощью которого может быть получена коллекция принадлежащих таблице столбцов.
public DataColumnCollection Columns {get;}
Возвращается коллекция объектов – представителей класса DataColumn таблицы. Если у объекта-таблицы нет столбцов, возвращается null.
Объекты – представители класса DataColumn образуют набор DataColumns, который является обязательным элементом каждого объекта – представителя класса DataTable.
Эти объекты соответствуют столбцам таблицы, представленной объектом – представителем класса DataTable.
Объект DataColumn содержит информацию о структуре столбца (метаданные). Например, у этого объекта имеется свойство Type, описывающее тип данных столбца.
Также имеются свойства
ReadOnly,Unique,Default,AutoIncrement,
которые, в частности, позволяют ограничить диапазон допустимых значений поля и определить порядок генерации значений для новых данных.
Объект DataColumn представляет тип колонки в DataTable. Это стандартный блок, предназначенный для построения схемы DataTable.
Каждый объект DataColumn как элемент схемы характеризуется собственным типом, определяющим тип значений, которые DataColumn содержит.
Если объект DataTable создается как отсоединенное хранилище информации, представляющее таблицу базы данных, тип столбца объекта-таблицы должен соответствовать типу столбца таблицы в базе данных.
DataReader
Компонента провайдера, объект – представитель (варианта) класса DataReader.
Предоставляет подключенный к источнику данных набор записей, доступный лишь для однонаправленного чтения.
Позволяет просматривать результаты запроса по одной записи за один раз. Для доступа к значениям столбцов используется свойство Item, обеспечивающее доступ к столбцу по его индексу (то есть ИНДЕКСАТОР!).
При этом метод GetOrdinal объекта – представителя класса DataReader принимает строку с именем столбца и возвращает целое значение, соответствующее индексу столбца.
Непосредственно обращением к конструктору эту компоненту провайдера создать нельзя. Этим DataReader отличается от других компонент провайдера данных.
Объект DataReader создается в результате обращения к одному из вариантов метода ExecuteReader объекта Command (SqlCommand.Execute Reader возвращает ссылку на SqlDataReader, OleDbCommand.Execute Reader возвращает ссылку на OleDbDataReader).
То есть выполняется команда (например, запрос к базе данных), а соответствующий результат получается при обращении к объекту – представителю класса DataReader.
Метод ExecuteReader возвращает множество значений как ОДИН ЕДИНСТВЕННЫЙ ОБЪЕКТ – объект – представитель класса DataReader. Остается только прочитать данные.
Выполняется запрос, получается объект – представитель класса DataReader, который позволяет перебирать записи результирующего набора и... ПЕРЕДАВАТЬ НУЖНЫЕ ЗНАЧЕНИЯ КОДУ ПРИЛОЖЕНИЯ.
При этом DataReader обеспечивает чтение непосредственно из базы и поэтому требует монопольного доступа к активному соединению. DataReader реализован без излишеств. Только ОДНОНАПРАВЛЕННОЕ чтение! Любые другие варианты его использования невозможны.
// Создание объектов DataReader. Пример кода. System.Data.OleDb.OleDbCommand myOleDbCommand; System.Data.OleDb.OleDbDataReader myOleDbDataReader; myOleDbDataReader = myOleDbCommand.ExecuteReader();
System.Data.SqlClient.SqlCommand mySqlCommand; System.Data.SqlClient.SqlDataReader mySqlDataReader; mySqlDataReader = mySqlCommand.ExecuteReader();
DataRows
СОДЕРЖИМОЕ таблицы (непосредственно данные) задается набором DataRows – это конкретное множество строчек таблицы, каждая из которых является объектом – представителем класса DataRow.
Его методы и свойства представлены в таблице.
HasErrors | Возвращает значение, показывающее, есть ли ошибки в строке |
Item | Перегружен. Возвращает или задает данные, сохраненные в указанном столбце. |
В языке C# это свойство является индексатором класса DataRow
AcceptChanges | Сохраняет все изменения, сделанные с этой строкой со времени последнего вызова AcceptChanges |
BeginEdit | Начинает операцию редактирования объекта DataRow |
CancelEdit | Отменяет текущее редактирование строки |
ClearErrors | Удаляет ошибки в строке, включая RowError и ошибки, установленные SetColumnError |
Delete | Удаляет DataRow |
EndEdit | Прекращает редактирование строки |
Equals (унаследовано от Object) | Перегружен. Определяет, равны ли два экземпляра Object |
GetChildRows | Перегружен. Возвращает дочерние строки DataRow |
GetColumnError | Перегружен. Возвращает описание ошибки для столбца |
GetColumnsInError | Возвращает массив столбцов, имеющих ошибки |
GetHashCode (унаследовано от Object) | Служит хэш-функцией для конкретного типа, пригоден для использования в алгоритмах хэширования и структурах данных, например в хэш-таблице |
GetParentRow | Перегружен. Возвращает родительскую строку DataRow |
GetParentRows | Перегружен. Возвращает родительские строки DataRow |
GetType (унаследовано от Object) | Возвращает Type текущего экземпляра |
HasVersion | Возвращает значение, показывающее, существует ли указанная версия |
IsNull | Перегружен. Возвращает значение, показывающее, содержит ли нулевое значение указанный столбец |
RejectChanges | Отменяет все значения, выполненные со строкой после последнего вызова AcceptChanges |
SetColumnError | Перегружен. Устанавливает описание ошибки для столбца |
SetParentRow | Перегружен. Устанавливает родительскую строку DataRow |
ToString (унаследовано от Object) | Возвращает String, который представляет текущий Object |
Finalize (унаследовано от Object) | Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора. |
Посредством набора Rows реализуется возможность ссылки на любую запись таблицы. К любой записи можно обратиться напрямую, и поэтому не нужны методы позиционирования и перемещения по записям таблицы.
В примере используются различные варианты индексации. По множеству строк позиционирование проводится по целочисленному значению индекса. Выбор записи в строке производится по строковому значению, которое соответствует имени столбца.
Пример:
private void PrintValues(DataTable myTable) { // Для каждой строки, которая входит в состав коллекции // строк объекта таблицы... foreach(DataRow myRow in myTable.Rows) { // Для каждой ячейки (столбца) в строке... foreach(DataColumn myCol in myTable.Columns) { // Выдать на консоль ее значение! Console.WriteLine(myRow[myCol]); } } }
DataSet
В рамках отсоединенной модели ADO .NET объект DataSet становится важным элементом технологии отсоединенного доступа. Объект-представитель DataSet ПРЕДСТАВЛЯЕТ МНОЖЕСТВО ТАБЛИЦ.
Для успешного решения задачи представления в DataSet'е есть все необходимое. Его функциональные возможности позволяют загрузить в локальное хранилище на стороне приложения данные из любого допустимого для ADO .NET источника: SQL Server, Microsoft Access, XML-файл.
В числе данных – членов этого класса имеется набор Tables. Объект DataSet может содержать таблицы, количество которых ограничивается лишь возможностями набора Tables.
Для каждой таблицы – элемента набора Tables может быть (и, естественно, должна быть) определена структура таблицы. В случае, когда приложение взаимодействует с реальной базой данных, структура таблиц в DataSet'е должна соответствовать структуре таблиц в базе данных. DataSet – это находящийся в памяти объект ADO .NET, используемый в приложении для представления данных; он определяет согласованную реляционную модель базы данных, которая не зависит от источника содержащихся в нем данных. Степень полноты модели определяется задачами, которые решает приложение.
Объект DataSet может представлять абсолютно точную модель базы данных, и в таком случае эта модель должна будет включать полный набор структурных элементов базы данных, включая таблицы, содержащие данные, с учетом установленных ограничений и отношений между таблицами.
Содержащуюся в объекте DataSet информацию можно изменять независимо от источника данных (от самой БД). Соответствующие значения формируются непосредственно в программе и добавляются в таблицы.
При работе с базой данных данные могут собираться из разных таблиц, локальное представление которых обеспечивается различными объектами – представителями классов DataSet. В классе DataSet определено множество перегруженных методов Merge, которые позволяют объединять содержимое нескольких объектов DataSet.
Любой объект-представитель класса DataSet позволяет организовать чтение и запись содержимого (теоретически – информации из базы) в файл или область памяти. При этом можно читать и сохранять:
только содержимое объекта (собственно информацию из базы);только структуру объекта – представителя класса DataSet;полный образ DataSet (содержимое и структуру).
Таким образом, DataSet является основой для построения различных вариантов отсоединенных объектов – хранилищ информации.
Класс DataSet – класс не абстрактный и не интерфейс. Это значит, что существует множество вариантов построения отсоединенных хранилищ.
На основе базового класса DataSet можно определять производные классы определенной конфигурации, которая соответствует структуре базы данных.
Можно также создать объект – представитель класса DataSet оригинальной конфигурации и добавить непосредственно к этому объекту все необходимые составляющие в виде таблиц (объектов – представителей класса Table) соответствующей структуры и множества отношений Relation.
Объект – представитель класса DataSet и сам по себе, без сопутствующего окружения, представляет определенную ценность. Дело в том, что информация, представляемая в приложении в виде таблиц, НЕ ОБЯЗЯТЕЛЬНО должна иметь внешний источник в виде реальной базы данных. Ничто не мешает программисту обеспечить в приложении чтение обычного "плоского" файла или даже "накопить" необходимую информацию посредством интерактивного взаимодействия с пользователем, используя при этом обычный диалог. В конце концов, база данных – это один из возможных способов ОРГАНИЗАЦИИ информации (а не только ее хранения!). Не случайно DataSet представляет ОТСОЕДИНЕННЫЕ данные.
На DataSet работают все ранее перечисленные компоненты ADO .NET.
В свою очередь, в приложении, обеспечивающем взаимодействие с базой данных, объект DataSet функционирует исключительно за счет объекта DataAdapter, который обслуживает DataSet.
При этом DataAdapter является центральным компонентом архитектуры отсоединенного доступа.
DataSet в свободном полете
Листинг 7: ,
Естественно, что данная последовательность действий может повторяться сколь угодно много раз, пока не будут созданы и не заполнены все члены объекта DataSet.
Далее будет рассмотрен пример использования объекта DataSet для прочтения информации из текстового файла. При этом используется метод класса string Split(), который обеспечивает выделение из исходной строки подстрок с их последующим преобразованием в символьные массивы. Критерием выделения подстроки является массив символов-разделителей или целочисленное значение, определяющее максимальную длину подстроки.
DataTable
Каждый объект DataTable представляет одну таблицу базы данных. Таблица в каждый конкретный момент своего существования характеризуется:
СХЕМОЙ таблицы,СОДЕРЖИМЫМ таблицы (информацией).
При этом СХЕМА таблицы (структура объекта DataTable) определяется двумя наборами:
множеством столбцов таблицы (набор DataColumns, состоящий из множества объектов DataColumn),множеством ограничений таблицы (набор Constraints, состоящий из множества объектов Constraint).
DataView
Объекты – представители класса DataView НЕ ПРЕДНАЗНАЧЕНЫ для организации визуализации объектов DataTable.
Их назначение – простой последовательный доступ к строкам таблицы. Объекты DataView являются средством перебора записей таблицы. При обращении ЧЕРЕЗ объект DataView к таблице получают данные, которые хранятся в этой таблице.
DataView нельзя рассматривать как таблицу. DataView не может обеспечить представление таблиц. Также DataView не может обеспечить исключения и добавления столбцов. Таким образом, DataView НЕ является средством преобразования исходной информации, зафиксированной в таблице.
После создания объекта DataView и его настройки на конкретную таблицу появляется возможность перебора записей, их фильтрации, поиска и сортировки.
DataView предоставляет средства динамического представления набора данных, к которому можно применить различные вырианты сортировки и фильтрации на основе критериев, обеспечиваемых базой данных.
Класс DataView обладает большим набором свойств, методов и событий, что позволяет с помощью объекта – представителя класса DataView создавать различные представления данных, содержащихся в DataTable.
Используя этот объект, можно представлять содержащиеся в таблице данные в соответствии с тем или иным порядком сортировки, а также организовать различные варианты фильтрации данных.
DataView предоставляет динамический взгляд на содержимое таблицы в зависимости от установленного в таблице порядка представления и вносимых в таблицы изменений.
Функционально реализация DataView отличается от метода Select, определенного в DataTable, который возвращает массив DataRow (строк).
Для управления установками представления для всех таблиц, входящих в DataSet, используется объект – представитель класса DataViewManager.
DataViewManager предоставляет удобный способ управления параметрами настройки представления по умолчанию для каждой таблицы.
Доступ к отсоединенным данным
В приложениях, работающих с базами данных, до недавних пор применялся доступ к данным через постоянное соединение с источником данных. Приложение открывало соединение с базой данных и не закрывало его по крайней мере до завершения работы с источником данных. В это время соединение с источником поддерживалось постоянно.
Недостатки такого подхода стали выявляться после появления приложений, ориентированных на Интернет.
Соединения с базой данных требуют выделения системных ресурсов, и если база данных располагается на сервере, то при большом количестве клиентов это может быть критично для сервера. Хотя постоянное соединение и позволяет немного ускорить работу приложения, общий убыток от растраты системных ресурсов преимущество в скорости выполнения приложения сводит на нет.
Факт плохого масштабирования приложений с постоянным соединением известен давно. Соединение с парой клиентов обслуживается приложением хорошо, 10 клиентов обслуживаются хуже, 100 – много хуже...
В ADO .NET используется другая модель доступа – доступ к отсоединенным данным. При этом соединение устанавливается лишь на то время, которое необходимо для проведения определенной операции над базой данных.
Модель доступа – модель компромиссная. В ряде случаев она проигрывает по производительности традиционной модели, и для этих случаев рекомендуется вместо ADO .NET использовать ADO.
И еще более простые шаги
На самом деле разработка простой формы для работы с базой данных требует еще меньше усилий. Можно совсем ничего не делать и получить готовую форму.
После создания объекта-представителя класса DataSet надо всего лишь "перетащить" на форму из окна Data Sources пиктограмму соответствующей таблицы базы данных. В случае с базой данных "Борей" – это пиктограмма таблицы "Клиенты". При этом автоматически к коду приложения добавляются соответствующие классы и элемент управления для навигации по таблице "Клиенты". Для визуализации множества записей, связанных с заказами клиентов, следует проделать следующие манипуляции:
"Раскрыть" в окне Data Sources пиктограмму, обозначающую таблицу "Клиенты". При этом становятся видимыми пиктограммы, отображающие столбцы таблицы, и пиктограмма связанной с таблицей "Клиенты" таблицы "Заказы". На форму следует перетащить эту самую пиктограмму. В результате получаем форму с парой объектов DataGrid, в которых можно наблюдать согласованные множества записей. Для каждого клиента наблюдаем множество заказов.
Разработчику приложения остается упражняться в вариантах перетаскивания пиктограмм (перетаскивать можно пиктограммы отдельных полей таблицы) и медитировать над полученным кодом.
Имитация отсоединенности. Пул соединений
В ADO .NET официально заявлено, что в приложении используются ОТСОЕДИНЕННЫЕ компоненты.
Присоединились, каким-то образом получили требуемую информацию, отсоединились... Но есть проблема. Открытие и закрытие соединения с БД – операция трудоемкая. Поддерживать соединение постоянно – плохо. Обрывать и восстанавливать соединение всякий раз по мере необходимости – тоже получается плохо. Компромиссное решение – ПУЛ соединений: место, где сохраняются установленные и неиспользуемые в данный момент соединения.
Приложение создает объект соединения, устанавливает соединение с базой, использует его и, не разрывая соединения с базой, передает его в ПУЛ соединений. В дальнейшем, по мере необходимости, объект соединения используется из пула. Если несколько приложений стремятся одновременно получить доступ к БД и в пуле не остается свободных соединений – создается и настраивается новый объект, который после употребления приложением также передается в пул. Таким образом поддерживается несколько готовых к использованию соединений, общее количество которых (по крайней мере теоретически) оказывается меньше общего количества приложений, работающих с базой в данный момент.
Пул включается по умолчанию. И при этом выполнение оператора
rollsConnection.Close();
приводит НЕ К РАЗРЫВУ соединения с базой, а к передаче этого соединения в пул соединений для повторного использования. Запретить размещение объекта соединения в пуле можно, указав в строке соединения для OLE DB .NET атрибут
OLE DB Services = –4
при котором провайдер OLE DB .NET не будет помещать соединение в пул при закрытии, а будет всякий раз его разрывать и устанавливать заново.
Пул соединений освобождается при выполнении метода Dispose. Этот метод вызывается сборщиком мусора при завершении приложения. В теле этого метода обеспечивается вызов метода Close. Таким образом, даже незакрытое соединение при завершении приложения закрывается. Явный вызов этого метода также приводит к разрыву соединения, освобождению занимаемых ресурсов и подготовке объекта к уничтожению сборщиком мусора.
На самом деле, если класс предоставляет метод Dispose(), именно его, а не Close(), следует вызывать для освобождения занимаемых объектом ресурсов.
Использование объекта DataReader
Обеспечение так называемого "доступа к ОТСОЕДИНЕННЫМ данным" – заслуга объекта DataReader. Дело в том, что получение данных приложением из базы данных все равно требует установления соединения, и это соединение должно быть максимально коротким по продолжительности и эффективным — быстро соединиться, быстро прочитать и запомнить информацию из базы, быстро разъединиться. Именно для этих целей используется в ADO .NET объект DataReader.
После получения ссылки на объект DataReader можно организовать просмотр записей. Для получения необходимой информации из базы данных этого достаточно.
У объекта DataReader имеется "указатель чтения", который устанавливается на первую запись результирующего набора записей, образовавшегося в результате выполнения метода ExecuteReader(). Очередная (в том числе и первая) запись набора становится доступной в результате выполнения метода Read().
В случае успешного выполнения этого метода указатель переводится на следующий элемент результирующей записи, а метод Read() возвращает значение true.
В противном случае метод возвращает значение false. Все это позволяет реализовать очень простой и эффективный механизм доступа к данным, например в рамках цикла while.
Если иметь в виду, что каждая запись состоит из одного и того же количества полей, которые к тому же имеют различные идентификаторы, то очевидно, что доступ к значению отдельного поля становится возможным через индексатор, значением которого может быть как значение индекса, так и непосредственно обозначающий данное поле идентификатор:
while (myDataReader.Read()) { object myObj0 = myDataReader[5]; object myObj1 = myDataReader["CustomerID"]; }
Важно!
При таком способе доступа значения полей представляются ОБЪЕКТАМИ. Хотя, существует возможность получения от DataReader'а и типизированных значений.
Следует иметь в виду, что DataReader удерживает монопольный доступ к активному соединению. Вот как всегда! Пообещали отсоединенный доступ к данным, а получаем постоянное соединение! Закрывается соединение методом Close():
myDataReader.Close();
Можно также настроить DataReader таким образом, чтобы закрытие соединения происходило автоматически, без использования команды Close(). Для этого при вызове метода ExecuteReader свойство объекта команды CommandBehavior должно быть выставлено в CloseConnection.
Пример. Выборка столбца таблицы с помощью объекта DataReader. Предполагается наличие объекта OleDbCommand под именем myOleDb Command. Свойство Connection этого объекта определяет соединение с именем myConnection.
Итак:
// Активное соединение открыто. MyConnection.Open(); System.Data.OleDb.OleDbDataReader myReader = myOleDbCommand.ExecuteReader(); while (myReader.Read()) { Console.WriteLine(myReader["Customers"].ToString()); } myReader.Close(); // Активное соединение закрыто. MyConnection.Close();
Изменение данных в DataTable и состояние строки таблицы
Основной контроль за изменениями данных в таблице возлагается на строки – объекты класса DataRow.
Для строки определены несколько состояний, которые объявлены в перечислении RowState. Контроль за сохраняемой в строках таблицы информацией обеспечивается посредством определения состояния строки, которое обеспечивается одноименным (RowState) свойством – членом класса DataRow.
Unchanged | Строка не изменялась со времени загрузки при помощи метода Fill() либо с момента вызова метода AcceptChanges() |
Added | Строка была добавлена в таблицу, но метод AcceptChanges() еще не вызывался |
Deleted | Строка была удалена из таблицы, но метод AcceptChanges() еще не вызывался |
Modified | Некоторые из полей строки были изменены, но метод AcceptChanges() еще не вызывался |
Detached | Строка НЕ ЯВЛЯЕТСЯ ЭЛЕМЕНТОМ КОЛЛЕКЦИИ DataRows. Ее создали от имени таблицы, но не подключили |
Извлечение типизированных данных
Среди множества методов классов DataReader (SqlDataReader и OleDbDataReader) около десятка методов, имена который начинаются с приставки Get..., следом за которой – имя какого-то типа. GetInt32, GetBoolean, ...
С помощью этих методов от DataReader можно получить и типизированные значения, а не только объекты базового типа!
int CustomerID; string Customer; // Определить порядковый номер поля 'CustomerID' CustomerID = myDataReader.GetOrdinal("CustomerID"); // Извлечь строку из этого поля и прописать ее в переменную Customer Customer = myDataReader.GetString(CustomerID);
Настройка команд
Манипулирование данными, которое осуществляется в процессе выполнения команд, естественно, требует определенной информации, которая представляется в команде в виде параметров. При этом характер и содержание управляющей дополнительной информации зависит от конкретного состояния базы на момент выполнения приложения.
Это означает, что настройка команды должна быть проведена непосредственно в процессе выполнения приложения. Именно во время выполнения приложения определяются конкретные значения параметров, проводится соответствующая настройка команд и их выполнение.
В структуре команды предусмотрены так называемые ПОЛЯ ПОДСТАНОВКИ. Объект команды при выполнении приложения настраивается путем присвоения значения параметра полю подстановки. Эти самые поля подстановки реализованы в виде свойства Parameters объекта команды. В зависимости от типа провайдера каждый параметр представляется объектом одного из классов: OleDbParameter или SqlParameter.
Необходимое количество параметров для выполнения той или иной команды зависит от конкретных обстоятельств: каждый параметр команды представляется собственным объектом-параметром. Кроме того, если команда представляется сохраняемой процедурой, то может потребоваться дополнительный объект-параметр.
Во время выполнения приложения объект команды предварительно настраивается путем присваивания свойству Parameters списка (вполне возможно, состоящего из одного элемента) соответствующих значений параметров – объектов класса Parameters. При выполнении команды эти значения параметров считываются и либо непосредственно подставляются в шаблон оператора языка SQL, либо передаются в качестве параметров хранимой процедуре.
Параметр команды является объектом довольно сложной структуры, что объясняется широким диапазоном различных действий, которые могут быть осуществлены над базой данных.
Кроме того, работа с конкретной СУБД также накладывает свою специфику на правила формирования объекта команды.
Оглавление
ADO .NET (ActiveX Data Objects .NET) является набором классов, реализующих программные интерфейсы для облегчения подключения к базам данных из приложения независимо от особенностей реализации конкретной системы управления базами данных и от структуры самой базы данных, а также независимо от места расположения этой самой базы — в частности, в распределенной среде (клиент-серверное приложение) на стороне сервера.
ADO .NET широко используется совместно с технологией web-программирования с использованием объектов ASP .NET для доступа к расположенным на сервере базам данных со стороны клиента.
Особенность изложения материала этой главы заключается в следующем.
Решение даже самой простой задачи, связанной с данными, предполагает использование множества разнообразных объектов – представителей классов ADO .NET, которые находятся между собой в достаточно сложных взаимоотношениях. Из-за этих отношений строго последовательное описание элементов ADO .NET представляется весьма проблематичным. С какого бы элемента ни начиналось описание, всегда предполагается предварительное представление о множестве других элементов.
По этой причине часто упоминание и даже примеры использования некоторых классов предшествуют их подробному описанию.
Основы ADO .NET Лекция из курса «Введение в программирование на C# 2.0»
Марченко Антон Леонардович
Интернет-Университет Информационных Технологий, INTUIT.ru
Отступление о запросах
Запросы, которые не возвращают записей (action query или КОМАНДНЫЕ ЗАПРОСЫ). Различаются: запросы обновления или Data Manupulation Language queries. Предназначаются для изменения содержимого базы данных UPDATE Customers Set CompanyName = 'NewHappyName' WHERE CustomerID = '007'
INSERT INTO Customers (CustomerID, CompanyName) VALUES ('007', 'NewHappyCustomer')
DELETE FROM Customers WHERE CustomerID = '007'запросы изменения или Data Definition Language queries. Предназначены для изменения структуры базы данных CREATE TABLE myTable ( Field1 int NOT NULL Field2 varchar() ) Запросы, возвращающие значения из базы данных. Ниже представлены три примера запросов.
Возвращает значения полей для всех записей, представленных в таблице Customers. SELECT CustomerID, CompanyName, ContactName, Phone FROM Customers
Возвращает значения полей для записей, представленных в таблице Customers, у которых значение поля Phone равно строке '333–2233'. SELECT CustomerID, CompanyName, ContactName FROM Customers WHERE Phone = '222–3322'
Параметризованный запрос. Множество возвращаемых значений зависит от значения параметра, стандартно обозначаемого маркером '?' и замещаемого непосредственно при выполнении запроса: SELECT CompanyName, ContactName, Phone FROM Customers WHERE CustomerID = ?
Parameter
Данные из базы выбираются в результате выполнения объекта Command. В ADO .NET применяются параметризованные команды. Объект Parameter является средством модификации команд. Представляет свойства и методы, позволяющие определять типы данных и значения параметров.
Подключение к БД на этапе разработки приложения
Для продолжения экспериментов воспользуемся базой-примером "Северные ветры".
Для внешнего окружения – это всего лишь файл с расширением .mdb.
В нашем случае –
E:\Program Files\Microsoft Office\Office10\Samples\Борей.mdb.
Ближайшей задачей будет разработка простого Windows-приложения, обеспечивающего просмотр содержащейся в базе информации о клиентах и их заказах.
Внешний вид формы приложения будет представлен несколькими текстовыми полями, в которых будет отображаться информация о клиенте, а также элементом DataGrid, где будет отображаться информация о связанных с клиентом заказах.
Один из возможных вариантов создания соединения средствами встроенных мастеров предполагает следующую последовательность шагов в рамках Visual Studio .NET 2005. Непосредственно перед началом работ по созданию приложения для наглядности рекомендуется держать открытым окно с информацией о базе данных (пункты меню Data.Show Data Sources):
для нового приложения выполняются действия, связанные с созданием объекта – представителя класса DataSet (Data, Add New Data Source). При этом осуществляются действия по установлению и тестированию соединения с базой данных, требуется ответить на вопрос по поводу возможности копирования информации из базы в директорию приложения (речь идет о локальной копии базы);с использованием инструмента "Add Connection" объявляется тип источника данных (Microsoft Access Database File (OLE DB)) и определяется имя файла базы данных (файл "Борей.mdb"). Волшебник предоставляет возможность непосредственного тестирования устанавливаемого соединения; в результате создается объект – представитель класса DataSet, построенный и настроенный применительно к данному приложению для работы с базой данных. О сложности этой конструкции можно судить по объему программного кода, подсоединяемого к проекту;для исследования и редактирования его свойств DataSet предусмотрено средство DataSet Designer; свидетельством успешного установления соединения является возможность выполнения действия "Edit DataSet with designer", в результате которого в окошке NorthWinds.xsd визуализируется полная схема базы данных, включая таблицы и отношения между ними.
При этом в кодах приложения размещается соответствующий программный код, который обеспечивает создание объекта соединения соответствующего типа и с определенными параметрами.
Объект – представитель класса DataSet можно расположить на форме в виде компоненты. Объявление класса (включая строку соединения) можно будет попытаться проанализировать (файл имеет объем около 9000 строк), открыв файл БорейDataSet.Designer.sc.
Таким образом, процедура установления соединения с базой данных на этапе создания приложения целиком обеспечивается средствами "волшебников" в рамках работы по созданию приложения.
Подсоединенные объекты модели ADO .NET. Провайдеры
Поставщик данных для приложения (Провайдер) – объект, предназначенный для обеспечения взаимодействия приложения с хранилищем информации (базами данных).
Естественно, приложению нет никакого дела до того, где хранится и как извлекается потребляемая приложением информация. Для приложения источником данных является тот, кто передает данные приложению. И как сам этот источник эту информацию добывает – никого не касается.
Источник данных (Data Provider) – это набор взаимосвязанных компонентов, обеспечивающих доступ к данным. Функциональность и само существование провайдера обеспечивается набором классов, специально для этой цели разработанных.
ADO .NET поддерживает два типа источников данных, соответственно, два множества классов:
SQL Managed Provider (SQL Server.NET Data Provider) – для работы с Microsoft SQL Server 7.0 и выше. Работает по специальному протоколу, называемому TabularData Stream (TDS) и не использует ни ADO, ни ODBC, ни какую-либо еще технологию. Ориентированный специально на MS SQL Server, протокол позволяет увеличить скорость передачи данных и тем самым повысить общую производительность приложения;ADO Managed Provider (OleDb.NET Data Provider) – для всех остальных баз данных. Обеспечивает работу с произвольными базами данных. Однако за счет универсальности есть проигрыш по сравнению с SQL Server Provider, так что при работе с SQL Server рекомендовано использовать специализированные классы.
В следующих разделах приводится описание составных элементов провайдера.
Получение возвращаемого значения
Сохраняемые процедуры могут обеспечить передачу возвращаемого значения функции приложения, которое обеспечило их вызов. Это передача может быть обеспечена непосредственно параметром при установке свойства Direction параметра в Output или InputOutput либо за счет непосредственного возвращения значения сохраняемой процедурой, при котором используется параметр со свойством, установленным в ReturnValue.
Получение значений, возвращаемых сохраняемой процедурой.
Использование параметра.
Для этого следует создать параметр с Direction-свойством, установленным в Output или InputOutput (если параметр используется в процедуре как для получения, так и для отправления значений). Очевидно, что тип параметра должен соответствовать ожидаемому возвращаемому значению.
После выполнения процедуры можно прочитать значение возвращаемого параметра.
Непосредственный перехват возвращаемого значения сохраняемой процедурой.
Для этого следует создать параметр с Direction-свойством, установленным в ReturnValue. Такой параметр должен быть первым в списке параметров.
При этом тип параметра должен соответствовать ожидаемому возвращаемому значению.
Предложения SQL – Update, Insert, and Delete возвращают целочисленное значение, соответствующее количеству записей, на которые повлияло выполнение данного предложения.
Это значение может быть получено как возвращаемое значение метода ExecuteNonQuery.
Следующий пример демонстрирует, как получить возвращаемое значение, возвращаемое хранимой процедурой CountAuthors. В этом случае предполагается, что первый параметр списка параметров конфигурируется как возвращаемый:
int cntAffectedRecords; // The CommandText and CommandType properties can be set // in the Properties window but are shown here for completeness. oleDbcommand1.CommandText = "CountAuthors"; oleDbCommand1.CommandType = CommandType.StoredProcedure; oleDbConnection1.Open(); oleDbCommand1.ExecuteNonQuery(); oleDbConnection1.Close(); cntAffectedRecords = (int)(OleDbCommand1.Parameters["retvalue"].Value); MessageBox.Show("Affected records = " + cntAffectedRecords.ToString());
Применение объекта соединения для исследования схемы базы
Установив соединение с базой данных, можно исследовать ее схему. Эта возможность обеспечивается объявленным в классе соединения методом GetOleDbSchemaTable.
Управление процессом сбора информации о схеме базы обеспечивается двумя параметрами метода.
Первый параметр представлен статическими свойствами класса OleDbSchemaGuid.
Второй параметр – массивом (кортежем) объектов, каждый из которых в зависимости от занимаемой позиции в кортеже может принимать определенное фиксированное значение либо иметь значение null:
Класс OleDbSchemaGuid. Определение схемы базы Namespace: System.Data.OleDb
При вызове метода GetOleDbSchemaTable значение – параметр типа OleDbSchemaGuid обеспечивает возвращение типа схемы таблицы.
Информация о структуре класса представлена ниже.
Следует иметь в виду, что не все провайдеры полностью поддерживают предоставляемые классом возможности по определению схемы таблицы.
Члены класса OleDbSchemaGuid:
OleDbSchemaGuid-конструктор | Инициализирует новый экземпляр класса OleDbSchemaGuid |
Assertions | Статический. Возвращает утверждения, определенные в каталоге, владельцем которого является указанный пользователь |
Catalogs | Статический. Возвращает физические атрибуты, связанные с каталогами, доступными из источника данных. Возвращает утверждения, определенные в каталоге и принадлежащие указанному пользователю |
Character_Sets | Статический. Возвращает наборы символов, определенные в каталоге и доступные указанному пользователю |
Check_Constraints | Статический. Возвращает ограничения проверки, определенные в каталоге и принадлежащие указанному пользователю |
Check_Constraints_By_Table | Статический. Возвращает ограничения проверки, определенные в каталоге и принадлежащие указанному пользователю |
Collations | Статический. Возвращает сравнения знаков, определенные в каталоге и доступные указанному пользователю |
Columns | Статический. Возвращает столбцы таблиц (включая представления), определенные в каталоге и доступные указанному пользователю |
Column_Domain_Usage | Статический. Возвращает столбцы, определенные в каталоге и зависящие от домена, который определен в каталоге и принадлежит указанному пользователю |
Column_Privileges | Статический. Возвращает привилегии для столбцов таблиц, определенные в каталоге и доступные указанному пользователю или предоставленные им |
Constraint_Column_Usage | Статический. Возвращает столбцы, которые используются ссылочными ограничениями, уникальными ограничениями и утверждениями, определенными в каталоге и принадлежащими указанному пользователю |
Constraint_Table_Usage | Статический. Возвращает таблицы, которые используются ссылочными ограничениями, уникальными ограничениями и утверждениями, определенными в каталоге и принадлежащими указанному пользователю |
DbInfoLiterals | Статический. Возвращает список литералов, используемых в текстовых командах и специфичных для конкретного поставщика |
Foreign_Keys | Статический. Возвращает столбцы внешнего ключа, определенные в каталоге данным пользователем |
Indexes | Статический. Возвращает индексы, определенные в каталоге и принадлежащие указанному пользователю |
Key_Column_Usage | Статический. Возвращает столбцы, определенные в каталоге и ограниченные как ключи данным пользователем |
Primary_Keys | Статический. Возвращает столбцы первичного ключа, определенные в каталоге данным пользователем |
Procedures | Статический. Возвращает процедуры, определенные в каталоге и принадлежащие указанному пользователю |
Procedure_Columns | Статический. Возвращает сведения о столбцах наборов строк, возвращаемых процедурами |
Procedure_Parameters | Статический. Возвращает сведения о параметрах и кодах возврата процедур |
Provider_Types | Статический. Возвращает основные типы данных, поддерживаемые поставщиком данных .NET Framework для OLE DB |
Referential_Constraints | Статический. Возвращает ссылочные ограничения, определенные в каталоге и принадлежащие указанному пользователю |
Schemata | Статический. Возвращает объекты схемы, принадлежащие указанному пользователю |
Sql_Languages | Статический. Возвращает уровни соответствия, параметры и диалекты, поддерживаемые данными обработки с помощью реализации SQL |
Statistics | Статический. Возвращает статистические данные, определенные в каталоге и принадлежащие указанному пользователю |
Tables | Статический. Возвращает таблицы (включая представления), определенные в каталоге и доступные указанному пользователю |
Tables_Info | Статический. Возвращает таблицы (включая представления), доступные указанному пользователю |
Table_Constraints | Статический. Возвращает табличные ограничения, определенные в каталоге и принадлежащие указанному пользователю |
Table_Privileges | Статический. Возвращает привилегии для таблиц, определенные в каталоге и доступные указанному пользователю или предоставленные им |
Table_Statistics | Статический. Описывает доступный набор статистических данных по таблицам для поставщика |
Translations | Статический. Возвращает переводы знаков, определенные в каталоге и доступные указанному пользователю |
Trustee | Статический. Определяет доверенные объекты, заданные в источнике данных |
Usage_Privileges | Статический. Возвращает привилегии USAGE для объектов, определенные в каталоге и доступные указанному пользователю или предоставленные им |
Views | Статический. Возвращает представления, определенные в каталоге и доступные указанному пользователю |
View_Column_Usage | Статический. Возвращает столбцы, от которых зависят просматриваемые таблицы, определенные в каталоге и принадлежащие данному пользователю |
View_Table_Usage | Статический. Возвращает таблицы, от которых зависят просматриваемые таблицы, определенные в каталоге и принадлежащие данному пользователю |
Equals | Перегружен. Определяет, равны ли два экземпляра Object |
GetHashCode | Служит хэш- функцией для конкретного типа, пригоден для использования в алгоритмах хэширования и структурах данных, например в хэш-таблице |
GetType | Возвращает Type текущего экземпляра |
ToString | Возвращает String, который представляет текущий Object |
Finalize | Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора. |
// Применение метода GetOleDbSchemaTable. // Получение информации о схеме базы данных. // При этом можно использовать параметр Restrictions, // с помощью которого можно фильтровать возвращаемые сведения // о схеме. private void schemaButton_Click(object sender, System.EventArgs e) { // Точной информацией о том, что собой представляют остальные // члены множества Restrictions, пока не располагаю. object[] r; r = new object[] {null, null, null, "TABLE"}; oleDbConnection.Open(); DataTable tbl; tbl=oleDbConnection.GetOleDbSchemaTable (System.Data.OleDb.OleDbSchemaGuid.Tables, r); rpDataGrid.DataSource = tbl; oleDbConnection1.Close(); }
Таким образом получается информация о схеме базы, связь с которой устанавливается через объект соединения.
Но основная область применения объекта соединения – в составе команды.
Добавляем RowChanging event handler для
using System; using System.Data; namespace DataRowsApplication00 { class DataTester { DataTable custTable; public void DTBuild() { custTable = new DataTable("Customers"); // Добавляем столбики. custTable.Columns.Add("id", typeof(int)); custTable.Columns.Add("name", typeof(string)); // Определяем первичный ключ. custTable.Columns["id"].Unique = true; custTable.PrimaryKey = new DataColumn[] { custTable.Columns["id"] }; // Добавляем RowChanging event handler для нашей таблицы. custTable.RowChanging += new DataRowChangeEventHandler(Row_Changing); // Добавляем a RowChanged event handler для нашей таблицы. custTable.RowChanged += new DataRowChangeEventHandler(Row_Changed); } public void RowsAdd(int id) { int x; // Добавляем строки. for (x = 0; x < id; x++) { custTable.Rows.Add(new object[] { x, string.Format("customer{0}", x) }); } // Фиксируются все изменения, которые были произведены над таблицей // со времени последнего вызова AcceptChanges. custTable.AcceptChanges(); } public void RowsChange() { // Изменяем значение поля name во всех строках. // Все имена убираются, а на их место // подставляется буквосочетание, состоящее из // префикса vip и старого значения строки каждого клиента. // Была строка customer5 – стала vip customer5. foreach (DataRow row in custTable.Rows) { row["name"] = string.Format("vip {0}", row["id"]); } } // И после вмешательства результаты становятся известны // обработчику события Row_Changing. А толку-то... private static void Row_Changing(object sender, DataRowChangeEventArgs e) { Console.WriteLine("Row_Changing Event: name={0}; action={1}", e.Row["name"], e.Action); } // Аналогично устроен обработчик Row_Changed. private static void Row_Changed(object sender, DataRowChangeEventArgs e) { Console.WriteLine("Row_Changed Event: name={0}; action={1}", e.Row["name"], e.Action); } } class Program { static void Main(string[] args) { DataTester dt = new DataTester(); dt.DTBuild(); dt.RowsAdd(10); dt.RowsChange(); } } } |
Листинг 18.1. |
using System; using System.Data; namespace DataColumnsApplication00 { class DataTester { DataTable custTable; public void DTBuild() { custTable = new DataTable("Customers"); // Добавляем столбики. custTable.Columns.Add("id", typeof(int)); custTable.Columns.Add("name", typeof(string)); // Определяем первичный ключ. custTable.Columns["id"].Unique = true; custTable.PrimaryKey = new DataColumn[] { custTable.Columns["id"] }; // Добавление события ColumnChanging handler для таблицы. custTable.ColumnChanging += new DataColumnChangeEventHandler(Column_Changing); // Добавление события ColumnChanged event handler для таблицы. custTable.ColumnChanged += new DataColumnChangeEventHandler(Column_Changed); } public void RowsAdd(int id) { int x; // Добавляем строки. for (x = 0; x < id; x++) { custTable.Rows.Add(new object[] { x, string.Format("customer{0}", x) }); } // Фиксируются все изменения, которые были произведены над таблицей // со времени последнего вызова AcceptChanges. custTable.AcceptChanges(); } public void RowsChange() { // Изменяем значение поля name во всех строках. foreach (DataRow row in custTable.Rows) { row["name"] = string.Format("vip{0}", row["id"]); } } // И после вмешательства результаты становятся известны // обработчику события Column_Changing. А толку-то... private static void Column_Changing (object sender, DataColumnChangeEventArgs e) { Console.WriteLine ("Column_Changing Event: name={0}; Column={1}; proposed name={2}", e.Row["name"], e.Column.ColumnName, e.ProposedValue); } // Аналогично устроен обработчик Column_Changed. private static void Column_Changed (object sender, DataColumnChangeEventArgs e) { Console.WriteLine ("Column_Changed Event: name={0}; Column={1}; proposed name={2}", e.Row["name"], e.Column.ColumnName, e.ProposedValue); } } class Program { static void Main(string[] args) { DataTester dt = new DataTester(); dt.DTBuild(); dt.RowsAdd(10); dt.RowsChange(); } } } |
Листинг 18.2. |
using System; using System.Data; namespace Rolls01 { // Работа с таблицей: // определение структуры таблицы, // сборка записи (строки таблицы), // добавление новой записи в таблицу, // индексация записей, // выбор значения поля в строке, // изменение записи. public class RollsData { public DataTable rolls; int rollIndex; public RollsData() { rollIndex = 0; // Создается объект "таблица". rolls = new DataTable("Rolls"); // Задаются и подсоединяются столбики, которые определяют тип таблицы. // Ключевой столбец. DataColumn dc = rolls.Columns.Add("nRoll",typeof(Int32)); dc.AllowDBNull = false; dc.Unique = true; //rolls.PrimaryKey = dc; // Прочие столбцы, значения которых определяют физические // характеристики объектов. rolls.Columns.Add("Victim",typeof(Int32)); // Значения координат. rolls.Columns.Add("X", typeof(Single)); rolls.Columns.Add("Y", typeof(Single)); // Старые значения координат. rolls.Columns.Add("lastX", typeof(Single)); rolls.Columns.Add("lastY", typeof(Single)); // Составляющие цвета. rolls.Columns.Add("Alpha", typeof(Int32)); rolls.Columns.Add("Red", typeof(Int32)); rolls.Columns.Add("Green", typeof(Int32)); rolls.Columns.Add("Blue", typeof(Int32)); } // Добавление записи в таблицу. public void AddRow(int key, int victim, float x, float y, float lastX, float lastY, int alpha, int red, int green, int blue) { // Новая строка создается от имени таблицы, // тип которой определяется множеством ранее // добавленных к таблице столбцов. Подобным образом // созданная строка содержит кортеж ячеек // соответствующего типа. DataRow dr = rolls.NewRow(); // Заполнение ячеек строки. dr["nRoll"] = key; dr["Victim"] = victim; dr["X"] = x; dr["Y"] = y; dr["lastX"] = lastX; dr["lastY"] = lastY; dr["Alpha"] = alpha; dr["Red"] = red; dr["Green"] = green; dr["Blue"] = blue; // Созданная и заполненная строка // подсоединяется к таблице. rolls.Rows.Add(dr); } // Выборка значений очередной строки таблицы. // Ничего особенного. Для доступа к записи (строке) используются // выражения индексации по отношению к множеству Rows. // Для доступа к полю выбранной записи используются // "индексаторы-идентификаторы". public void NextRow(ref rPoint p) { p.index = (int)rolls.Rows[rollIndex]["nRoll"]; p.victim = (int)rolls.Rows[rollIndex]["Victim"]; p.p.X = (float)rolls.Rows[rollIndex]["X"]; p.p.Y = (float)rolls.Rows[rollIndex]["Y"]; p.lastXdirection = (float)rolls.Rows[rollIndex]["lastX"]; p.lastYdirection = (float)rolls.Rows[rollIndex]["lastY"]; p.c.alpha = (int)rolls.Rows[rollIndex]["Alpha"]; p.c.red = (int)rolls.Rows[rollIndex]["Red"]; p.c.green = (int)rolls.Rows[rollIndex]["Green"]; p.c.blue = (int)rolls.Rows[rollIndex]["Blue"]; p.cp.alpha = p.c.alpha – 50; p.cp.red = p.c.red – 10; p.cp.green = p.c.green – 10; p.cp.blue = p.c.blue – 10; rollIndex++; // Изменили значение индекса строки. if (rollIndex == rolls.Rows.Count) rollIndex = 0; } // Та же выборка, но в параметрах дополнительно указан индекс записи. public void GetRow(ref rPoint p, int key) { p.index = (int)rolls.Rows[key]["nRoll"]; p.victim = (int)rolls.Rows[key]["Victim"]; p.p.X = (float)rolls.Rows[key]["X"]; p.p.Y = (float)rolls.Rows[key]["Y"]; p.lastXdirection = (float)rolls.Rows[key]["lastX"]; p.lastYdirection = (float)rolls.Rows[key]["lastY"]; p.c.alpha = (int)rolls.Rows[key]["Alpha"]; p.c.red = (int)rolls.Rows[key]["Red"]; p.c.green = (int)rolls.Rows[key]["Green"]; p.c.blue = (int)rolls.Rows[key]["Blue"]; p.cp.alpha = p.c.alpha – 50; p.cp.red = p.c.red – 10; p.cp.green = p.c.green – 10; p.cp.blue = p.c.blue – 10; if (rollIndex == rolls.Rows.Count) rollIndex = 0; } // Изменяется значение координат и статуса точки. // Значение порядкового номера объекта-параметра используется // в качестве первого индексатора, имя столбца – в // качестве второго. Скорость выполнения операции присваивания // значения ячейке оставляет желать лучшего. public void SetXYStInRow(rPoint p) { rolls.Rows[p.index]["X"] = p.p.X; rolls.Rows[p.index]["Y"] = p.p.Y; rolls.Rows[p.index]["lastX"] = p.lastXdirection; rolls.Rows[p.index]["lastY"] = p.lastYdirection; rolls.Rows[p.index]["Victim"] = p.victim; } public void ReSetRowIndex() { rollIndex = 0; } } } |
Листинг 18.3. |
// Выставили значение индекса int findIndex = –1; ::::: // Поиск в строке по полю "nRoll" (целочисленный столбец) rd.view.Sort = "nRoll"; try { // Проверка на соответствие типа. int.Parse(this.findTtextBox.Text); // Сам поиск. findIndex = rd.view.Find(this.findTtextBox.Text); } catch (Exception e1) { this.findTtextBox.Text = "Integer value expected..."; } } ::::: // Проверка результатов. if (findIndex == –1) { this.findTtextBox.Text = "Row not found: " + this.findTtextBox.Text; } else { this.findTtextBox.Text = "Yes :" + rd.view[findIndex]["nRoll"].ToString() + "," + rd.view[findIndex]["X"].ToString() + "," + rd.view[findIndex]["Y"].ToString(); } |
Листинг 18.4. |
// Массив для получения результатов поиска DataRowView[] rows; ::::: // Поиск в строке по полю "nRoll" (целочисленный столбец) rd.view.Sort = "nRoll"; try { // Проверка на соответствие типа. int.Parse(this.findTtextBox.Text); // Сам поиск. Возвращается массив rows. rows = rd.view.FindRows(this.findTtextBox.Text); } catch (Exception e1) { this.findTtextBox.Text = "Integer value expected..."; } } ::::: // Проверка результатов. if (rows.Length == 0) { this.findTtextBox.Text = "No rows found: " + this.findTtextBox.Text; } else { foreach (DataRowView row in rows) { this.findTtextBox.Text = row["nRoll"].ToString() + "," + row["X"].ToString() + "," + row["Y"].ToString(); } } |
Листинг 18.5. |
using System; using System.Data; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; namespace Lights01 { public class DataViewForm : System.Windows.Forms.Form { private System.ComponentModel.Container components = null; DataTable dt; // Таблица. DataColumn c1, c2; // Столбцы таблцы. DataRow dr; // Строка таблицы. DataView dv; // Вьюер таблицы. DataRowView rv; // Вьюер строки таблицы. int currentCounter; // Счетчик текущей строки для вьюера таблицы. private System.Windows.Forms.DataGrid dG; private System.Windows.Forms.DataGrid dGforTable; private System.Windows.Forms.Button buttPrev; private System.Windows.Forms.Button buttFirst; private System.Windows.Forms.Button buttLast; private System.Windows.Forms.Button buttonFind; private System.Windows.Forms.TextBox demoTextBox; private System.Windows.Forms.TextBox findTextBox; private System.Windows.Forms.Button buttonAdd; private System.Windows.Forms.Button buttonAcc; private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox2; private System.Windows.Forms.Button buttNext; public DataViewForm() { InitializeComponent(); CreateTable(); dG.DataSource = dv; dGforTable.DataSource = dt; currentCounter = 0; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } protected override void Dispose( bool disposing ) { if( disposing ) { if(components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code // Required method for Designer support – do not modify // the contents of this method with the code editor. private void InitializeComponent() { this.dG = new System.Windows.Forms.DataGrid(); this.demoTextBox = new System.Windows.Forms.TextBox(); this.buttPrev = new System.Windows.Forms.Button(); this.buttNext = new System.Windows.Forms.Button(); this.buttFirst = new System.Windows.Forms.Button(); this.buttLast = new System.Windows.Forms.Button(); this.findTextBox = new System.Windows.Forms.TextBox(); this.buttonFind = new System.Windows.Forms.Button(); this.buttonAdd = new System.Windows.Forms.Button(); this.dGforTable = new System.Windows.Forms.DataGrid(); this.buttonAcc = new System.Windows.Forms.Button(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox2 = new System.Windows.Forms.GroupBox(); ((System.ComponentModel.ISupportInitialize)(this.dG)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dGforTable)).BeginInit(); this.groupBox1.SuspendLayout(); this.groupBox2.SuspendLayout(); this.SuspendLayout(); // // dG // this.dG.DataMember = ""; this.dG.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.dG.Location = new System.Drawing.Point(8, 80); this.dG.Name = "dG"; this.dG.Size = new System.Drawing.Size(280, 128); this.dG.TabIndex = 0; this.dG.MouseDown += new System.Windows.Forms.MouseEventHandler(this.dG_MouseDown); // // demoTextBox // this.demoTextBox.Location = new System.Drawing.Point(152, 48); this.demoTextBox.Name = "demoTextBox"; this.demoTextBox.Size = new System.Drawing.Size(128, 20); this.demoTextBox.TabIndex = 1; this.demoTextBox.Text = ""; // // buttPrev // this.buttPrev.Location = new System.Drawing.Point(189, 16); this.buttPrev.Name = "buttPrev"; this.buttPrev.Size = new System.Drawing.Size(25, 23); this.buttPrev.TabIndex = 2; this.buttPrev.Text = "<–"; this.buttPrev.Click += new System.EventHandler(this.buttPrev_Click); // // buttNext // this.buttNext.Location = new System.Drawing.Point(221, 16); this.buttNext.Name = "buttNext"; this.buttNext.Size = new System.Drawing.Size(25, 23); this.buttNext.TabIndex = 3; this.buttNext.Text = "–>"; this.buttNext.Click += new System.EventHandler(this.buttNext_Click); // // buttFirst // this.buttFirst.Location = new System.Drawing.Point(157, 16); this.buttFirst.Name = "buttFirst"; this.buttFirst.Size = new System.Drawing.Size(25, 23); this.buttFirst.TabIndex = 4; this.buttFirst.Text = "<<"; this.buttFirst.Click += new System.EventHandler(this.buttFirst_Click); // // buttLast // this.buttLast.Location = new System.Drawing.Point(253, 16); this.buttLast.Name = "buttLast"; this.buttLast.Size = new System.Drawing.Size(25, 23); this.buttLast.TabIndex = 5; this.buttLast.Text = ">>"; this.buttLast.Click += new System.EventHandler(this.buttLast_Click); // // findTextBox // this.findTextBox.Location = new System.Drawing.Point(8, 48); this.findTextBox.Name = "findTextBox"; this.findTextBox.Size = new System.Drawing.Size(128, 20); this.findTextBox.TabIndex = 6; this.findTextBox.Text = ""; // // buttonFind // this.buttonFind.Location = new System.Drawing.Point(88, 16); this.buttonFind.Name = "buttonFind"; this.buttonFind.Size = new System.Drawing.Size(48, 23); this.buttonFind.TabIndex = 7; this.buttonFind.Text = "Find"; this.buttonFind.Click += new System.EventHandler(this.buttonFind_Click); // // buttonAdd // this.buttonAdd.Location = new System.Drawing.Point(8, 16); this.buttonAdd.Name = "buttonAdd"; this.buttonAdd.Size = new System.Drawing.Size(40, 23); this.buttonAdd.TabIndex = 8; this.buttonAdd.Text = "Add"; this.buttonAdd.Click += new System.EventHandler(this.buttonAdd_Click); // // dGforTable // this.dGforTable.DataMember = ""; this.dGforTable.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.dGforTable.Location = new System.Drawing.Point(8, 24); this.dGforTable.Name = "dGforTable"; this.dGforTable.Size = new System.Drawing.Size(272, 120); this.dGforTable.TabIndex = 9; // // buttonAcc // this.buttonAcc.Location = new System.Drawing.Point(8, 152); this.buttonAcc.Name = "buttonAcc"; this.buttonAcc.Size = new System.Drawing.Size(40, 23); this.buttonAcc.TabIndex = 10; this.buttonAcc.Text = "Acc"; this.buttonAcc.Click += new System.EventHandler(this.buttonAcc_Click); // // groupBox1 // this.groupBox1.Controls.Add(this.buttonAcc); this.groupBox1.Controls.Add(this.dGforTable); this.groupBox1.Location = new System.Drawing.Point(6, 8); this.groupBox1.Name = "groupBox1"; this.groupBox1.Size = new System.Drawing.Size(298, 184); this.groupBox1.TabIndex = 11; this.groupBox1.TabStop = false; this.groupBox1.Text = "Таблица как она есть "; // // groupBox2 // this.groupBox2.Controls.Add(this.buttPrev); this.groupBox2.Controls.Add(this.buttonFind); this.groupBox2.Controls.Add(this.buttFirst); this.groupBox2.Controls.Add(this.buttLast); this.groupBox2.Controls.Add(this.demoTextBox); this.groupBox2.Controls.Add(this.buttNext); this.groupBox2.Controls.Add(this.dG); this.groupBox2.Controls.Add(this.buttonAdd); this.groupBox2.Controls.Add(this.findTextBox); this.groupBox2.Location = new System.Drawing.Point(8, 200); this.groupBox2.Name = "groupBox2"; this.groupBox2.Size = new System.Drawing.Size(296, 216); this.groupBox2.TabIndex = 12; this.groupBox2.TabStop = false; this.groupBox2.Text = "Вид через вьюер"; // // DataViewForm // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(312, 421); this.Controls.Add(this.groupBox2); this.Controls.Add(this.groupBox1); this.Name = "DataViewForm"; this.Text = "DataViewForm"; ((System.ComponentModel.ISupportInitialize)(this.dG)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dGforTable)).EndInit(); this.groupBox1.ResumeLayout(false); this.groupBox2.ResumeLayout(false); this.ResumeLayout(false); } #endregion private void CreateTable() { // Создается таблица. dt = new DataTable("Items"); // Столбцы таблицы... // Имя первого столбца – id, тип значения – System.Int32. c1 = new DataColumn("id", Type.GetType("System.Int32")); c1.AutoIncrement=true; // Имя второго столбца – Item, тип значения – System.Int32. c2 = new DataColumn("Item", Type.GetType("System.Int32")); // К таблице добавляются объекты-столбцы... dt.Columns.Add(c1); dt.Columns.Add(c2); // А вот массив столбцов (здесь он из одного элемента) // для организации первичного ключа (множества первичных ключей). DataColumn[] keyCol= new DataColumn[1]; // И вот, собственно, как в таблице задается множество первичных ключей. keyCol[0]= c1; // Свойству объекта t передается массив, содержащий столбцы, которые // формируемая таблица t будет воспринимать как первичные ключи. dt.PrimaryKey=keyCol; // В таблицу добавляется 10 rows. for(int i = 0; i <10;i++) { dr=dt.NewRow(); dr["Item"]= i; dt.Rows.Add(dr); } // Принять изменения. // Так производится обновление таблицы. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. dt.AcceptChanges(); // Здесь мы применим специализированный конструктор, который // задаст значения свойств Table, RowFilter, Sort, RowStateFilter // объекта DataView в двух операторах кода... //dv = new DataView(dt); // Вместо этого... // Определение того, что доступно через объект - представитель DataView. // Задавать можно в виде битовой суммы значений. И не все значения сразу! // Сумма всех значений – противоречивое сочетание! // А можно ли делать это по отдельности? DataViewRowState dvrs = DataViewRowState.Added | DataViewRowState.CurrentRows | DataViewRowState.Deleted | DataViewRowState.ModifiedCurrent | //DataViewRowState.ModifiedOriginal | //DataViewRowState.OriginalRows | //DataViewRowState.None | // Записи не отображаются. DataViewRowState.Unchanged; // Вот такое хитрое объявление... // Таблица, // | значение, относительно которого проводится сортировка, // | | // | | имя столбца, значения которого сортируются, // | | | // | | | составленное значение DataViewRowState. // | | | | dv = new DataView(dt, "", "Item", dvrs); } private void buttNext_Click(object sender, System.EventArgs e) { if (currentCounter+1 < dv.Count) currentCounter++; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttPrev_Click(object sender, System.EventArgs e) { if (currentCounter–1 >= 0) currentCounter––; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttFirst_Click(object sender, System.EventArgs e) { currentCounter = 0; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void buttLast_Click(object sender, System.EventArgs e) { currentCounter =dv.Count – 1; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } private void dG_MouseDown (object sender, System.Windows.Forms.MouseEventArgs e) { currentCounter = dG.CurrentRowIndex; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } // Реализация поиска требует специального опеделения порядка // сортировки строк, который должен задаваться в конструкторе. private void buttonFind_Click(object sender, System.EventArgs e) { int findIndex = –1; // Сначала проверяем строку на соответствие формату отыскиваемого // значения. // В нашем случае строка должна преобразовываться в целочисленное // значение. try { int.Parse(findTextBox.Text); } catch { findTextBox.Text = "Неправильно задан номер..."; return; } findIndex = dv.Find(findTextBox.Text); if (findIndex >= 0) { currentCounter = findIndex; rv = dv[currentCounter]; demoTextBox.Text = rv["Item"].ToString(); } else { findTextBox.Text = "Не нашли."; } } private void buttonAdd_Click(object sender, System.EventArgs e) { // При создании новой записи средствами вьюера таблицы, // связанный с ним вьюер строки переходит в состояние rv.IsNew. // При этом в действиях этих объектов есть своя логика. // И если туда не вмешиваться, при создании очередной записи // предыдущая запись считается принятой и включается в таблицу // автоматически. // Контролируя состояния вьюера строки (rv.IsEdit rv.IsNew), // мы можем предотвратить процесс последовательного автоматического // обновления таблицы. Все под контролем. if (rv.IsEdit rv.IsNew) return; rv = dv.AddNew(); rv["Item"] = dv.Count–1; } private void buttonAcc_Click(object sender, System.EventArgs e) { // И вот мы вмешались в процесс. // Добавление новой записи в таблицу становится возможным лишь // после явного завершения редактирования предыдущей записи. // Без этого попытки создания новой записи блокируются. // Завершить редактирование. rv.EndEdit(); // Принять изменения. // Так производится обновление таблицы. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. dt.AcceptChanges(); } } } |
Листинг 18.6. |
DataSet myDataSet = new DataSet(); // Пустой объект - представитель класса DataSet. DataTable myTable = new DataTable(); // Пустая таблица создана. myDataSet.Tables.Add(myTable); // И подсоединена к объекту класса DataSet. // Определение структуры таблицы. Это мероприятие можно было // провести и до присоединения таблицы. DataColumn shipColumn = new DataColumn("Ships"); myDataSet.Tables[0].Columns.Add(shipColumn); // Прочие столбцы подсоединяются аналогичным образом. // Таким образом формируются поля данных таблицы. // Внимание! После того как определена структура таблицы, // то есть определены ВСЕ СТОЛБЦЫ таблицы, от имени этой конкретной // таблицы порождается объект-строка. Этот объект сразу располагается // непосредственно в таблице. Для каждой определенной таблицы // метод NewRow() порождает строку // (последовательность значений соответствующего типа). // Для непосредственного // редактирования вновь созданной строки запоминается ее ссылка. // Работать со строкой через эту ссылку проще, чем с массивом // строк таблицы. DataRow myRow = myDataSet.Tables[0].NewRow(); // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Остается заполнить строку таблицы содержательной информацией. // При этом может быть использован любой источник данных. // В данном примере предполагается наличие объекта типа ArrayList // с именем ShipCollection. for (int i = 0; i < ShipCollection.Count; i++) { myRow.Item[Counter] = ShipCollection[i]; } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Заполненный объект - представитель класса DataRow добавляется // к набору Rowsкласса DataTable. myDataSet.Tables[0].Rows.Add(myRow); |
Листинг 18.7. |
using System; using System.Data; namespace mergeTest { class Class1 { static void Main(string[] args) { // Создается объект DataSet. DataSet ds = new DataSet("myDataSet"); // Создается таблица. DataTable t = new DataTable("Items"); // Столбцы таблицы – это особые объекты. // Имя первого столбца – id, тип значения – System.Int32. DataColumn c1 = new DataColumn("id", Type.GetType("System.Int32")); c1.AutoIncrement=true; // Имя второго столбца – Item, тип значения – System.Int32. DataColumn c2 = new DataColumn("Item", Type.GetType("System.Int32")); // Сборка объекта DataSet: // Добавляются объекты-столбцы... t.Columns.Add(c1); t.Columns.Add(c2); // А вот массив столбцов (здесь он из одного элемента) // для организации первичного ключа (множества первичных ключей). DataColumn[] keyCol= new DataColumn[1]; // И вот, собственно, как в таблице задается множество первичных ключей. keyCol[0]= c1; // Свойству объекта t передается массив, содержащий столбцы, которые // формируемая таблица t будет воспринимать как первичные ключи. t.PrimaryKey=keyCol; // А что с этими ключами будет t делать? А это нас в данный момент // не касается. Очевидно, что методы, которые обеспечивают контроль // над информацией в соответствии со значениями ключей, уже где-то // "зашиты" в классе DataTable. Как и когда они будут выполняться – // не наше дело. Наше дело – указать на столбцы, которые для данной // таблицы будут ключевыми. Что мы и сделали. // Таблица подсоединяется к объекту ds – представителю класса DataSet. ds.Tables.Add(t); DataRow r; // В таблицу, которая уже присоединена к // объекту ds DataSet, добавляется 10 rows. for(int i = 0; i <10;i++) { r=t.NewRow(); r["Item"]= i; t.Rows.Add(r); } // Принять изменения. // Так производится обновление DataSet'а. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. ds.AcceptChanges(); PrintValues(ds, "Original values"); // Изменение значения в первых двух строках. t.Rows[0]["Item"]= 50; t.Rows[1]["Item"]= 111; t.Rows[2]["Item"]= 111; // Добавление еще одной строки. // Судя по всему, значение первого столбца устанавливается автоматически. // Это ключевое поле со значением порядкового номера строки. r=t.NewRow(); r["Item"]=74; t.Rows.Add(r); // Объявляем ссылку для создания временного DataSet. DataSet xSet; // ДЕКЛАРАЦИЯ О НАМЕРЕНИЯХ КОНТРОЛЯ ЗА КОРРЕКТНОСТЬЮ ЗНАЧЕНИЙ СТРОКИ. // Вот так добавляется свойство, содержащее строку для описания // ошибки в значении. Наш DataSet содержит одну строку с описанием. // Это всего лишь указание на то обстоятельство, что МЫ САМИ // обязались осуществлять // некоторую деятельность по проверке чего-либо. Чтобы не забыть, // в чем проблема, // описание возможной ошибки (в свободной форме!) добавляем // в свойства строки, // значения которой требуют проверки. t.Rows[0].RowError= "over 100 (ЙЦУКЕН!)"; t.Rows[1].RowError= "over 100 (Stupid ERROR!)"; t.Rows[2].RowError= "over 100 (Ну и дела!)"; // Но одно дело – декларировать намерения, а другое – осуществлять // контроль. // Проблема проверки корректности значения – наша личная проблема. // Однако о наших намерениях контроля за значениями становится // известно объекту – представителю DataSet! PrintValues(ds, "Modified and New Values"); // Мы вроде бы согласились проводить контроль значений. // Даже декларировали некий принцип проверки. // Однако ничего само собой не происходит. // Так вот, // // ЕСЛИ В ТАБЛИЦУ БЫЛИ ДОБАВЛЕНЫ СТРОКИ ИЛИ ИЗМЕНЕНЫ ЗНАЧЕНИЯ СТРОК // И // МЫ ОБЯЗАЛИСЬ КОНТРОЛИРОВАТЬ ЗНАЧЕНИЯ СТРОК В ТАБЛИЦЕ, // // то самое время организовать эту проверку... // Критерий правильности значений, естественно, наш! // Алгоритмы проверки – тоже НАШИ! // Единственное, чем нам может помочь ADO .NET, – это выделить // подмножество строк таблицы, // которые были добавлены или модифицированы со времени последнего // обновления нашего объекта - представителя DataSet'а, if(ds.HasChanges(DataRowState.Modified | DataRowState.Added)& ds.HasErrors) { // И для этого мы воспользуемся методом, который позволяет обеспечить // выделение подмножества добавленных и // модифицированных строк в новый объект DataSet'а. // Use GetChanges to extract subset. xSet = ds.GetChanges(DataRowState.Modified|DataRowState.Added); PrintValues(xSet, "Subset values"); // Insert code to reconcile errors. In this case, we'll reject changes. // Вот, собственно, код проверки. Все делается своими руками. foreach(DataTable xTable in xSet.Tables) { if (xTable.HasErrors) { foreach(DataRow xRow in xTable.Rows) { // Выделенное подмножество проверяем на наличие // ошибочного значения (для нас все, что больше 100, – // уже ошибка!) Console.Write(xRow["Item"] + " "); if((int)xRow["Item",DataRowVersion.Current ]> 100) { // Находим ошибку в строке, сообщаем о ней, Console.WriteLine("Error! – " + xRow.RowError); // Возвращаем старое значение... xRow.RejectChanges(); // Отменяем значение свойства - уведомителя о возможных // ошибках для данной строки... xRow.ClearErrors(); } else Console.WriteLine("OK."); } } } PrintValues(xSet, "Reconciled subset values"); // Сливаем измененные и откорректированные строки в основной // объект – DataSet // Merge changes back to first DataSet. ds.Merge(xSet); PrintValues(ds, "Merged Values"); } } // А это всего лишь вывод содержимого DataSet'а. private static void PrintValues(DataSet ds, string label) { Console.WriteLine("\n" + label); foreach(DataTable t in ds.Tables) { Console.WriteLine("TableName: " + t.TableName); foreach(DataRow r in t.Rows) { foreach(DataColumn c in t.Columns) { Console.Write("\t " + r[c] ); } Console.WriteLine(); } } } } } |
Листинг 18.8. |
// Объявили и определили объект соединения. private System.Data.OleDb.OleDbConnection oleDbConnection1; this.oleDbConnection1 = new System.Data.OleDb.OleDbConnection(); :::::::::: // Настроили объект соединения. // Для наглядности необходимая для установления соединения // информация представлена серией строк. oleDbConnection1.ConnectionString = @"Jet OLEDB:Global Partial Bulk Ops=2;" + @"Jet OLEDB:Registry Path=;" + @"Jet OLEDB:Database Locking Mode=1;" + @"Data Source=""F:\Users\Work\CS\DB.BD\DBTests\Lights.mdb"";" + @"Jet OLEDB:Engine Type=5;" + @"Jet OLEDB:Global Bulk Transactions=1;" + @"Provider=""Microsoft.Jet.OLEDB.4.0"";" + // Поставщик @"Jet OLEDB:System database=;" + @"Jet OLEDB:SFP=False;" + @"persist security info=False;" + @"Extended Properties=;" + @"Mode=Share Deny None;" + @"Jet OLEDB:Create System Database=False;" + @"Jet OLEDB:Don't Copy Locale on Compact=False;" + @"Jet OLEDB:Compact Without Replica Repair=False;" + @"User ID=Admin;" + @"Jet OLEDB:Encrypt Database=False"; |
Листинг 18.9. |
private void readButton_Click(object sender, System.EventArgs e) { int i = 0; this.timer.Stop(); rd.rolls.Clear(); nPoints = 0; string strSQL = "SELECT nRolls,Victim,X,Y,oldX,OldY,Alpha,Red,Green,Blue From RollsTable"; oleDbConnection.Open(); OleDbCommand cmd = new OleDbCommand(strSQL,oleDbConnection); OleDbDataReader rdr = cmd.ExecuteReader(); while (rdr.Read()) { rd.AddRow( int.Parse((rdr["nRolls"]).ToString()), int.Parse((rdr["Victim"]).ToString()), float.Parse((rdr["X"]).ToString()), float.Parse((rdr["Y"]).ToString()), float.Parse((rdr["oldX"]).ToString()), float.Parse((rdr["oldY"]).ToString()), int.Parse((rdr["Alpha"]).ToString()), int.Parse((rdr["Red"]).ToString()), int.Parse((rdr["Green"]).ToString()), int.Parse((rdr["Blue"]).ToString()) ); i++; } rdr.Close(); oleDbConnection.Close(); rpDataGrid.DataSource = rd.rolls; nPoints = i; this.timer.Start(); } |
Листинг 18.10. |
Примеры использования DataView
Для организации просмотра информации, сохраняемой объектом-представителем класса DataTable через объект – представитель класса DataView, этот объект необходимо связать с таблицей.
Таким образом, в приложении создается (независимый!) вьюер, который связывается с таблицей.
Итак, имеем
DataTable tbl = new DataTable("XXX"); // Объявлен и определен объект "таблица". DataView vie; // Ссылка на вьюер.
vie = new DataView(); // Создали... vie.Table = tbl; // Привязали таблицу к вьюеру.
// Можно и так... vie = new DataView(tbl); // Создали и сразу привязали...
Управление вьюером также осуществляется посредством различных достаточно простых манипуляций, включая изменение свойств объекта. Например, сортировка включается следующим образом:
// Предполагается наличие кнопочных переключателей, // в зависимости от состояния которых в свойстве Sort // вьюера выставляется в качестве значения имя того или // иного столбца. Результат сортировки становится виден // непосредственно после передачи информации объекту, // отвечающему за демонстрацию таблицы (rpDataGreed).
if (rBN.Checked) rd.view.Sort = "nRoll"; if (rBX.Checked) rd.view.Sort = "X"; if (rBY.Checked) rd.view.Sort = "Y";
this.rpDataGrid.DataSource = rd.view;
Следующий пример кода демонстрирует возможности поиска, реализуемые объектом – представителем класса DataView. Сортировка обеспечивается вариантами методов Find и FindRows, которые способны в различной реализации воспринимать отдельные значения или массивы значений.
Поиск информации проводится по столбцам, предварительно перечисленным в свойстве Sort. При неудовлетворительном результате поиска метод Find возвращает отрицательное значение, метод FindRows – нулевое.
В случае успеха метода Sort возвращается индекс первой найденной записи. По этому индексу можно получить непосредственный доступ к записи:
Листинг 4: ,
Применение метода FindRows. В случае успешного завершения поиска возвращается массив записей, элементы которого могут быть выбраны посредством цикла foreach:
Листинг 5: ,
В примере демонстрируется взаимодействие автономной таблицы и "заточенного" под нее вьюера. Записи в таблицу можно добавлять как непосредственно, так и через вьюер. При этом необходимо совершать некоторое количество дополнительных действий. Через вьюер же организуется поиск записей.
Листинг 6: ,
Продолжение разработки. Простые шаги
Продолжение работы по созданию простого приложения для работы с базой данных также не предусматривает непосредственной работы с программным кодом.
После создания объекта DataSet и трансляции кода приложения на панели инструментов появляется новая вкладка, предоставляющая возможность работы с автоматически объявленными классами – адаптерами таблиц. После чего работа по созданию приложения сводится к нескольким достаточно простым "волшебным" манипуляциям:
создается объект – представитель класса BindingSource, свойству DataSource которого присваивается ссылка на ранее созданный объект DataSet, а свойству DataMember – значение, связанное с определенной в базе данных таблицей "Клиенты";это действие сопровождается созданием объекта – адаптера таблицы, условное обозначение которого появляется на панели компонентов формы, что делает адаптер доступным для возможной модификации и настройки;получение информации из базы данных обеспечивается при помощи запросов к базе, которые также достаточно просто построить, запустив соответствующий "волшебник". Мышиный клик по пиктограмме адаптера на панели, вызов генератора запроса, далее – в соответствии с замыслом приложения и сценарием генератора. После создания запроса на панели компонентов формы появляется пиктограмма, обозначающая ранее построенный объект-представитель класса DataSet, а непосредственно на форме – инструментальная панель с элементом, который обеспечивает выполнение запроса, в результате которого через соответствующий адаптер таблицы производится заполнение объекта DataSet'а;для решения поставленной задачи необходимо дважды запустить генератор запросов для заполнения таблиц "клиенты" и "заказы". Генератор запускается "от существующего" адаптера таблицы. Процесс создания второго запроса сопровождается появлением второго адаптера таблицы. При этом на форме появляются две инструментальные панели, обеспечивающие загрузку информации при выполнении приложения;информация о клиентах и заказах размещается в элементах управления типа TextBox (о клиентах) и элементе управления DataGrid (о заказах). Эти элементы размещаются на поверхности формы с последующей их привязкой к элементам DataSet'а, при этом мастер создает объекты – представители класса BindingSource;для обеспечения навигации по данным используется комбинированный элемент управления BindingNavigator, который настраивается на один из объектов – представителей класса BindingSource (к моменту настройки навигатора таких объектов в приложении – два).
В результате получаем приложение, которое обеспечивает просмотр содержимого базы данных.
Работа с базами данных
Работа с БД на уровне приложения .NET – это работа:
с множествами объявлений классов, которые содержат объявления унаследованных методов и свойств, предназначенных для решения задачи извлечения информации из базы данных;с множеством объектов-представителей классов, которые обеспечивают работу с базами данных;с множеством значений и свойств конкретных объектов, отражающих специфику структуры конкретной базы данных.
Функциональные особенности этой сложной системы взаимодействующих классов обеспечивают ЕДИНООБРАЗНУЮ работу с базами данных независимо от системы управления базой и ее реализации.
Конечно же, при написании программы, взаимодействующей с базами данных, программист все может сделать своими руками. Правда, в силу сложности задачи (много всяких деталек придется вытачивать), времени на разработку такого приложения может потребоваться достаточно много.
Для программиста – разработчика приложения принципиальной становится информация о логической организации (структуре таблиц, отношениях и ограничениях) данной конкретной базы данных — то есть о том, как эту базу видит приложение.
Деятельность программиста – разработчика приложений для работы с базами данных в основе своей ничем не отличается от того, что было раньше. Те же объявления классов и интерфейсов.
А потому желательно:
понимать принципы организации и взаимодействия классов, которые обеспечивают работу с базой данных;представлять структуру, назначение и принципы работы соответствующих объектов;знать, для чего и как применять различные детали при организации взаимодействия с базами данных;уметь создавать компоненты ADO .NET заданной конфигурации с использованием вспомогательных средств (волшебников), предоставляемых в рамках Visual Studio .NET.
Relations
В классе DataSet определяется свойство Relations – набор объектов – представителей класса DataRelations. Каждый такой объект определяет связи между составляющими объект DataSet объектами DataTable (таблицами). Если в DataSet более одного набора DataTable, набор DataRelations будет содержать несколько объектов типа DataRelation. Каждый объект определяет связи между таблицами – DataTable. Таким образом, в объекте DataSet реализуется полный набор элементов для управления данными, включая сами таблицы, ограничения и отношения между таблицами.
Реляционные базы данных. Основные понятия
Ниже обсуждаются наиболее общие понятия, связанные с представлением реляционной базы данных с точки зрения программиста, использующего ADO .NET.
Детали реализации конкретной базы данных в рамках данной СУБД не обсуждаются. ADO .NET для того и используется, чтобы максимально скрыть детали реализации конкретной базы и предоставить программисту набор стандартных классов, интерфейсов, программных средств, которые превращают процесс создания приложения в стандартизированный технологический процесс. Таким образом, с точки зрения .NET:
столбец (поле, атрибут) —
характеризуется определенным типом (данных),множество значений столбца являются значениями одного типа;
строка (запись, кортеж) —
характеризуется кортежем атрибутов, состоит из упорядоченного множества значений (кортежа) атрибутов;
таблица —
набор данных, представляющих объекты определенного типа,состоит из множества элементов столбцов-строк,каждая строка таблицы УНИКАЛЬНА;
первичный ключ таблицы —
непустое множество столбцов таблицы (возможно, состоящее из одного столбца), соответствующие значения (комбинации значений) которых в строках таблицы обеспечивают уникальность каждой строки в данной таблице;
дополнительный ключ таблицы —
а бог его знает, зачем еще одна гарантия уникальности строки в таблице;
внешний ключ таблицы —
непустое множество столбцов таблицы (возможно, состоящее из одного столбца), соответствующие значения (комбинации значений) которых в строках таблицы соответствуют первичному или дополнительному ключу другой таблицы,обеспечивает логическую связь между таблицами.
События класса DataTable
В классе определены четыре события, которые позволяют перехватывать и в случае необходимости отменять изменения состояния таблицы данных.
Изменения строк. DataRowChanging – изменения вносятся в строку таблицы.
Объявление соответствующего обработчика события имеет вид private static void Row_Changing( object sender, DataRowChangeEventArgs e )DataRowChanged – изменения внесены в строку таблицы.
Объявление соответствующего обработчика события имеет вид private static void Row_Changed( object sender, DataRowChangeEventArgs e )
Пример программного кода для объекта – представителя класса DataTable:
Листинг 1: ,
Параметр обработчика события DataRowChangeEventArgs обладает двумя свойствами (Action и Row), которые позволяют определить изменяемую строку и выполняемое над строкой действие. Действие кодируется значениями специального перечисления: enum RowDataAction { Add, Change, Delete, Commit, Rollback, Nothing } Изменения полей (элементов в строках таблицы) DataColumnChanging – изменения вносятся в поле строки данных.
Объявление соответствующего обработчика события имеет вид private static void Column_Changing (object sender, DataColumnChangeEventArgs e)DataColumnChanged – изменения были внесены в поле строки данных.
Объявление соответствующего обработчика события имеет вид private static void Column_Changed (object sender, DataColumnChangeEventArgs e)
Параметр обработчика события DataColumnChangeEventArgs e обладает тремя свойствами:
Column | Get. Объект-представитель класса DataColumn с изменённым значением |
ProposedValue | Gets, sets. Новое значение для поля в строке |
Row | Строка, содержащая запись с изменяемым (измененным) значением |
Аналогичный пример. Только теперь программируется реакция на модификацию столбца (поля), а не строки:
Листинг 2: ,
Способы создания команд
Известно три способа создания команд для манипулирования данными:
объявление и создание объекта команды непосредственно в программном коде с последующей настройкой этого объекта вручную. Следующие фрагменты кода демонстрируют этот способ: string cs = @"Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + @"F:\SharpUser\CS.book\Rolls.db"; OleDbConnection cn = new OleDbConnection(cs); cn.Open(); OleDbCommand cmd = new OleDbCommand(); cmd.Connection = cn; // Объект "Соединение" цепляется к команде!
Либо так:string cs = @"Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + @"F:\SharpUser\CS.book\Rolls.db"; OleDbConnection cn = new OleDbConnection(cs); cn.Open(); OleDbCommand cmd = cn.CreateCommand();// Команда создается соединением!использование инструментария, предоставляемого панелью ToolBox (вкладка Data). Объект соответствующего класса перетаскивается в окно дизайнера с последующей настройкой этого объекта. SqlCommand или OleDbCommand перетаскивается в окно конструктора со вкладки Data, при этом остается вручную задать свойства Connection, CommandText, CommandType (для определения типа команды, которая задается в свойстве CommandText). При этом свойство CommandType может принимать одно из трех значений: (1) Text – значение свойства CommandText воспринимается как текст команды SQL. При этом возможна последовательность допустимых операторов, разделенных точкой с запятой; (2) StoredProcedure – значение свойства CommandText воспринимается как имя существующей хранимой процедуры, которая будет исполняться при вызове данной команды; (3) TableDirect – при этом свойство CommandText воспринимается как непустой список имен таблиц, возможно, состоящий из одного элемента. При выполнении команды возвращаются все строки и столбцы этих таблиц; размещение (путем перетаскивания) хранимой процедуры из окна Server Explorer в окно дизайнера. Объект "Команда" соответствующего типа при этом создается автоматически на основе любой хранимой процедуры. При этом новый объект ссылается на хранимую процедуру, что позволяет вызывать эту процедуру без дополнительной настройки.
Оба типа объектов "Команда" (SqlCommand и OleDbCommand) поддерживают три метода, которые позволяют исполнять представляемую команду:
ExecuteNonQuery – применяется для выполнения команд SQL и хранимых процедур, таких как INCERT, UPDATE, DELETE. С помощью этого метода также выполняются команды DDL – CREATE, ALTER. Не возвращает никаких значений. ExecuteScalar – обеспечивает выбор строк из таблицы БД. Возвращает значение первого поля первой строки, независимо от общего количества выбранных строк.ExecuteReader – обеспечивает выбор строк из таблицы БД. Возвращает неизменяемый объект DataReader, который допускает последовательный однонаправленный просмотр извлеченных данных без использования объекта DataAdapter.
Кроме того, класс SqlCommand поддерживает еще один метод:
ExecuteXmlReader – обеспечивает выбор строк из таблицы БД в формате XML. Возвращает неизменяемый объект XMLReader, который допускает последовательный однонаправленный просмотр извлеченных данных.
Назначение всех четырех методов – ИСПОЛНЕНИЕ НА ИСТОЧНИКЕ ДАННЫХ команды, представленной объектом команды.
Структура класса DataSet
База данных характеризуется множеством таблиц и множеством отношений между таблицами.
DataSet (как объявление класса) включает:
набор (возможно, что пустой) объявлений классов DataTable (фактически это описание структуры составляющих данный КЛАСС DataSet таблиц),набор объявлений классов DataRelations, который обеспечивает установку связей между разными таблицами в рамках данного DataSet.
Структура DataSet может в точности повторять структуру БД и содержать полный перечень таблиц и отношений, а может быть частичной копией БД и содержать, соответственно, лишь подмножество таблиц и подмножество отношений. Все определяется решаемой с помощью данного DataSet задачей.
При всем этом следует иметь в виду, что DataSet, структура которого полностью соответствует структуре БД (ИДЕАЛЬНАЯ DataSet), никогда не помешает решению поставленной задачи. Даже если будет содержать излишнюю информацию.
Процесс объявления (построения) класса DataSet, который ПОДОБЕН структуре некоторой базы данных, является достаточно сложным и трудоемким. Класс – структурная копия БД содержит множество стандартных и трудных для ручного воспроизведения объявлений. Как правило, его объявление строится с использованием специальных средств-волшебников, которые позволяют оптимизировать и ускорять процесс воспроизведения структуры БД средствами языка программирования. В конечном счете появляется все то же объявление класса, над которым также можно медитировать и (осторожно!) изменять его структуру.
Объект – представитель данного класса DataSet обеспечивает представление в памяти компьютера фрагмента данной БД. Этот объект является локальным представлением (фрагмента) БД.
Члены класса DataSet представлены ниже.
DataSet-конструктор | Перегружен. Инициализирует новый экземпляр класса DataSet |
CaseSensitive | Возвращает или задает значение, определяющее, учитывается ли регистр при сравнении строк в объектах DataTable |
Container (унаследовано от MarshalByValueComponent) | Получает контейнер для компонента |
DataSetName | Возвращает или задает имя текущего DataSet |
DefaultViewManager | Возвращает новое представление данных класса DataSet для осуществления фильтрации, поиска или перехода с помощью настраиваемого класса DataViewManager |
DesignMode (унаследовано от MarshalByValueComponent) | Получает значение, указывающее, находится ли компонент в настоящий момент в режиме разработки |
EnforceConstraints | Возвращает или задает значение, определяющее соблюдение правил ограничения при попытке совершения операции обновления |
ExtendedProperties | Возвращает коллекцию настраиваемых данных пользователя, связанных с DataSet |
HasErrors | Возвращает значение, определяющее наличие ошибок в любом из объектов DataTable в классе DataSet |
Locale | Возвращает или задает сведения о языке, используемые для сравнения строк таблицы |
Namespace | Возвращает или задает пространство имен класса DataSet |
Prefix | Возвращает или задает префикс XML, который является псевдонимом пространства имен класса DataSet |
Relations | Возвращает коллекцию соотношений, связывающих таблицы и позволяющих переходить от родительских таблиц к дочерним |
Site | Переопределен. Возвращает или задает тип System.ComponentModel.ISite для класса DataSet |
Tables | Возвращает коллекцию таблиц класса DataSet |
AcceptChanges | Сохраняет все изменения, внесенные в класс DataSet после его загрузки или после последнего вызова метода AcceptChanges |
Clear | Удаляет из класса DataSet любые данные путем удаления всех строк во всех таблицах |
Clone | Копирует структуру класса DataSet, включая все схемы, соотношения и ограничения объекта DataTable. Данные не копируются |
Copy | Копирует структуру и данные для класса DataSet |
Dispose (унаследовано от MarshalByValueComponent) | Перегружен. Освобождает ресурсы, использовавшиеся объектом MarshalByValueComponent |
Equals (унаследовано от Object) | Перегружен. Определяет, равны ли два экземпляра Object |
GetChanges | Перегружен. Возвращает копию класса DataSet, содержащую все изменения, внесенные после его последней загрузки или после вызова метода AcceptChanges |
GetHashCode (унаследовано от Object) | Служит хэш-функцией для конкретного типа, пригоден для использования в алгоритмах хэширования и структурах данных, например в хэш-таблице |
GetService (унаследовано от MarshalByValueComponent) | Получает реализацию объекта IServiceProvider |
GetType (унаследовано от Object) | Возвращает Type текущего экземпляра |
GetXml | Возвращает XML-представления данных, хранящихся в классе DataSet |
GetXmlSchema | Возвращает XSD-схему для XML-представление данных, хранящихся в классе DataSet |
HasChanges | Перегружен. Возвращает значение, определяющее наличие изменений в классе DataSet, включая добавление, удаление или изменение строк |
InferXmlSchema | Перегружен. Применяет XML-схему к классу DataSet |
Merge | Перегружен. Осуществляет слияние указанного класса DataSet, DataTable или массива объектов DataRow с текущим объектом DataSet или DataTable |
ReadXml | Перегружен. Считывает XML-схему и данные в DataSet |
ReadXmlSchema | Перегружен. Считывает XML-схему в DataSet |
RejectChanges | Отменяет все изменения, внесенные в класс DataSet после его создания или после последнего вызова метода DataSet.AcceptChanges |
Reset | Сбрасывает DataSet в исходное состояние. Для восстановления исходного состояния класса DataSet необходимо переопределить метод Reset в подклассах |
ToString (унаследовано от Object) | Возвращает String, который представляет текущий Object |
WriteXml | Перегружен. Записывает XML-данные и по возможности схемы из DataSet |
WriteXmlSchema | Перегружен. Записывает структуру класса DataSet в виде XML-схемы |
Disposed (унаследовано от MarshalByValueComponent) | Добавляет обработчик событий, чтобы воспринимать событие Disposed на компоненте |
MergeFailed | Возникает, если значения первичного ключа конечного и основного объектов DataRow совпадают, а свойство EnforceConstraints имеет значение true |
DataSet-конструктор | Перегружен. Инициализирует новый экземпляр класса DataSet |
Events (унаследовано от MarshalByValueComponent) | Получает список обработчиков событий, которые подключены к этому компоненту |
Dispose (унаследовано от MarshalByValueComponent) | Перегружен. Освобождает ресурсы, использовавшиеся объектом MarshalByValueComponent |
Finalize (унаследовано от Object) | Переопределен. Позволяет объекту Object попытаться освободить ресурсы и выполнить другие завершающие операции, перед тем как объект Object будет уничтожен в процессе сборки мусора |
System.ComponentModel.IListSource.ContainsListCollection
Сведения о хранимых процедурах
Хранимые процедуры – предварительно оттранслированное множество предложений SQL и дополнительных предложений для управления потоком, сохраняемое под именем и обрабатываемое (выполняемое) как одно целое. Хранимые процедуры сохраняются непосредственно в базе данных; их выполнение обеспечивается вызовом со стороны приложения; допускает включение объявляемых пользователем переменных, условий и других программируемых возможностей.
В хранимых процедурах могут применяться входные и выходные параметры; сохраняемые процедуры могут возвращать единичные значения и результирующие множества.
Функционально хранимые процедуры аналогичны запросам. Вместе с тем, по сравнению с предложениями SQL, они обладают рядом преимуществ:
в одной процедуре можно сгруппировать несколько запросов; в одной процедуре можно сослаться на другие сохраненные процедуры, что упрощает процедуры обращения к БД; выполняются быстрее, чем индивидуальные предложения SQL.
Таким образом, хранимые процедуры облегчают работу с базой данных.
Свойства, методы и события класса OleDbConnection
ConnectionString | string | Строка, определяющая способ подключения объекта к источнику данных |
ConnectionTimeout | Int32 | Интервал времени, в течение которого объект пытается установить соединение с источником данных (только для чтения) |
Container | string | Get. Возвращает объект IContainer, который содержит объект Component |
Database | string | Gets текущей базы данных или базы, которая использовалась после установления соединения |
DataSource | string | Get. Имя сервера или имя файла-источника данных. Все зависит от того, с каким хранилищем информации ведется работа. Серверное хранилище данных (SQL Server, Oracle) – имя компьютера, выступающего в роли сервера. Файловые БД (Access) – имя файла |
Provider | string | Gets имя OLE DB провайдера, которое было объявлено в "Provider= ..." clause строки соединения |
ServerVersion | string | Get. Строка с информацией о версии сервера, с которым было установлено соединение |
Site | string | Get или set. Объект ISite с информацией о функциональных возможностях узла |
State | string | Gets текущее состояние соединения |
Текущее состояние соединения кодируется как элемент перечисления ConnestionState. Список возможных значений представлен ниже.
Broken | Соединение с источником данных разорвано.Подобное может случиться только после того, как соединение было установлено. В этом случае соединение может быть либо закрыто, либо повторно открыто | 16 |
Closed | Соединение закрыто | 0 |
Connecting | Идет процесс подключения (значение зарезервировано) | 2 |
Executing | Соединение находится в процессе выполнения команды (значение зарезервировано.) | 4 |
Fetching | Объект соединения занят выборкой данных (значение зарезервировано) | 8 |
Open | Соединение открыто | 1 |
BeginTransaction | Перегружен. Начинает транзакцию базы данных |
ChangeDatabase | Изменяет текущую базу данных для открытого OleDbConnection |
Close | Закрывает подключение к источнику данных. Это рекомендуемый метод закрытия любого открытого подключения |
CreateCommand | Создает и возвращает объект OleDbCommand, связанный с OleDbConnection |
CreateObjRef (унаследовано от MarshalByRefObject) | Создает объект, который содержит всю необходимую информацию для конструирования прокси-сервера, используемого для коммуникации с удаленными объектами |
Dispose (унаследовано от Component) | Перегружен. Освобождает ресурсы, используемые объектом Component |
EnlistDistributed | Зачисляет в указанную транзакцию в качестве |
Transaction | распределенной транзакции |
Equals (унаследовано от Object) | Перегружен. Определяет, равны ли два экземпляра Object |
GetHashCode (унаследовано от Object) | Служит хэш-функцией для конкретного типа, пригоден для использования в алгоритмах хэширования и структурах данных, например в хэш-таблице |
GetLifetimeService (унаследовано от MarshalByRefObject) | Извлекает служебный объект текущего срока действия, который управляет средствами срока действия данного экземпляра |
GetOleDbSchemaTable | Возвращает сведения схемы из источника данных так же, как указано в GUID, и после применения указанных ограничений |
GetType (унаследовано от Object) | Возвращает Type текущего экземпляра |
InitializeLifetimeService (унаследовано от MarshalByRefObject) | Получает служебный объект срока действия для управления средствами срока действия данного экземпляра |
Open | Открывает подключение к базе данных со значениями свойств, определяемыми ConnectionString |
ReleaseObjectPoolOleDb Connection | Статический. Означает, что пул объектов может быть освобожден, когда последнее основное подключение будет освобождено |
ToString (унаследовано от Object) | Возвращает String, который представляет текущий Object |
Dispose | Перегружен. Переопределен. Освобождает ресурсы, используемые объектом OleDbConnection |
Finalize (унаследовано от Component) | Переопределен. Освобождает неуправляемые ресурсы и выполняет другие действия по очистке, перед тем как пространство, которое использует Component, будет восстановлено сборщиком мусора. |
В языках C# и C++ для функций финализации используется синтаксис деструктора
Disposed | Это событие сопровождает процесс удаления объекта |
InfoMessage | Некоторые СУБД (SQL Server) поддерживают механизм информационных сообщений. Это событие происходит при отправке провайдером некоторых сообщений |
StateChange | Возникает при изменении состояния соединения |
Свойства параметров
Parameter является достаточно сложной конструкцией, о чем свидетельствует НЕПОЛНЫЙ список его свойств:
Value – свойство, предназначенное для непосредственного сохранения значения параметра;Direction – свойство объекта-параметра, которое определяет, является ли параметр входным или выходным. Множество возможных значений представляется следующим списком: Input, Output, InputOutput, ReturnValue;DbType (не отображается в окне дизайнера) в сочетании с OleDbType (только для объектов типа OleDbParameters) – параметры, используемые для согласования типов данных, принятых в CTS (Common Type System) и типов, используемых в конкретных базах данных;DbType (не отображается в окне дизайнера) в сочетании с SQLType (только для объектов типа SqlParameters) – параметры, также используемые для согласования типов данных, принятых в CTS (Common Type System) и типов, используемых в конкретных базах данных;ParameterName – свойство, которое обеспечивает обращение к данному элементу списка параметров команды непосредственно по имени, а не по индексу. Разница между этими двумя стилями обращения к параметрам демонстрируется в следующем фрагменте кода: OleDbCommand1.Parameters[0].Value = "OK"; // В команде, представляемой объектом OleDbCommand1, значение первого // элемента списка параметров есть строка "OK". OleDbCommand1.Parameters["ParameterOK"].Value = "OK"; // В команде, представляемой объектом OleDbCommand1, значение элемента // списка параметров, представленного именем "ParameterOK", // есть строка "OK". Precision, Scale, Size определяют длину и точность соответствующих параметров. При этом первые два свойства применяются для задания разрядности и длины дробной части значения параметров таких типов, как float, double, decimal (последнее свойство используется для указания максимально возможных длин строкового и двоичного параметров).
Транзакция
Под транзакцией понимается неделимая с точки зрения воздействия на базу данных последовательность операторов манипулирования данными:
чтения,удаления,вставки,модификации, приводящая к одному из двух возможных результатов:либо последовательность выполняется, если все операторы правильные,либо вся транзакция откатывается, если хотя бы один оператор не может быть успешно выполнен.
Прежде всего, необходимым условием применения транзакций как элементов модели ADO .NET является поддержка источником данных (базой данных) концепции транзакции. Обработка транзакций гарантирует целостность информации в базе данных. Таким образом, транзакция переводит базу данных из одного целостного состояния в другое.
При выполнении транзакции система управления базами данных должна придерживаться определенных правил обработки набора команд, входящих в транзакцию. В частности, гарантией правильности и надежности работы системы управления базами данных являются четыре правила, известные как требования ACID.
Atomicity – неделимость. Транзакция неделима в том смысле, что представляет собой единое целое. Все ее компоненты либо имеют место, либо нет. Не бывает частичной транзакции. Если может быть выполнена лишь часть транзакции, она отклоняется. Consistency – согласованность. Транзакция является согласованной, потому что не нарушает бизнес-логику и отношения между элементами данных. Это свойство очень важно при разработке клиент-серверных систем, поскольку в хранилище данных поступает большое количество транзакций от разных систем и объектов. Если хотя бы одна из них нарушит целостность данных, то все остальные могут выдать неверные результаты. Isolation – изолированность. Транзакция всегда изолированна, поскольку ее результаты самодостаточны. Они не зависят от предыдущих или последующих транзакций – это свойство называется сериализуемостью и означает, что транзакции в последовательности независимы. Durability – устойчивость. Транзакция устойчива. После своего завершения она сохраняется в системе, которую ничто не может вернуть в исходное (до начала транзакции) состояние, т.е. происходит фиксация транзакции, означающая, что ее действие постоянно даже при сбое системы. При этом подразумевается некая форма хранения информации в постоянной памяти как часть транзакции.
Указанные выше правила реализуются непосредственно источником данных. На программиста возлагаются обязанности по созданию эффективных и логически верных алгоритмов обработки данных. Он решает, какие команды должны выполняться как одна транзакция, а какие могут быть разбиты на несколько последовательно выполняемых транзакций.
Работа с транзакцией предполагает следующую последовательность действий:
Инициализация транзакции.
Обеспечивается вызовом метода BeginTransaction() от имени объекта Connection, представляющего открытое соединение. В результате выполнения этого метода возвращается ссылка на объект – представитель класса Transaction, который должен быть записан в свойство Transaction всех объектов-команд, которые должны быть задействованы в данной транзакции. Выполнение команд – участников транзакции с анализом их возвращаемых значений (обычно этот анализ сводится к тривиальному размещению всех операторов, связанных с выполнением транзакции в один try-блок).Если все команды выполняются удовлетворительно, от имени объекта – представителя класса Transaction вызывается метод Commit(), который подтверждает изменение состояния источника данных. В противном случае (блок catch), от имени объекта – представителя класса Transaction вызывается метод Rollback(), который отменяет ранее произведенные изменения состояния Базы данных.
Ниже приводится пример исполнения транзакции с помощью объекта соединения класса OleDbConnection с именем xConnection и пары объектов OleDbCommand с именами xCommand1 и xCommand2:
System.Data.OleDb.OleDbTransaction xTransaction = null; try { xConnection.Open(); // Создается объект транзакции. xTransaction = xConnection.BeginTransaction(); // Транзакция фиксируется в командах. xCommand1.Transaction = xTransaction; xCommand2.Transaction = xTransaction; // Выполнение команд. xCommand1.ExecuteNonQuery(); xCommand2.ExecuteNonQuery(); // Если ВСЕ ХОРОШО и мы все еще здесь – ПРИНЯТЬ ТРАНЗАКЦИЮ! xTransaction.Commit(); } catch { // Если возникли осложнения – отменяем транзакцию. xTransaction. Rollback(); } finally { // В любом случае соединение закрывается. xConnection.Close(); }
Установка значений параметров
Следующий пример показывает, как установить параметры перед выполнением команды, представленной хранимой процедурой. Предполагается, что уже была собрана соответствующая последовательность параметров, с именами au_id, au_lname, и au_fname.
OleDbCommand1.CommandText = "UpdateAuthor"; OleDbCommand1.CommandType = System.Data.CommandType.StoredProcedure; OleDbCommand1.Parameters["au_id"].Value = listAuthorID.Text; OleDbCommand1.Parameters["au_lname"].Value = txtAuthorLName.Text; OleDbCommand1.Parameters["au_fname"].Value = txtAuthorFName.Text; OleDbConnection1.Open(); OleDbCommand1.ExecuteNonQuery(); OleDbConnection1.Close();