ГГУ им. Ф.Скорины

http://www.gsu.by

Разработка Web-приложений ASP .NET с использованием
Visual Studio .NET

10. Лекция: Использование баз данных в
приложениях ASP.NET: версия для печати и PDA
Рассматриваются вопросы использования баз данных в
приложениях ASP.NET. Лекция охватывает все вопросы, необходимые для
построения Web-приложения, эффективно взаимодействующего с базой
данных.

Цель лекции: познакомиться с принципами
организации подключения Web-приложения к базе данных, рассмотреть
основные возможности модели доступа к данным ADO.NET, изучить
примеры выполнения наиболее типовых действий с данными в базе
данных, таких как добавление, удаление, редактирование, а также
поиск, сортировка и фильтрация.

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

Основные сведения о модели доступа к данным ADO.NET

ADO.NET представляет собой
набор библиотек, входящих в Microsoft .NET Framework и
предназначенных для взаимодействия с различными хранилищами данных
из .NET-приложений. Библиотеки ADO.NET
включают все необходимые классы для подключения к источникам данных
практически произвольного формата, выполнения запросов к этим
источникам и получения результата. Кроме того, несомненным
достоинством ADO.NET является возможность
работы с отсоединенными источниками
данных, представляющими собой структуры, которые организуют
данные в оперативной памяти компьютера и работать с которыми
возможно с использованием ставших уже привычными средств доступа к
данным. Таким образом, ADO.NET можно
использовать в качестве надежного, иерархически организованного
отсоединенного от источника кэша данных
для автономной работы, что незаменимо при построении масштабируемых
приложений, особенно ориентированных на Web.

На сегодняшний день ADO.NET
является наиболее развитой технологией доступа к данным среди
технологий, разработанных корпорацией Microsoft. Она развивает те
принципы, которые были заложены в таких технологиях, как DAO и ADO,
делая их более простыми в применении, более мощными и
универсальными. В то же время ADO.NET
является уже иной технологией доступа к данным.

Рассмотрим архитектуру ADO.NET

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

На рис.
10.1 показаны классы, составляющие объектную модель ADO.NET

Рис. 10.1.  Иерархия
объектов ADO.NET

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

Одной из основных идей, лежащих в основе ADO.NET, является наличие поставщиков
данных.

Поставщик данных - это набор
классов, предназначенных для взаимодействия с хранилищем данных
определенного типа. За счет этого модель ADO.NET является чрезвычайно гибкой и
расширяемой. Рассмотрим уровни модели поставщиков ADO.NET (рис.
10.2).

Рис. 10.2.  Уровни
моделей поставщиков ADO.NET

Как видно из рис.
10.2, приложение .NET взаимодействует с базой данных посредством
поставщиков данных. Каждый поставщик
данных может обеспечивать доступ только к базе данных
определенного формата. Так, для доступа к БД Microsoft SQL Server
используется поставщик SQL Server для .NET, для доступа к БД Oracle
- поставщик Oracle для .NET и т. д. Названные базы данных являются
одними из самых распространенных во всем мире, поэтому поставщики
данных для них выделены в отдельные элементы модели ADO.NET; тем не менее существует множество баз
данных другого формата, к которым необходимо осуществлять доступ из
приложения .NET. Для этого могут применяться поставщики данных OleDb
для .NET или ODBC для .NET, обеспечивающие доступ к любым данным,
для которых существует драйвер OleDb либо ODBC соответственно.

При построении приложения, использующего доступ к
данным, необходимо сперва попытаться найти "родного" поставщика
данных для .NET Когда же такового не существует, можно
воспользоваться OleDb, если существует соответствующий драйвер для
источника данных, к которому устанавливается подключение. Т. к.
технология OleDb существует достаточно давно, создано много
драйверов для различных источников данных, поддерживающих ее. Если в
системе не установлен соответствующий драйвер OleDb, его можно
попытаться найти на сайте разработчика той базы данных, к которой
осуществляется доступ, либо на сайте Microsoft. То же самое
справедливо и для технологии ODBC. В ряде случаев существует
несколько альтернативных вариантов организации доступа к данным
определенного формата. Например, доступ к источнику данных на основе
SQL Server можно организовать используя либо поставщик SQL Server
для .NET, либо поставщик OleDb. Тем не менее всегда предпочтительнее
использовать тот поставщик данных,
который специально предназначен для обеспечения доступа к
конкретному источнику данных, т. к. он учитывает его
особенности.

Каждый поставщик .NET реализует одинаковые базовые
классы - Connection, Transaction, DataAdapter,
Command, Parameter, DataReader имена которых зависят от
поставщика. Например, у поставщика SQL Server существует объект
SqlDataAdapter, у поставщика OleDb —
OleDbDataAdapter и т. д.

У каждого поставщика данных существует собственное
пространство имен. Хотя все поставщики относятся к пространству имен
System.Data, каждый из них содержит свой
подраздел этого пространства, который содержит объекты, специфичные
для данного поставщика. Например, объект SqlDataAdapter находится в пространстве имен
System.Data.SqlClient.

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

При организации доступа к данным с помощью ADO.NET исключительно важную роль играют
объекты, изображенные на рис.
10.1. Рассмотрим их более подробно.

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

Объект Command представляет запрос к источнику
данных, вызов хранимой процедуры или прямой запрос на возврат
содержимого конкретной таблицы. Как известно, существует несколько
типов запросов. Часть из них возвращают данные, извлекаемые из
источника данных, другие изменяют записи, третьи - управляют
структурой БД. С помощью объекта Command
возможно выполнить любой из перечисленных типов запросов. Различия в
поведении объекта Command начинают
проявляться тогда, когда необходимо исполнить тот или иной запрос.
Так, например, при необходимости исполнения запроса, не
возвращающего записи, необходимо использовать метод ExecuteNonQuery объекта Command, а при извлечении данных из БД - метод
ExecuteReader, который, в свою очередь,
возвращает объект DataReader,
позволяющий просматривать полученные в результате запроса
записи.

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

Объект Transaction позволяет осуществлять
группировку записей в логическую единицу работы, называемой
транзакцией. Транзакция логически объединяет несколько различных
действий, связанных с манипулированием данными, в единое целое. В
процессе выполнения действий, осуществляемых в рамках транзакции,
СУБД обычно кэширует изменения, вносимые этими действиями в данные
до момента завершения транзакции. Это позволяет производить отмену
любых изменений, выполненных в рамках транзакции, в случае, если
хотя бы одно из действий транзакции завершилось неудачно.

Объект Parameter позволяет вводить в запрос
элемент, значение которого может быть задано непосредственно перед
исполнением запроса. За счет этого отпадает необходимость каждый раз
изменять текст самого запроса.

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

Объект DataTable позволяет просматривать данные в
виде наборов записей и столбцов. Фактически, он представляет собой
аналог таблицы БД, размещенный в памяти. Достоинством такой
организации является возможность автономной работы с данными. Это
означает, что после установления соединения с базой данных, чтения
данных и заполнения ими объекта DataTable можно отключиться от источника
данных и продолжать работать с ним в автономном режиме. Такая
возможность чрезвычайно полезна при организации Web-приложений,
которые должны быть хорошо масштабируемыми и ориентированы на
многопользовательский режим работы. При этом, однако, возникают и
побочные эффекты, один из которых связан с тем, что человек,
работающий с автономным набором данных, не может увидеть изменений,
вносимых в данные в этот момент другими пользователями.

Объект DataColumn представляет собой столбец
объекта DataTable. Набор же всех столбов
объекта DataTable представляет собой
коллекцию Columns. Посредством этого
объекта можно получить доступ к значению ячейки соответствующего
столбца.

Объект DataRow представляет собой строку объекта
DataTable. Набор всех строк этого
объекта представляет собой коллекцию Rows. DataRow очень часто используется для доступа к
значению конкретного поля определенной записи. При этом применяется
свойство Item.

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

Объект DataRelation представляет собой описание
связей между таблицами реляционной базы данных. Он предоставляется
объектом DataSet и позволяет
организовывать взаимосвязи между таблицами отсоединенного набора
данных объекта DataSet. Объект DataRelation выполняет функцию, аналогичную
той, которую выполняют связи, определяемые в СУБД между таблицами
при создании структур данных. Это касается и соблюдения принципов
ссылочной целостности информации. Например, DataRelation можно настроить таким образом,
чтобы изменения значения первичного ключа родительской таблицы
автоматически передавались (каскадировались) дочерним записям, а при
удалении записи в родительской таблице автоматически удалялись
записи в дочерних таблицах, связанных с ней.

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

Более подробно использование всех объектов модели ADO.NET будет рассмотрено ниже.

Организация взаимодействия с БД

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

Устанавливается соединение, открывается подключение к базе данных.

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

Осуществляется отключение от источника данных. При этом
пользователь работает с отсоединенным набором данных, просматривая
его, выполняя фильтрацию, внося изменения и т. д.

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

Рассмотрим эти этапы более подробно.

Подключение к БД

Как уже говорилось выше, для подключения к источнику
данных с использованием ADO.NET
необходимо воспользоваться объектом Connection. Для рассмотрения
примеров подключений к источникам данных прежде всего необходимо
выбрать поставщика данных, с которым будет работать приложение.
Затем необходимо подключить соответствующие пространства имен,
содержащие определения объектов выбранного поставщика. В качестве
примеров подключения к источникам данных будем рассматривать базы
данных Access и SQL Server 2005 Express Edition. Такой выбор
продиктован прежде всего тем, что Access очень хорошо подходит для
построения небольших информационных систем, нетребователен к
ресурсам компьютера, не требует установки специального программного
обеспечения. SQL Server Express является свободно распространяемой
СУБД, обладающей всеми достоинствами коммерческого SQL Server 2005 и
имеющей ряд ограничений, в том числе на максимальный объем базы
данных (не более 4 Гб). Все приемы работы с базами данных,
описываемые ниже, могут быть использованы также для работы с
коммерческими версиями продуктов Microsoft и другими СУБД.

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

Сервер, на котором находится база данных. Если СУБД, к которой
осуществляется подключение, расположена на клиентском компьютере
(так будет во всех примерах, рассматриваемых в рамках данного
курса), то вместо имени сервера необходимо указывать имя localhost
либо IP-адрес 127.0.0.1.

Имя базы данных, к которой производится подключение.

Способ аутентификации пользователя. Существующие
клиент-серверные СУБД (к которым относится SQL Server, Oracle и
ряд других) позволяют указывать в строке подключения имя
пользователя и пароль, которые будут нужны для проверки
возможности доступа к базе данных, либо использовать параметры
текущего пользователя.

В качестве примера рассмотрим создание небольшого
Web-приложения для работы с базой данных, ER-модель которой
представлена на рис.
10.3.

Рис. 10.3.  ER-модель
фрагмента базы данных

Создадим базы данных в соответствии с моделью,
изображенной на рис.
10.3, в формате SQL Server Express и Access 2003. Для этого
исполним следующие SQL-запросы:

CREATE DATABASE TEST_DB

CREATE TABLE Товары
(КодТовара INTEGER NOT NULL,
НаименованиеТовара VARCHAR (50) NOT NULL,
Цена FLOAT,
PRIMARY KEY (КодТовара))

CREATE TABLE Контрагенты
(КодКонтрагента INTEGER NOT NULL,
НаименованиеКонтрагента VARCHAR (50) NOT NULL,
PRIMARY KEY (КодКонтрагента))

CREATE TABLE Закупки
(КодОперации INTEGER NOT NULL,
ДатаОперации DATETIME NOT NULL,
Количество FLOAT,
Цена FLOAT,
КодКонтрагента INTEGER NOT NULL,
КодТовара INTEGER NOT NULL,
PRIMARY KEY (КодОперации),
CONSTRAINT Входят_в
FOREIGN KEY (КодТовара)
REFERENCES Товары,
CONSTRAINT Осуществляют
FOREIGN KEY (КодКонтрагента)
REFERENCES Контрагенты)

Рис. 10.4.  Диаграмма
базы данных в SQL Server 2005 Express

Построение Web-приложений для работы с базами данных
различных видов практически идентичны. Основное различие заключается
в способе организации доступа к самой базе, т. е. в способе
подключения к ней. Для подключения к БД используется объект Connection из пространства имен System.Data.SqlClient в случае с SQL Server и
System.Data.OleDb - в случае с другими
источниками данных, например, Access. Строки соединения с базами
данных при этом будут выглядеть следующим образом:

string strSqlConnection = "Data Source=localhost\\
sqlexpress;Initial
Catalog=TEST_DB;Integrated Security=SSPI";
string strOleDbConnection = "Provider=Microsoft.Jet.
OLEDB.4.0;Data
Source=C:\\Projects\\Ex_Db\\App_Data\\TEST_DB.mdb";

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

SqlConnection sqlCon=new SqlConnection(strSqlConnection);
sqlCon.Open();
OleDbConnection oleDbCon=new OleDbConnection();
oleDbCon.ConnectionString=strOleDbConnection;
oleDbCon.Open();

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

<connectionStrings>
<add name="TEST_DBConnectionString" connectionString="Provider=
Microsoft.Jet.OLEDB.4.0;Data Source=C:\Projects\Ex_Db\App_Data\
TEST_DB.mdb"/>
</connectionStrings>

Впоследствии эту строку можно извлечь из файла web.config.

"string strOleDbConnection = WebConfigurationManager.
ConnectionStrings
["TEST_DBConnectionString"].ConnectionString;

Управление соединением осуществляется очень легко.
Методы Open и Close объекта Connection выполняют всю работу. Однако
следует учитывать, что при подключении к базе данных может произойти
сбой, в результате которого установить соединение с ней окажется
невозможно. Это может быть особенно актуальным при размещении базы
данных на другом сервере, который в момент подключения может
оказаться недоступным. Для того чтобы неудавшаяся попытка соединения
с базой данных не приводила к фатальным последствиям при работе
приложения, необходимо использовать конструкции try catch, позволяющие адекватно реагировать
на возникшую ошибку. Следующий пример демонстрирует возможность
использования такого подхода.

try
{
sqlCon.Open();
lbl_DB.Text = "<b>Сервер:</b>"+sqlCon.ServerVersion;
lbl_DB.Text += "</br><b>Соединение:</b>" +
sqlCon.ToString();
}
catch(Exception ex)
{
lbl_DB.Text = "При соединении с БД произошла ошибка ";
lbl_DB.Text += ex.Message;
}
finally
{
sqlCon.Close();
lbl_DB.Text += "</br><b>Соединение:</b>";
lbl_DB.Text += sqlCon.State.ToString();
}

Результатом работы этого фрагмента кода в случае
успешного установления соединения будет сообщение, изображенное на
рис.
10.5.

Рис. 10.5.  Окно
сообщения о параметрах и состоянии текущего соединения с БД

На рис.
10.6 изображено окно, содержащее сообщение об ошибке
установления соединения.

Рис. 10.6.  Окно
сообщения об ошибке в момент подключения к БД

Для обслуживания соединения с базой данных расходуются
ресурсы компьютера, на котором выполняется Web-приложение. В связи с
этим рекомендуется открывать соединение как можно позже, а закрывать
как можно раньше, сразу после того, как все необходимые действия с
объектами базы данных были выполнены. Кроме того, желательно строить
программный код так, чтобы соединение с базой
данных закрывалось при любом исходе соединения с ней. В
предыдущем примере код, расположенный в блоке finally, выполнится при любом исходе попытки
подключения, что гарантирует освобождение ресурсов сервера, не
дожидаясь момента, когда память будет освобождена сборщиком
мусора.

Выполнение команд над наборами данных

Установление соединения является необходимым условием
подключения к базе данных. Однако подключение необходимо для того,
чтобы выполнять определенные команды над элементами базы. Рассмотрим
возможности выполнения команд над источниками данных.

Одним из основных элементов из набора классов ADO.NET, способным выполнять любой
SQL-оператор, является класс Command.
Для того, чтобы использовать класс Command, необходимо установить тип команды,
установить текст запроса SQL и привязать ее к соединению с БД.

Существует 3 типа команд класса Command:

CommandType.Text

Выполнение прямого оператора
SQL, текст которого устанавливается в свойстве CommandText. Это значение по
умолчанию.

CommandType.StoredProcedure

Выполнение хранимой процедуры,
имя которой установлено в свойстве CommandText.

CommandType.TableDirect

Выполнение опроса всей таблицы
базы данных, имя которой задается в свойстве CommandText. Этот тип команды
используется для обратной совместимости с некоторыми
драйверами OLE DB и не поддерживается поставщиком данных SQL
Server.

Пример создания объекта Command для выполнения SQL-запроса и хранимой процедуры представлен
ниже.

SqlCommand cmd_SQL = new SqlCommand("Select * From Товары",
sqlCon);
cmd_SQL.CommandType = CommandType.Text;

SqlCommand cmd_Proc=new SqlCommand("GetGoods",sqlCon);
cmd_Proc.CommandType = CommandType.StoredProcedure;

Для выполнения созданной команды необходимо
использовать один из следующих методов.

ExecuteReader()

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

ExecuteNonQuery()

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

ExecuteScalar()

Выполнение SQL-команды и
возврат первой строки результата запроса. Обычно используется
для выполнения команды, содержащей агрегирующие функции типа
COUNT(), MAX() и т. д.

При использовании метода ExecuteReader() создается объект DataReader, с помощью которого можно
организовать перебор всех строк возвращенного набора данных. Для
этого необходимо реализовать цикл по строкам результирующего набора
данных. Объект DataReader представляет
собой один из самых простых и быстрых способов получения доступа к
данным БД. В следующем примере демонстрируется способ отображения
содержимого таблицы "Товары" в виде списка.

SqlDataReader rdr_SQL = cmd_SQL.ExecuteReader();
StringBuilder strResult=new StringBuilder("");
while (rdr_SQL.Read())
{
strResult.Append("<li>");
strResult.Append("Код товара <b>");
strResult.Append(rdr_SQL["КодТовара"]);
strResult.Append("</b>, Наименование товара <b>");
strResult.Append(rdr_SQL.GetString(1));
strResult.Append("</b>");
strResult.Append(", Цена <b>");
strResult.Append(rdr_SQL.GetDouble(2));
strResult.Append("</b></li>");
}
rdr_SQL.Close();
lbl_Result.Text = strResult.ToString();

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

В данном примере используется класс StringBuilder, который, как уже
демонстрировалось ранее, более эффективно работает со строками. Те
же самые операции со строками возможны и с применением стандартной
операции конкатенации. Результат работы программы представлен на рис.
10.7.

Рис. 10.7.  Построение
списка на основе содержимого таблицы "Товары"

Использованный подход достаточно трудоемок для того,
чтобы его применять на практике для вывода информации, содержащейся
в базе данных. В ASP.NET существует несколько более мощных классов,
способных производить данную операцию. Одним из таких классов
является класс GridView. Применение
данного класса позволяет осуществлять вывод информации на экран
очень простым способом. В следующем примере показан способ вывода
информации, получаемой из БД посредством объекта DataReader.

GridView1.DataSource = rdr_SQL;
GridView1.DataBind();

Результат работы данного кода представлен на рис.
10.8.

Рис. 10.8.  Результат
работы программы, отображающей информацию с помощью GridView

Метод ExecuteNonQuery()
используется для выполнения команд, которые не возвращают
результирующих наборов данных. К таким командам относятся команды
вставки, удаления и обновления данных. Результатом работы метода
ExecuteNonQuery() является количество
обработанных записей. В следующем примере демонстрируется
возможность удаления товара из таблицы "Товары".

string strSqlConnection = "Data Source=localhost\\
sqlexpress;Initial Catalog=TEST_DB;Integrated Security=SSPI";
sqlCon = new SqlConnection(strSqlConnection);
SqlCommand cmdDelete = new SqlCommand("DELETE FROM Товары
WHERE КодТовара=5",sqlCon);
try
{
sqlCon.Open();
int n = cmdDelete.ExecuteNonQuery();
lbl_Delete.Text += String.Format("Удалено {0}
записей</br>",n);
}
catch (SqlException ex)
{
lbl_Delete.Text += String.Format("Ошибка: {0}</br>",
ex.Message);
}
finally
{
sqlCon.Close();
}

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

Рис. 10.9.  Результат
работы программы, удаляющей запись из таблицы "Товары"

В реальных ситуациях практически всегда пользователю
требуется ввести некоторое условие, в соответствии с которым будет
выполняться запрос SQL. Таким условием, например, может быть ввод
наименования товара, подлежащего удалению. Для реализации такой
возможности в предыдущем примере добавим элемент управления TextBox, предназначенный для ввода
наименования товара, который необходимо удалить, а также изменим
текст строки, формирующей команду, следующим образом:

SqlCommand cmdDelete = new SqlCommand("DELETE FROM Товары
WHERE НаименованиеТовара='"+tb_Delete.Text+"'",sqlCon);

Как видно, в данном случае, строка SQL-запроса удаления записи из таблицы "Товары"
формируется динамически в зависимости от введенного в элемент tb_Delete значения. Такая практика
динамического формирования запросов в реальных приложениях
допустима, однако при этом могут возникать проблемы, связанные с
безопасностью Web-приложения, например атаки внедрением SQL.
Подробнее о такого рода атаках можно прочитать в [1]. Суть этого вида нарушения безопасности
приложения состоит в том, что пользователь может ввести в поле ввода
параметра текст, отличный от того, чего от него ожидает приложение.
Это может приводить к тому, что текст SQL-запроса фактически изменяется и выполняет
не то действие, на которое рассчитывал программист при его
реализации. Например, если в предыдущем примере в элемент tb_Delete ввести следующий текст "Какой-то
товар' OR '1'='1", то в результате будут удалены все записи из
таблицы "Товары", т. к. текст SQL-запроса
в этом случае, после подстановки значения введенного в элемент tb_Delete, будет

"DELETE FROM Товары WHERE НаименованиеТовара=
'Какой-то товар' OR '1'='1'"

Как видно из этого примера, наименование введенного
товара становится неважным, т. к. условие всегда будет истинным за
счет добавления оператора OR с заведомо верным условием. Можно
привести примеры и более сложных атак внедрением SQL, приводящих к
еще более серьезным и масштабным последствиям и создающих реальную
угрозу функционирования всего Web-приложения.

Решений данной проблемы может быть несколько.
Во-первых, можно постараться проанализировать текст, введенный
пользователем, до того как он будет подставлен в текст SQL-запроса. При этом можно анализировать длину
введенного текста (если длина превышает стандартную длину вводимых
данных, ожидаемых от пользователя, следует предпринять определенные
действия и, возможно, заблокировать выполнение запроса), можно
заменить все одиночные кавычки двумя одиночными кавычками. Хорошей
практикой в этом случае считается обработка исключительных ситуаций,
возникающих при работе с базой данных, и выдача соответствующих
сообщений об ошибке. При этом текст сообщения об ошибке должен
содержать только общую формулировку возникшей ошибки, т. е. не стоит
выводить текст ошибки, передаваемый в объекте Exception.

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

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

Использование параметризованных команд

Параметризованная команда -
это обычная SQL-команда, в тексте которой используются специальные
символы, которые указывают место для динамической подстановки
значений, передаваемых объекту Command
через коллекцию Parameters. Например,
команда удаления записи из таблицы "Товары", использованная выше,
будет выглядеть следующим образом при использовании параметра:

DELETE FROM Товары WHERE НаименованиеТовара=@ProductName

Здесь @ProductName
представляет собой параметр, значение которого должно быть
установлено до того, как будет запущено выполнение запроса.
Синтаксис параметризованных команд отличается в разных поставщиках
данных. Приведенный выше пример справедлив для взаимодействия с SQL
Server. Для использования той же команды при взаимодействии с Access
тот же запрос должен выглядеть следующим образом:

DELETE FROM Товары WHERE НаименованиеТовара=?

Текст программы, реализующей подключение к наборам
данных SQL Server и Access, установку значений параметров запросов и
их исполнение, приведен ниже.

OleDbConnection AccessCon=new
OleDbConnection(strOleDbConnection);
string strSQLServer = "DELETE FROM Товары WHERE Наименование
Товара=@ProductName";
string strAccess = "DELETE FROM Товары WHERE Наименование
Товара=?";
SqlCommand cmdDeleteSQLServer = new SqlCommand
(strSQLServer,sqlCon);
OleDbCommand cmdDeleteAccess=new OleDbCommand
(strAccess,AccessCon);
try
{
cmdDeleteSQLServer.Parameters.Add("@ProductName",
tb_Delete.Text);
sqlCon.Open();
int n = cmdDeleteSQLServer.ExecuteNonQuery();
lbl_Delete.Text += String.Format("Из базы данных SQL Server
удалено {0} записей</br>",n);

cmdDeleteAccess.Parameters.Add("ProductName",
tb_Delete.Text);
AccessCon.Open();
int k = cmdDeleteAccess.ExecuteNonQuery();
lbl_Delete.Text += String.Format("Из базы данных Access
удалено {0} записей</br>", k);

}
catch (Exception ex)
{
lbl_Delete.Text += String.Format("Ошибка: {0}</br>",
ex.Message);
}
finally
{
sqlCon.Close();
AccessCon.Close();
}

Результат работы программы изображен на рис.
10.10.

Рис. 10.10.  Результат
работы программы удаления записи из таблицы "Товары" баз данных SQL
Server и Access

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

Использование хранимых процедур

При использовании сложной СУБД, реализующей полный
набор функций клиент-серверной СУБД, разработчик приложения,
взаимодействующего с базой данных, получает в свое распоряжение
несколько дополнительных очень мощных инструментов, позволяющих
облегчить, ускорить и обезопасить процесс взаимодействия с базой
данных по сравнению с локальными базами данных. Одними из основных
таких возможностей являются способы организации и использования
хранимых процедур и триггеров. Вопросы создания и программирования
хранимых процедур и триггеров, а также всевозможные вопросы,
связанные с особенностями их реализации в конкретных СУБД, выходят
за рамки данного курса. Желающим более подробно ознакомиться с
особенностями реализации такого рода элементов рекомендуется
ознакомиться с [5], [6]. Здесь же будут в основном рассмотрены вопросы
использования хранимых процедур при работе с SQL Server без
объяснения того, как именно создавалась та или иная хранимая процедура.

Хранимая процедура
представляет собой набор операторов SQL, сохраненный в базе данных
(на стороне сервера), имеющий имя, а также набор параметров,
посредством которых могут быть переданы значения в хранимую
процедуру. Они подобны процедурам, используемым в языках
программирования, с той лишь разницей, что в данном случае
предназначены для обработки данных, содержащихся в базе данных.

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

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

Рассмотрим пример создания и использования простой
хранимой процедуры, позволяющей добавить новый товар в таблицу
"Товары" базы данных. Текст хранимой процедуры, реализованной в СУБД
SQL Server 2005, выглядит следующим образом:

CREATE PROCEDURE AddProduct
@ProductID int,
@ProductName varchar(100),
@ProductPrice float
AS
INSERT INTO Товары
VALUES(@ProductID,@ProductName,@ProductPrice)

Данная хранимая процедура
использует три значения переменных для формирования запроса на
добавление данных в таблицу "Товары".

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

SqlCommand cmd_SQL=new SqlCommand("AddProduct",sqlCon);
cmd_SQL.CommandType = CommandType.StoredProcedure;
string[] strProduct = tb_AddProduct.Text.Split(new char[]
{','});
cmd_SQL.Parameters.Add(new SqlParameter("@ProductID",
SqlDbType.Int, 4));
cmd_SQL.Parameters["@ProductID"].Value =
Convert.ToInt32(strProduct[0]);
cmd_SQL.Parameters.Add(new SqlParameter("@ProductName",
SqlDbType.NVarChar, 100));
cmd_SQL.Parameters["@ProductName"].Value = strProduct[1];
cmd_SQL.Parameters.Add(new SqlParameter("@ProductPrice",
SqlDbType.Float, 8));
cmd_SQL.Parameters["@ProductPrice"].Value =
Convert.ToDouble(strProduct[2]);

try
{
sqlCon.Open();
int k = cmd_SQL.ExecuteNonQuery();
}
finally
{
sqlCon.Close();

}

В процессе работы данной программы текст, введенный
пользователем в элемент TextBox,
преобразуется в массив строк. В коллекцию Parameters объекта SqlConnection добавляется три параметра,
соответствующих параметрам хранимой процедуры. Для них
устанавливаются значения, ранее помещенные в массив строк. После
чего открывается соединение и посылается запрос на исполнение
хранимой процедуры посредством команды ExecuteNonQuery().

Из данного примера видно, что при добавлении нового
параметра, выступающего в роли параметра хранимой процедуры,
необходимо обязательно указывать его тип, а также длину (для
строковых типов это количество символов, для числовых - количество
байт).

Отсоединенные наборы данных

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

Основной идеей использования отсоединенных наборов
данных является изменение алгоритмов взаимодействия приложения с
базой данных за счет подключения к набору данных, выполнения запроса
и создания копии данных на стороне клиента, отключения от БД,
осуществления манипуляций с данными на стороне клиента, при
необходимости внесения изменений в базу данных, подключения к ней,
передачи изменений и отключения. Таким образом, все основные
манипуляции с данными происходят в отсоединенном наборе данных,
который представляет собой копию данных, хранящихся в БД, а внесение
изменений происходит в одной пакетной операции. Все это уменьшает
время, в течение которого должно быть открыто соединение с БД,
ускоряет работу и упрощает логику взаимодействия приложения с
данными. Рассмотрим объект ADO.NET,
реализующий данный механизм.

Класс DataSet

Объект DataSet представляет
собой контейнер, содержащий объекты DataTable и Relation. DataTable представляет собой таблицу,
состоящую из строк и столбцов. Строки таблицы представлены объектом
DataRow, который, в свою очередь,
представляет собой коллекцию столбцов таблицы (объект DataColumn). Данные в DataSet отсоединены от БД. Все изменения
данных кэшируются в объектах DataRow.
При возникновении необходимости передачи изменений в данных объекта
DataSet существует возможность передачи
только изменившейся части данных, что позволяет значительно
экономить ресурсы канала связи, т. к. в этом случае передается
гораздо меньший объем данных. Обобщая все вышесказанное, можно
сделать вывод о том, что использование объекта DataSet в ряде случаев оказывается более
эффективным, чем DataReader. Наиболее
типичными ситуациями, в которых рекомендуется применять объект DataSet, являются следующие:

Необходимость реализации сериализации данных на диск. DataSet позволяет легко сохранять данные в
файле XML. При этом возможны варианты сохранения либо только
данных, либо только структуры данных, либо и того и другого.

Необходимость организации навигации по набору данных в двух
направлениях. Как уже упоминалось, DataReader обеспечивает перемещение по
набору только вперед. С помощью же DataSet возможна организация постраничного
просмотра данных.

Необходима привязка нескольких элементов управления к одному
набору данных. DataSet, в отличие от
DataReader, содержит средства
организации сортировки и фильтрации данных.

Более подробная информация относительно сценариев
использования DataSet находится в [1].

Важно! Большинство Web-приложений использует
DataSet для извлечения данных из базы данных и показа их на
странице. Для обновления же данных в БД используются прямые
команды.

Использование DataSet

Выше уже говорилось о том, что DataSet состоит из таблиц, которые, в свою
очередь, состоят из строк, а они, в свою очередь, - из столбцов.
Каждый из перечисленных элементов реализован в виде класса. Для
управления автономными изменениями DataSet отслеживает информацию о версии
каждого объекта DataRow. Это означает,
что когда происходит редактирование строки, ее исходное значение
сохраняется в памяти, а строка помечается как измененная.
Аналогичные действия происходят и при добавлении и изменении строк
DataTable. В дальнейшем в базу данных
можно перенести значения только тех строк, которые были затронуты
изменениями. Таким образом, DataSet
никогда не поддерживает постоянного соединения с базой данных.

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

Класс DataAdapter

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

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

Fill

Выполнение запроса типа Select, определенного в свойстве SelectCommand, и добавление таблицы,
получаемой в результате данного запроса, в DataSet.

FillSchema()

Выполнение запроса типа Select, текст которого расположен в
свойстве SelectCommand, и
добавление таблицы, которая содержит только структуру данных,
полученных в результате выполнения запроса, в DataSet.

Update()

Применяет все изменения,
внесенные в DataTable, к источнику
данных. При этом исполняются команды вставки, обновления и
удаления, расположенные в свойствах InsertCommand, UpdateCommand, DeleteCommand.

Рассмотрим пример использования объектов DataSet и DataAdapter для извлечения данных из БД,
наполнения DataSet и отображения данных
на странице Web-приложения.

Прежде всего, необходимо установить подключение к
источнику данных. Для этого нужно использовать строку подключения, а
также объект Connection. В данном
примере подключение будет происходить к базе данных Test_Db, расположенной на локальном сервере
SQL Server Express 2005:

string strCon = "Server=.\SQLEXPRESS;Integrated
Security=SSPI;Initial Catalog=Test_Db";
string sqlString = "SELECT * FROM Товары";
SqlConnection sqlCon = new SqlConnection(strCon);

Создадим объект DataAdapter
и передадим ему в качестве параметров строку запроса, а также строку
подключения к БД:

SqlDataAdapter da = new SqlDataAdapter(sqlString,sqlCon);

Теперь необходимо создать объект DataSet и заполнить
его данными с помощью DataAdapter:

DataSet ds = new DataSet();
da.Fill(ds, "Goods");

Из приведенного выше примера видно, что метод Fill объекта da в
качестве параметров использует имя объекта DataSet, куда необходимо поместить данные,
возвращаемые в результате выполнения запроса, который определен в
строке sqlString. Во втором параметре
можно указать имя, которое будет сопоставлено с таблицей, созданной
в DataSet. Из примера видно, что в
данном случае не используется явный вызов метода Open объекта Connection - данный метод вызывается неявно
при исполнении метода Fill. Таким
образом, DataAdapter сперва открывает
соединение с БД, затем выполняет необходимые манипуляции с данными,
после чего закрывает открытое ранее соединение. Если по каким-то
причинам такой алгоритм взаимодействия с базой данных требуется
изменить, необходимо до вызова метода Fill открыть соединение с БД. В этом случае
DataAdapter будет использовать уже
существующее соединение.

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

Привязка и отображение данных

Привязка данных является
очень мощным средством организации автоматического отображения
данных в элементе управления без использования программирования.

Большинство элементов управления ASP.NET поддерживают
привязку данных. При этом привязка может работать как для данных с
одним значением, так и для данных с множественными значениями.
Привязка с одним значением означает, что элемент управления может
отображать единственное значение, извлекаемое из источника данных.
Такие принципы используются элементами управления TextBox, LinkButton, Image, Hyperlink.
Привязка с множественными значениями означает, что элемент
управления может отображать несколько значений, извлекаемых из
источника данных. Элементы управления, поддерживающие привязку с
множественными значениями, строятся на основе списков и электронных
таблиц. Типичными представителями таких элементов управления
являются ListBox и GridView.

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

Рассмотрим основные принципы использования обоих типов
привязки данных.

Привязка с одним значением

Элементы управления, поддерживающие привязку данных с
одним значением, позволяют осуществить привязку некоторых из их
свойств к данным с помощью выражения привязки данных. Выражение
привязки данных вводится в тексте страницы .aspx и заключается
внутри ограничителей <%# %>.

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

Для того чтобы вычислить выражение привязки, необходимо
в коде приложения вызвать метод Page.DataBind(). В момент вызова данного
метода ASP.NET проверяет все выражения на текущей странице, при
необходимости производит вычисления и заменяет их соответствующими
значениями.

В качестве примера рассмотрим привязку элемента Label к данным таблицы "Товары". Для этого
создадим метод GetProductName(),
возвращающий в качестве значения содержимое первой строки столбца
"НаименованиеТовара" таблицы "Товары".

public string GetProductName()
{
return ds.Tables["Goods"].Rows[0]["НаименованиеТовара"].
ToString();
}

Теперь необходимо добавить элемент Label на страницу .aspx и установить выражение привязки для
свойства Text данного элемента.

<asp:Label ID="Label1" runat="server" Text="<%#GetProductName()
%>"></asp:Label>

В методе Page.Load()
произведем вызов метода Page.DataBind().

Результат работы данного приложения представлен на рис.
10.11.

Рис. 10.11.  Результат
отображения данных с использованием механизма привязки данных с
одним значением

Полный текст исходного кода данного примера выглядит
следующим образом:

public partial class _Default : System.Web.UI.Page
{
private DataSet ds;
protected void Page_Load(object sender, EventArgs e)
{
string strCon = WebConfigurationManager.ConnectionStrings
["Test_Db"].ConnectionString;
string sqlString = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары";
SqlConnection sqlCon = new SqlConnection(strCon);

SqlDataAdapter da = new SqlDataAdapter(sqlString,sqlCon);
ds = new DataSet();
da.Fill(ds, "Goods");
this.DataBind();
}

public string GetProductName()
{
return ds.Tables["Goods"].Rows[0]["НаименованиеТовара"].
ToString();
}
}

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

Кроме выражений привязки, ASP.NET поддерживает также
$-выражения, представляющие собой последовательность кода, которую
можно добавлять на aspx-страницу и которая будет исполняться в
момент обращения к странице. Visual Studio 2005 содержит встроенный
построитель $-выражений, который позволяет извлекать
пользовательские настройки приложения и информацию о строке
подключения, расположенные в файле web.config. Например, для извлечения настройки
приложения по имени MySetting и вывода
ее содержимого на экран можно поместить на странице элемент Literal, в свойстве Text которого установить значение <%$ AppSettings:MySetting %>.

Такого же эффекта можно добиться, если использовать
построитель выражений. Для этого необходимо в режиме редактирования
дизайна открыть свойства объекта Literal
и вызвать построитель выражений, нажав на кнопку
(рис.
10.12). В открывшемся окне, изображенном на рис.
10.13, необходимо выбрать категорию AppSettings и в свойстве
AppSetting установить значение MySetting. Теперь значение
определенное в свойстве MySetting файла web.config, будет отображаться на
странице.

Рис. 10.12.  Вызов
построителя выражений

Рис. 10.13.  Окно
построителя выражений

Конечно, отображение значения свойства, определенного в
файле web.config, имеет незначительное
практическое значение. Однако извлечение таким образом строки
подключения к базе данных, определенной в web.config, и запись ее значения в свойство
ConnectionString объекта SqlDataSource, обеспечивающего подключение к базе данных, способно значительно
облегчить задачу создания универсального механизма подключения к
источнику данных. Использование элемента SqlDataSource будет рассмотрено ниже.

Привязка с множественным значением

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

Каждый элемент, поддерживающий возможность
множественной привязки, содержит следующий набор свойств:

DataSource

Объект, содержащий
привязываемые данные.

DataSourcelD

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

DataTextField

Содержит наименование столбца
(для строки) таблицы или свойство (для объекта) элемента
данных, включающего в себя отображаемое на экране
значение.

DataTextFormatString

Содержит необязательную строку
форматирования, применяемую для отображения данных.

DataValueField

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

Принципы использования всех списковых элементов,
способных отображать связанные данные, одинаков. Для привязки такого
элемента к источнику данных необходимо установить значения свойств
DataSource и DataTextField.

В следующем примере на странице размещены два элемента
- DropDownList1 и ListBox1, которые связаны с одним и тем же
источником данных - в качестве такового выступает таблица
"Товары":

protected void Page_Load(object sender, EventArgs e)
{
string strCon = WebConfigurationManager.ConnectionStrings
["Test_Db"].ConnectionString;
string sqlString = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары";
SqlConnection sqlCon = new SqlConnection(strCon);

SqlDataAdapter da = new SqlDataAdapter(sqlString,sqlCon);
DataSet ds = new DataSet();
da.Fill(ds, "Goods");
DropDownList1.DataSource = ds.Tables["Goods"];
DropDownList1.DataTextField = "НаименованиеТовара";
DropDownList1.DataValueField = "КодТовара";

ListBox1.DataSource = ds.Tables["Goods"];
ListBox1.DataTextField = "НаименованиеТовара";
ListBox1.DataValueField = "КодТовара";

this.DataBind();
}

Так как объект DataSet
может содержать много таблиц, в свойстве DataSource необходимо указать имя таблицы, к
которой необходимо осуществить привязку. Свойство DataTextField содержит наименование столбца
таблицы, значения которого будут отображаться элементом управления.
Свойство DataValueField является
необязательным и в данном случае обозначает то, что при выборе
значения в элементе управления значение свойства SelectedItem этого элемента будет установлено
равным значению поля "КодТовара", соответствующего выбранной записи.
Считать выбранные значения элементов можно следующим образом:

protected void Button1_Click(object sender, EventArgs e)
{
StringBuilder str = new StringBuilder();
str.Append("В Выпадающем списке выбран элемент - ");
str.Append(DropDownList1.SelectedItem.Text);
str.Append(" ему соответствует значение ");
str.Append(DropDownList1.SelectedValue);
str.Append("</br>");
str.Append("В Списке выбраны элементы:</br>");
foreach (ListItem li in ListBox1.Items)
{
if (li.Selected)
{
str.Append(li.Text);
str.Append(" значение - ");
str.Append(li.Value);
str.Append("</br>");
}
}
Label2.Text = str.ToString();
}

Результат работы программы изображен на рис.
10.14.

Рис. 10.14.  Результат
использования элементов множественной привязки данных и вывода
значений выбранных элементов списка

В данном примере элемент ListBox1 поддерживает возможность выбора сразу
нескольких элементов (для этого необходимо установить значение
свойства SelectionMode данного элемента
равным Multiple). Чтение значения
выделенного элемента в списке осуществляется посредством свойства
SelectedItem. Однако в случае с ListBox1 данное свойство использовать нельзя,
т. к. возможно множественное выделение. Для определения каждого
элемента, выделенного в ListBox1,
необходимо применить цикл, где происходит обращение к коллекции
элементов Items, для каждого из которых
имеется свойство Selected. Если значение
данного свойства установлено равным true, данный элемент выделен и его необходимо
отобразить.

Аналогичный принцип можно применить с любым другим
элементом управления, поддерживающим множественное выделение
элементов.

Кроме использованных списковых элементов, ASP.NET
содержит также и более сложные элементы, способные привязываться к
данным и отображать их. К таким элементам относятся GridView, DetailsView, FormView.

GridView представляет собой
таблицу, способную привязываться к данным и отображать содержимое
таблиц. Поддерживает выбор, редактирование, сортировку и перемещение
по страницам данных. DetailsView
предназначен для показа одной записи из таблицы за раз. Поддерживает
редактирование и перемещение по страницам. FormView практически аналогичен DetailsView, с той лишь разницей, что FormView основан на шаблонах, и это делает его
более гибким при отображении информации.

Самым мощным элементом по количеству возможностей
является GridView. Очень часто его
приходится использовать для отображения информации в виде таблицы.
Для привязки GridView к источнику данных
необходимо установить значение свойства DataSource. В следующем примере на странице
размещен объект GridView, который
привязывается к таблице "Товары" и отображает ее содержимое:

protected void Page_Load(object sender, EventArgs e)
{
string strCon = WebConfigurationManager.ConnectionStrings
["Test_Db"].ConnectionString;
string strQuery = "SELECT * FROM Товары";
SqlConnection sqlCon = new SqlConnection(strCon);
DataSet ds = new DataSet();
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = new SqlCommand(strQuery, sqlCon);
da.Fill(ds, "Goods");
GridView1.DataSource = ds.Tables["Goods"];
this.DataBind();
}

Результат работы программы представлен на рис.
10.15.

Рис. 10.15.  Результат
использования GridView на странице Web-приложения

Показанные примеры демонстрируют способы привязки к
источникам данных с использованием программного кода, однако это не
единственная возможность осуществить привязку элемента управления к
источнику данных. Visual Studio 2005 позволяет осуществить привязку
с использованием визуальных средств. Для этих целей .NET Framework
содержит несколько элементов управления источниками данных. Они
расположены на вкладке Data панели Toolbox. Среди них такие как:

SqlDataSource - позволяет
подключаться к любому источнику данных, который имеет поставщика
данных ADO.NET К таким источникам данных
относятся SQL Server, OLE DB, Oracle и другие.

ObjectDataSource -
позволяет подключаться к пользовательскому классу доступа к данным.
Этот подход целесообразно применять при построении крупномасштабных
и сложных приложений. Подробнее об использовании пользовательских
классов доступа к данным и элементе ObjectDataSource будет
рассказано в разделе, посвященном реализации трехуровневой архитектуры доступа к данным в
ASP.NET

AccessDataSource -
позволяет подключаться к базам данных Microsoft Access.

XmlDataSource - позволяет
подключаться к XML-файлу.

SiteMapDataSource -
позволяет подключаться к файлу Web.Sitemap, содержащему описание
навигационной структуры Web-приложения.

Для использования элемента управления источниками
данных достаточно перетащить его с панели Toolbox в область
Web-страницы. При этом он отображается в виде серого прямоугольника,
содержащего внутри наименование элемента управления источником
данных.

Рис. 10.16.
Использование элемента управления источником данных

Для конфигурирования элемента SqlDataSource можно воспользоваться мастером,
запускаемым при нажатии на ссылку Configure
Data Source.

На первом шаге конфигурирования элемента SqlDataSource необходимо определить подключение к базе данных. Если существует уже
настроенное подключение к источнику данных, его просто можно выбрать
из списка; в противном случае, можно создать и настроить новое
подключение к БД, нажав на кнопку New Connection.

Рис. 10.17.  Первый шаг
настройки элемента SqlDataSource на подключение к базе данных

На втором шаге мастер предлагает сохранить
сгенерированную строку подключения к БД в файле web.config. При необходимости можно
отказаться, а также изменить наименование переменной, содержащей
строку подключения.

Третий шаг мастера позволяет определить запросы к БД,
используемые SqlDataSource для
выполнения операций над данными. В частности, предлагается выбрать
таблицу и сгенерировать запрос на выборку данных из нее. Здесь же
можно определить свои собственные запросы на выборку, добавление,
удаление и обновление данных или указать используемую хранимую
процедуру.

Мастер может также сгенерировать стандартные запросы,
используемые для добавления, удаления и обновления значений в
связанном источнике данных. Для построения этих запросов можно
воспользоваться кнопкой Advanced. При этом будет открыто окно, в
котором можно установить два свойства. Свойство Generate INSERT,
UPDATE, and DELETE statements задает режим создания соответствующих
запросов. Свойство Use optimistic concurrency задает режим
определения тех строк, которые были изменены с момента последней
загрузки данных в объект DataSet.

Рис. 10.18.  Второй шаг
мастера настройки SqlDataSource на подключение к БД

Рис. 10.19.  Третий шаг
мастера настройки SqlDataSource на подключение к БД

Рис. 10.20.  Окно
генерации запросов на вставку, обновление и удаление данных из БД

Четвертый шаг мастера позволяет протестировать
созданный запрос и, после нажатия на кнопку Finish, завершить
настройку элемента управления источником данных.

В результате настройки параметров объекта SqlDataSource устанавливается целый ряд
значений свойств элементов. Основными из них являются следующие (рис.
10.21).

Рис. 10.21.  Окно свойств
элемента SqlDataSource, установленных с помощью мастера

DataSourceMode — задает
режим, используемый для извлечения строк. Возможные значения — DataSet и DataReader.

ConnectionString — задает
строку подключения к источнику данных.

DeleteCommandType — задает
тип запроса Delete. Возможные значения -
Text и StoredProcedure.

DeleteQuery — задает
запрос, используемый для удаления данных из связанного
источника.

Последние два параметра применяются также и для других
запросов - на изменение, добавление и выборку данных.

При необходимости можно изменить любой запрос. Для
этого нужно щелкнуть на кнопку ,
расположенную в строке редактируемого запроса. В результате будет
открыто окно редактирования запроса. Пример такого окна для
редактирования запроса на изменение данных изображен на рис.
10.22.

Рис. 10.22.  Пример окна
редактирования запроса на изменение данных

После того как настройки элемента управления источником
данных завершены, необходимо связать с ним элемент отображения
данных на экране. Для этого нужно указать имя объекта SqlDataSource в свойстве DataSourceID элемента отображения данных. На
рис.
10.23 показана возможность привязки GridView к SqlDataSource и установка несколько
специфических режимов отображения данных (удаление, редактирование,
выбор строк, сортировка данных в столбцах).

Рис. 10.23.  Привязка
GridView к элементу управления источником данных и задание
специфических режимов их отображения

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

При использовании данного подхода к привязке данных
вызов метода DataBind() в коде
приложения становится необязательным.

Использование параметров в запросах

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

Поместим на форму элементы DropDownList и SqlDataSource. Настроим данные элементы на
подключение к источнику данных, содержащему список товаров.
Определения этих элементов выглядят следующим образом:

<asp:DropDownList ID="DropDownList1" runat="server"
DataSourceID="SqlDataSource1"
DataTextField="НаименованиеТовара" DataValueField=
"КодТовара" Width="196px" AutoPostBack="True">
</asp:DropDownList>

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$
ConnectionStrings:TEST_DBConnectionString %>"
SelectCommand="SELECT DISTINCT [КодТовара],
[НаименованиеТовара] FROM [Товары]">
</asp:SqlDataSource>

Следует обратить внимание на то, что свойство AutoPostBack элемента DropDownList установлено равным true. Это необходимо для обеспечения
автоматического инициирования обратной отсылки при изменении
текущего элемента списка, чтобы страница изменялась при выборе
другого элемента списка. Нужно сделать так, чтобы при выборе города
из списка содержимое элемента GridView,
отображающего закупки, обновлялось. Для реализации данного механизма
поместим на форму элементы GridView и
еще один - SqlDataSource. Настроим SqlDataSource в соответствии со следующим
определением:

<asp:SqlDataSource ID="SqlDataSource2" runat="server"
ConnectionString="<%$ ConnectionStrings:
TEST_DBConnectionString %>"
SelectCommand="SELECT Закупки.КодОперации,
Закупки.ДатаОперации, Контрагенты.НаименованиеКонтрагента
AS Контрагент, Закупки.Количество, Закупки.Цена, Закупки.
Количество * Закупки.Цена AS Стоимость FROM Закупки INNER
JOIN Контрагенты ON Закупки.КодКонтрагента = Контрагенты.
КодКонтрагента WHERE (Закупки.КодТовара = @Product )">
<SelectParameters>
<asp:ControlParameter ControlID="DropDownList1"
Name="Product" PropertyName="SelectedValue" />
</SelectParameters>

</asp:SqlDataSource>

Ключевыми элементами настройки SqlDataSource являются строка запроса,
определенная в свойстве SelectCommand, и
параметры, определенные в разделе <SelectParameters>. В строке запроса
определен параметр @Рroduct, который
используется для фильтрации списка закупок по значению кода товара.
В разделе параметров определен один параметр, для которого
установлено имя Product. Значение для
этого параметра извлекается из свойства SelectedValue объекта DropDownList1. Пример экрана браузера,
получаемого после запуска данного приложения, представлен на рис.
10.24.

Рис. 10.24.  Результат
работы приложения, содержащего главную и подчиненную таблицы

Рис. 10.25.  Диалоговое
окно редактирования параметров запроса

В данном примере значение параметра Product подставляется автоматически из
свойства SelectedValue элемента DropDownList. Тем не менее существует
возможность использования и других элементов управления, значения
свойств которых можно задействовать для этих целей. Для того чтобы
настроить извлечение параметра из элемента управления и подстановку
его значения в качестве параметра запроса, определенного в SqlDataSource, можно воспользоваться
визуальными средствами Visual Studio 2005. Для вызова диалогового
окна редактора параметров (рис.
10.25) нужно щелкнуть по кнопке
окна.

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

Control

Свойство элемента
управления.

Cookie

Значение Cookie набора.

Form

Переменная формы. С помощью
данного типа источника данных параметра можно извлекать
значения, отправленные странице элементом ввода.

Profile

Значение текущего профиля
пользователя.

QueryString

Строковое значение запроса.
Позволяет извлекать значение из текущей строки запроса.

Session

Значение состояния
сеанса.

Обновление данных

SqlDataSource предоставляет
возможность изменения данных, содержащихся в источниках данных. Для
того, чтобы воспользоваться этой возможностью, необходимо определить
соответствующие запросы, выполняющие операции вставки, обновления и
удаления записей. Все они создаются по аналогии с запросом Select на основе параметризованного запроса.
Выше уже упоминалось об имеющихся в SqlDataSource возможностях построения запросов
данного типа. В качестве примера добавим возможность изменения
данных в страницу просмотра товаров и заказов, созданную ранее. Для
этого выполним нижеследующие операции.

Определим в SqlDataSource
запрос на обновление данных (UpdateQuery). Текст запроса будет выглядеть
следующим образом:

UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество =
@Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE
(КодОперации = @КодОперации)

При составлении запроса важно давать имена параметрам
таким образом, чтобы они совпадали с именами столбцов таблицы. Это
объясняется тем, что ASP.NET автоматически отправляет значения
параметрам, взятым из столбцов, у которых имена совпадают с именами
параметров.

В данном же примере присутствует еще одна особенность,
которую следует учитывать. Она состоит в том, что ранее для удобства
отображения данных был использован запрос, в котором происходит
внутреннее соединение данных из таблиц "Контрагенты" и "Закупки", -
он необходим для подмены номеров контрагентов их наименованиями.
Поэтому при отображении информации в GridView мы видим наименования контрагентов
вместо их номеров. Однако, это создаст некоторые трудности при
реализации обновления данных. Дело в том, что теперь мы не можем
реализовать обновление данных контрагентов, так как они находятся в
таблице "Контрагенты", а SqlDataSource
связан с таблицей "Закупки". Все, что мы можем сделать, - это
изменить номер текущего контрагента в редактируемой операции
закупки. При этом необходимо будет вводить не наименование
контрагента, а его номер. Именно этим объясняется наличие в запросе
параметра КодКонтрагента = @Контрагент,
обновляющего и нформацию о контрагенте текущей операции закупки.

Добавим в GridView новый
столбец. Для этого в интеллектуальном дескрипторе выполним команду
Add New Column... (рис.
10.26).

увеличить изображение
Рис. 10.26.
Добавление столбца в GridView с помощью интеллектульного дескриптора

В открывшемся окне (рис.
10.27) установим тип поля CommandField, а также убедимся, что установлен
флажок Edit/Update. Если требуется
добавить также и возможность удаления элемента, необходимо
установить флажок Delete.

Рис. 10.27.  Окно
добавления нового столбца к GridView

После этого в GridView
будет добавлен столбец, в каждой строке которого рядом со значениями
элементов строк будет добавлена ссылка Edit. После щелчка по этой ссылке
соответствующая строка GridView будет
переведена в режим редактирования, в результате чего все ячейки, за
исключением столбцов, доступных только для чтения и не определенных
в команде Update, превратятся в
текстовые поля, а ссылка Edit будет
заменена ссылками Update и Cancel, предназначенными для подтверждения
внесения изменений в БД либо для их отмены (рис.
10.28).

Рис. 10.28.
Редактирование содержимого таблицы "Закупки" с помощью GridView

При обновлении данных в базе могут возникать серьезные
затруднения, вызванные возможностью одновременной работы большого
количества пользователей с одними и теми же данными. Это особенно
актуально для Web-приложений, количество пользователей которых может
быть в несколько раз больше, чем для традиционных win32-приложений.
Достаточно подробное описание возможных проблем и путей их решения
можно найти в [7]. Остановимся лишь на некоторых из них,
наиболее актуальных и востребованных.

Особые случаи обновления данных

Если посмотреть на текст запроса на обновление данных
таблицы "Закупки", использованный в предыдущем примере, можно
заметить, что очень важным его элементом является строка WHERE (КодОперации = @КодОперации). Она
позволяет обновить только ту строку, которая соот ветствует
значению, определенному в параметре "КодОперации". Этот код хорошо
работает, если в один и тот же момент времени одну запись пытается
обновить только один человек. Если же представить, что в момент
редактирования нами этой записи другой пользователь сможет изменить
значение поля "КодОперации", то попытка обновления таблицы
"Закупки", предпринимаемая нами, завершится неудачей, т. к. в
таблице больше не существует строки с тем значением кода операции,
которое было взято в качестве параметра в момент начала
редактирования. Поэтому, чтобы быть уверенным в том, что попытка
обновления завершится успешно, необходимо использовать лишь те
значения первичных ключей, которые доступны только для чтения. Кроме
того, параметр, который используется для хранения исходного значения
ключевого поля, необходимого для обновления записи, рекомендуется
помечать префиксом. Задать префикс можно с помощью свойства OldValuesParameterFormatString объекта SqlDataSource. Обычно в качестве префикса
используется значение original_. Так, в
приведенном выше примере строка WHERE с
учетом префикса может быть записана следующим образом:

WHERE КодОперации=@original_КодОперации

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

UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество =
@Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE
(КодОперации = @original_КодОперации AND ДатаОперации=
@original_ДатаОперации AND Количество=@originalКоличество
AND Цена=@original_Цена AND КодКонтрагента=@original_Контрагент)

Проблема в данном случае состоит в том, что
использование префикса возможно только для ключевых полей таблицы,
определенных в свойстве DataKeyNames
объекта GridView. Понятно, что
невозможно определить все поля таблицы в качестве ключевых. Поэтому
лучшим решением будет использование свойства ConflictDetection объекта SqlDataSource равным значению CompareAllValues.

Рассмотренные способы привязки элементов отображения к
источникам данных, а также возможности объекта SqlDataSource целесообразно применять только в
относительно небольших приложениях. При необходимости создания
сложного Web-приложения использование SqlDataSource может привести к возникновению
многих проблем, вызванных недостатками данного элемента. К
недостаткам SqlDataSource можно отнести
следующие:

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

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

Использование DataView для фильтрации и сортировки данных

При создании сложных приложений, ориентированных на
активное взаимодействие с пользователями, большое значение имеет
отображение данных и возможности настройки этого отображения.
ASP.NET предоставляет возможность такой настройки отображения
извлекаемой из базы данных информации посредством использования
класса DataView. Этот класс снабжен
возможностью фильтрации и сортировки отображаемых данных, причем эти
режимы никак не затрагивают реальные данные. Таким образом, DataView очень удобен в случаях, когда
необходимо настраивать возможность показа только части данных
таблицы, которые извлекаются из БД без необходимости изменять или
обрабатывать данные, необходимые для других задач.

Каждый объект DataTable по
умолчанию имеет ассоциированный с ним объект DataView. При этом допускается создание
множества объектов DataView для создания
множества представлений одной и той же таблицы. Для обращения к
представлению по умолчанию объекта DataTable необходимо обратиться к свойству
DataTable.DefaultView.

Рассмотрим примеры использования основных функций DataView.

Сортировка данных

Сценарий использования объекта DataView выглядит следующим образом:

Создать объект DataView.

Настроить его параметры сортировки или фильтрации.

Привязать его к элементу отображения данных.

Инициировать процесс привязки данных.

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

protected void Page_Load(object sender, EventArgs e)
{
string ConnectionString = WebConfigurationManager.
ConnectionStrings["TEST_DB"].ConnectionString;
SqlConnection con = new SqlConnection(ConnectionString);
string query = "SELECT * FROM Товары";
SqlDataAdapter da = new SqlDataAdapter(query, con);
DataSet ds = new DataSet();
da.Fill(ds, "Products");

DataView dv = new DataView(ds.Tables["Products"]);
dv.Sort = "НаименованиеТовара";
GridView1.DataSource = dv;
Page.DataBind();
}

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

Фильтрация данных

Для выполнения фильтрации данных при помощи DataView нужно воспользоваться свойством RowFilter. Содержимое RowFilter очень напоминает команду Where запроса SQL. Здесь записывается условие
фильтрации набора данных. При этом возможно применение тех же
операций сравнения и логических операций, которые используются в
языке SQL. Кроме того, здесь возможно выполнение простейших
вычислительных операций, таких как сложение, вычитание, умножение,
деление, вычисление остатка от деления.

Рассмотрим пример фильтрации данных таблицы
"Товары".

Для отображения информации о товаре "Товар1" необходимо
выполнить следующую команду:

dv.RowFilter = "НаименованиеТовара='Товар1'";

Следующий пример отобразит товары, цена которых лежит в
диапазоне от 10 до 100:

dv.RowFilter = "Цена>10 AND Цена<100";

DataView позволяет также
отображать строки DataTable, находящиеся
в одном из следующих состояний: вставленная, удаленная,
модифицированная, неизмененная. Для этого необходимо задать
соответствующее значение для свойства RowStateFilter. Следующий пример позволит
отобразить только вновь добавленные строки:

dv.RowStateFilter = DataViewRowState.Added;

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

Например, пусть требуется отобразить только те товары,
средняя стоимость закупки которых была выше 50.

Для создания такой фильтрации необходимо создать в
DataSet и заполнить две таблицы -
"Товары" и "Закупки", а также создать связь между ними. Для этого
нужно выполнить следующий код:

string ConnectionString = WebConfigurationManager.
ConnectionStrings["TEST_DB"].ConnectionString;
SqlConnection con = new SqlConnection(ConnectionString);
string query = "SELECT * FROM Товары";
SqlDataAdapter da = new SqlDataAdapter(query, con);
DataSet ds = new DataSet();
da.Fill(ds, "Products");
string query2 = "SELECT * FROM Закупки";
da.SelectCommand.CommandText = query2;
da.Fill(ds, "Purchase");
DataRelation rel = new DataRelation("ProductsPurchase",
ds.Tables["Products"].Columns["КодТовара"],
ds.Tables["Purchase"]. Columns["КодТовара"]);
ds.Relations.Add(rel);

После этого используем следующее условие для
фильтрации, задаваемое в свойстве RowFilter:

DataView dv = new DataView(ds.Tables["Products"]);
dv.Sort = "НаименованиеТовара";
dv.RowFilter = "AVG(Child(ProductsPurchase).Цена)>50";
GridView1.DataSource = dv;

Как видно из примера, для подсчета средней цены
используется функция AVG(). Обращение
Child позволяет сослаться на подчиненную
таблицу, установленную ранее в свойстве связи DataRelation. Для определения подчиненной и
главной таблиц применяется созданная ранее связь ProductsPurchase.

Реализация трехуровневой архитектуры доступа к данным в
ASP.NET

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

В сложных профессиональных приложениях код доступа к
базе данных не встраивается в само приложение, а инкапсулируется в
отдельный выделенный класс. Для выполнения операции с базой данных
приложению нужно создать экземпляр этого класса и вызвать
соответствующий метод. При создании такого класса будем следовать
ряду рекомендаций.

Необходимо открывать соединение с базой
данных перед выполнением операции с ней и закрывать сразу
после завершения этой операции.

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

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

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

Нельзя подключаться к источнику данных с использованием
клиентского идентификатора пользователя. Для более эффективной
организации взаимодействия приложения с БД лучше использовать
систему безопасности на основе ролей либо билетов.

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

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

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

Рассмотрим пример реализации взаимодействия
Web-приложения с таблицей "Товары" базы данных "TestDb" с
использованием принципов трехуровневой архитектуры.

Прежде всего, создадим класс Product, который содержит поля,
соответствующие полям таблицы "Товары". Доступ к этим полям должен
осуществляться посредством соответствующих свойств. В типичном
объектно-ориентированном приложении такие классы выполняют роль
хранилища данных, доступ к которым предоставляют методам,
осуществляющим их обработку. В достаточно большом и сложном
приложении классы желательно размещать отдельно от основного кода
программы. Это позволяет легко отделять код основной программы от
кода, который может быть перенесен в другие приложения и повторно
использоваться для облегчения и ускорения создания приложения. Для
создания класса рекомендуется выполнить команду Website Add New Item, в появившемся диалоговом окне
выбрать в качестве типа создаваемого элемента Class, ввести имя класса и нажать кнопку Add. Появившееся диалоговое окно, изображенное
на рис.
10.29, сообщает, что классы, используемые приложением, должны
быть помещены в папку Арр_Code.
Рекомендуется поместить создаваемый класс в эту папку.

увеличить изображение
Рис. 10.29.
Диалоговое окно создания нового класса в папке App_Code

Текст класса Product
приведен ниже.

public class Product
{
private int productID;
private string productName;
private double productCost;

public int ProductID
{
get { return productID; }
set { productID = value; }
}

public string ProductName
{
get { return productName; }
set { productName = value; }
}

public double ProductCost
{
get { return productCost; }
set { productCost = value; }
}

public Product(int productID, string productName, double
productCost)
{
this.productID = productID;
this.productName = productName;
this.productCost = productCost;
}
}

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

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

Создадим класс ProductsDB,
как было описано выше. Определим в классе закрытую переменную connectionString, содержащую строку
подключения, которая в момент создания класса (в конструкторе)
считывается из файла web.config и
помещается в connectionString. В web.config параметр, содержащий строку
подключения, может называться по-разному, поэтому можно
предусмотреть два конструктора: один — настроенный на извлечение
строки подключения из жестко заданного параметра, второй —
принимающий имя данного параметра и использующий его для чтения
строки подключения:

public class ProductsDB
{
private string connectionString;
public ProductsDB()
{
connectionString = WebConfigurationManager.ConnectionStrings
["Test_Db"].ConnectionString;
}
public ProductsDB(string conString)
{
connectionString = WebConfigurationManager.ConnectionStrings
[conString].ConnectionString;
}
}

Определим метод, предназначенный для добавления новой
записи в таблицу "Товары". Для этого создадим объект соединения с
базой данных. В данном примере используется СУБД SQL Server Express
2005, но аналогичный код (с небольшими изменениями) можно применять
для подключения практически к любой СУБД. Создадим запрос с
параметрами, заполним эти параметры значениями, которые были
извлечены из полей объекта Product,
переданного в данный метод в качестве параметра, и исполним запрос.
Текст метода AddProduct, выполняющего
вышеописанные действия, представлен ниже.

public int AddProduct(Product prod)
{
SqlConnection con = new SqlConnection(connectionString);
string query = "INSERT INTO Товары (КодТовара,Наименование
Товара,Цена) VALUES (@id,@name,@cost)";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add(new SqlParameter("@id", SqlDbType.Int, 4));
cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.
VarChar, 50));
cmd.Parameters.Add(new SqlParameter("@cost", SqlDbType.Float, 8));
cmd.Parameters["@id"].Value = prod.ProductID;
cmd.Parameters["@name"].Value = prod.ProductName;
cmd.Parameters["@cost"].Value = prod.ProductCost;
try
{
con.Open();
return cmd.ExecuteNonQuery();
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка добавления нового
товара в таблицу Товары");
}
finally
{
con.Close();
}
}

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

Аналогичным образом создаются остальные методы работы с
базой данных. Создадим метод для удаления товара из таблицы
"Товары". Код метода DeleteProduct,
осуществляющего эту операцию, приведен ниже.

public void DeleteProduct(int productID)
{
SqlConnection con = new SqlConnection(connectionString);
string query = "DELETE FROM Товары WHERE КодТовара=@ID";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@ID", SqlDbType.Int, 4);
cmd.Parameters["@ID"].Value = productID;
try
{
con.Open();
cmd.ExecuteNonQuery();
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка удаления товара
из таблицы Товары");
}
finally
{
con.Close();
}
}

Отличие данного метода от предыдущего заключается в
том, что выполняется запрос на удаление записи, а в качестве
параметра передается номер (код) товара, который надо удалить.

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

public void UpdateProduct(Product prod)
{
SqlConnection con = new SqlConnection(connectionString);
string query = "UPDATE Товары SET НаименованиеТовара=
@pName, Цена=@pCost WHERE КодТовара=@ID";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@ID",SqlDbType.Int,4);
cmd.Parameters.Add("@pName", SqlDbType.VarChar, 50);
cmd.Parameters.Add("@pCost", SqlDbType.Float, 8);
cmd.Parameters["@ID"].Value = prod.ProductID;
cmd.Parameters["@pName"].Value = prod.ProductName;
cmd.Parameters["@pCost"].Value = prod.ProductCost;
try
{
con.Open();
cmd.ExecuteNonQuery();
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка обновления
информации о товаре в таблице Товары");
}
finally
{
con.Close();
}
}

При создании методов, извлекающих данные из БД,
необходимо учитывать, как и для чего будут применяться данные
методы. Дело в том, что может существовать множество различных
потребностей в извлекаемых из БД данных. Отличия в них сводятся к
представлению извлеченных данных в
различных форматах. Например, если необходимо организовывать вывод
информации с применением таких элементов, как GridView, удобнее всего сделать так, чтобы
метод вернул такую структуру, которую можно использовать для
привязки данных к этому элементу. К таким структурам, как уже было
сказано выше, относятся DataReader, DataTable,
DataSet и т. д. Если нужно извлекать данные об одном товаре,
необходимо, чтобы метод возвращал объект Product, если же желательно, чтобы метод
извлекал список существующих товаров, представленных в БД и
необходимых для их обработки, удобнее, чтобы метод вернул массив
объектов Product. В реальных приложениях
может потребоваться несколько методов, предназначенных для
реализации различных режимов работы с БД. Например, для отображения
вызывается один метод, извлекающий данные из БД, а для их
редактирования - другой.

Создадим метод GetProductsTable(), предназначенный для
извлечения данных о товарах из таблицы "Товары" и помещения их в
объект DataTable. Исходный код этого
метода приведен ниже.

public DataTable GetProductsTable()
{
SqlConnection con = new SqlConnection(connectionString);
string query = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
SqlDataAdapter da = new SqlDataAdapter(query, con);
DataTable dt = new DataTable("Product");
try
{
con.Open();
da.Fill(dt);
return dt;
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка чтения списка
товаров из таблицы Товары");
}
finally
{
con.Close();
}
}

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

В качестве примера создадим также метод для извлечения
данных из таблицы "Товары" и предоставления вызывающей программе
объекта DataReader, с помощью которого
осуществляется чтение данных из БД. Исходный код метода GetProductsReader(), реализующего данную
операцию, представлен ниже.

public SqlDataReader GetProductsReader()
{
SqlConnection con = new SqlConnection(connectionString);
string query = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
try
{
con.Open();
return cmd.ExecuteReader();
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка чтения списка
товаров из таблицы Товары");
}
}

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

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

public Product GetProduct(int productID)
{
SqlConnection con = new SqlConnection(connectionString);
string query = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары WHERE КодТовара=@ID";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@ID", SqlDbType.Int, 4);
cmd.Parameters["@ID"].Value = productID;
try
{
con.Open();
SqlDataReader rdr = cmd.ExecuteReader
(CommandBehavior.SingleRow);
rdr.Read();
int pID=(int)rdr["КодТовара"];
string pName = rdr["НаименованиеТовара"].ToString();
double pCost = Convert. ToDouble(rdr["Цена"]);
Product prod = new Product(pID,pName,pCost);
rdr.Close();
return prod;
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка извлечения товара
из таблицы Товары");
}
finally
{
con.Close();
}
}

public List<Product> GetProducts()
{
SqlConnection con = new SqlConnection(connectionString);
string query = "SELECT КодТовара,НаименованиеТовара,Цена
FROM Товары";
SqlCommand cmd = new SqlCommand(query, con);
cmd.CommandType = CommandType.Text;
List<Product> products = new List<Product>();
try
{
con.Open();
SqlDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
Product prod =
new Product((int) rdr["КодТовара"], rdr["Наименование
Товара"].ToString(), (double) rdr["Цена"]);
products.Add(prod);
}
rdr.Close();
return products;
}
catch (SqlException e)
{
throw new ApplicationException("Ошибка извлечения списка
товаров из таблицы Товары");
}
finally
{
con.Close();
}
}

Для демонстрации использования созданного слоя доступа
к данным поместим на форму объект ProductsView класса GridView, а также создадим следующий код,
использующий возможности класса ProductsDB.

protected void Page_Load(object sender, EventArgs e)
{
//Создание нового объекта доступа к данным
ProductsDB prodDB = new ProductsDB("TEST_DB");
if (!Page.IsPostBack)
{
//Создание нового объекта Product
Product prod = new Product(31, "Товар31", 99);
//Добавление созданного объекта Product в таблицу БД
prodDB.AddProduct(prod);
//Получение из БД информации о товаре с кодом 5
Product product = prodDB.GetProduct(5);
//Изменение наименования полученного товара
product.ProductName = "Товар1000";
//Изменение цены полученного товара
product.ProductCost = 10.5;
//Обновление информации о товаре в БД
prodDB.UpdateProduct(product);
//Получение массива объектов Product. Количество объектов
//равно количеству записей в таблице Товары
List<Product> products = prodDB.GetProducts();
}
//Установить источник данных и осуществить их привязку
//для элемента GridView
ProductsView.DataSource = prodDB.GetProductsTable();
Page.DataBind();
}

Для демонстрации возможности удаления данных из таблицы
"Товары" поместим на форму кнопку и создадим следующий обработчик
события нажатия на нее:

protected void Button1_Click(object sender, EventArgs e)
{
ProductsDB prodDB = new ProductsDB("TEST_DB");
prodDB.DeleteProduct(31);
ProductsView.DataSource = prodDB.GetProductsReader();
Page.DataBind();
}

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

Результат работы программы представлен на рис.
10.30.

Рис. 10.30.  Результат
работы программы трехуровневого взаимодействия с БД

Использование объекта ObjectDataSource

Создание пользовательского кода, реализующего
возможность взаимодействия с базой данных, — достаточно трудоемкий
процесс. К тому же такой способ позволяет связывать визуальные
элементы с данными только в программном коде. Для получения
возможности создания такой связи в режиме редактирования страницы
можно использовать объект ObjectDataSource. Этот объект позволяет
создавать связь между элементами управления, расположенными на
Web-странице, и компонентами доступа к данным, реализованным в виде
пользовательских классов. Но для этого необходимо, чтобы
пользовательский класс доступа к данным подчинялся следующим
правилам:

Он не должен сохранять состояние.

Он должен иметь конструктор по умолчанию, без аргументов.

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

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

Он должен предоставлять результаты запроса при вызове
единственного метода.

Результатом запроса должна быть одни или несколько записей,
которые могут быть представлены в виде коллекции, массива либо
спискового объекта. Главное, чтобы он реализовывал интерфейс IEnumerable.

Использование ObjetDataSource в ряде случаев бывает гораздо
удобнее применения таких элементов доступа к данным, как SqlDataSource или AccessDataSource. Скажем, в предыдущем примере
для доступа к таблице "Товары" можно воспользоваться
пользовательским классом и объектом ObjectDataSource. Для этого необходимо
выполнить нижеследующие шаги.

Поместить на форму объект ObjectDataSource, перетащив его с панели Toolbox, после чего прикрепить к нему класс,
отвечающий за извлечение данных, - в нашем случае это ProductsDB. Для этого достаточно установить
значение свойства TypeName равным ProductsDB. После этого необходимо определить
свойства SelectMethod, DeleteMethod,
UpdateMethod и InsertMethod,
используемые для выполнения соответствующих операций над данными в
БД. В качестве значений этих свойств нужно установить имя метода,
выполняющего соответствующие операции. Так, в качестве значения
свойства SelectMethod в данном примере
установим имя метода, извлекающего данные из БД, - GetProducts. Этот метод удовлетворяет всем
критериям ObjectDataSource - он
возвращает объект, представляющий все данные через общедоступные
свойства. Имена этих свойств и будут использованы в качестве имен
столбцов при выводе информации на экран в табличной форме. После
того как связь ObjectDataSource с
классом, извлекающим данные из базы данных, установлена, необходимо
добавить на Web-форму элемент GridView и
связать их, установив в свойстве DataSourcelD этого элемента имя объекта ObjectDataSource. Определение ObjectDataSource и GridView тогда будет выглядеть следующим
образом:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
SelectMethod="GetProducts"
TypeName="ProductsDB"></asp:ObjectDataSource>
<br />
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False" DataSourceID="ObjectDataSource1">
<Columns>
<asp:BoundField DataField="ProductID"
HeaderText="ProductID" SortExpression="ProductID" />
<asp:BoundField DataField="ProductName"
HeaderText="ProductName" SortExpression="ProductName" />
<asp:BoundField DataField="ProductCost"
HeaderText="ProductCost" SortExpression="ProductCost" />
</Columns>
</asp:GridView>

Результат работы программы показан на рис.
10.31.

Рис. 10.31.  Окно
результата использования объекта ObjectDataSource для организации
доступа к БД

Как видно из этого примера, с точки зрения пользователя
использование ObjectDataSource и
принципов трехуровневой архитектуры построения приложений доступа к
данным аналогично использованию объекта SqlDataSource. Однако это сходство скрывает
целый ряд деталей, сильно отличающих принципы построения
трехуровневых приложений от обычной архитектуры. Наиболее значимый
эффект от использования этих принципов заключается в том, что при
трехуровневой архитектуре организации доступа к данным Web-страница
не содержит никакого кода SQL. Вместо этого вся работа выполняется
классом ProductsDB. За счет этого
приложение оказывается более гибким и легко модифицируемым при
необходимости изменения механизмов доступа к данным. Более подробную
информацию об использовании принципов построения трехуровневой
архитектуры доступа к данным в приложении можно найти в справочной
системе MSDN, а также в [1].

Краткие итоги

Деятельность большинства Web-приложений основывается на
работе с данными - извлечении, отображении и модификации. Основным
источником данных в большинстве современных приложений служит база
данных. Поэтому вопросы организации взаимодействия приложения с
базой данных является весьма актуальными.

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

Программная модель ADO.NET
полностью соответствует объектно-ориентированным принципам. Она
содержит объекты, необходимые для подключения к источнику данных,
исполнения команд SQL, предназначенных для извлечения данных из
источника, а также добавления их и внесения изменений, организации
транзакций, создания виртуальных таблиц и заполнения их данными,
извлеченными из источника. Очень важным элементом ADO.NET являются поставщики данных. Поставщик данных представляет собой набор
классов, предназначенных для взаимодействия с хранилищем данных
определенного типа. Использование поставщиков делает ADO.NET чрезвычайно гибкой и расширяемой
моделью доступа к данным: для организации доступа к новому источнику
данных достаточно создать соответствующего поставщика. При
использовании источника данных ADO.NET
сама задействует соответствующего поставщика данных для организации
доступа к данным, хранящимся в нем. При использовании приложения,
использующего доступ к данным, необходимо сперва попытаться найти
"родного" поставщика данных для .NET. Если такового нет, можно
воспользоваться другими технологиями, например OleDb,
предоставляющей широкий выбор драйверов для различных источников
данных.

Все поставщики данных .NET реализуют одинаковые базовые
функции, поэтому программный код, создаваемый для доступа к данным,
выглядит приблизительно одинаково независимо от поставщика.

При организации взаимодействия Web-приложения с базой
данных нужно использовать определенный сценарий, который
предусматривает установку соединения с базой данных, выполнение
SQL-запросов, отключение от источника
данных, перенос изменений из отсоединенного набора данных в БД при
необходимости. Соединение с базой данных
осуществляется с использованием строки подключения, содержащей все
необходимые параметры соединения. Строку подключения можно хранить
как в тексте программы, так и во внешнем файле, что является
предпочтительным. В качестве такого файла очень часто используется
web.config.

Весь программный код взаимодействия с базой данных,
потенциально способный вызывать исключения, необходимо помещать
внутри блока try catch.

Для обслуживания соединения с базой данных расходуются
ресурсы компьютера, на котором выполняется Web-приложение. В связи с
этим рекомендуется открывать соединение как можно позже, а закрывать
как можно раньше, сразу после того, как все необходимые действия с
объектами базы данных были выполнены. Программный код взаимодействия
с базой данных необходимо строить таким образом, чтобы открытое
соединение закрывалось при любом исходе соединения с ней.

ADO.NET поддерживает
выполнение двух типов команд, написанных на языке SQL: прямой
оператор языка и хранимая процедура. Для
выполнения запросов, осуществляющих возврат данных в основную
программу для их последующей обработки, а также запросов,
предназначенных для вставки, изменения, удаления записей,
применяются разные методы объекта Command.

Обращение к полям таблицы может происходить как по
имени, так и с помощью индекса.

В ADO.NET существует
несколько мощных классов, предназначенных для вывода информации на
экран. Одним из самых сложных классов, который предоставляет большое
количество возможностей по отображению данных, извлекаемых из БД,
является класс GridView.

При взаимодействии Web-приложения с базой данных в
реальных условиях очень часто возникает необходимость динамического
формирования SQL-запроса с последующим
его исполнением. Это может быть достигнуто несколькими способами.
Наиболее предпочтительными из них являются использование
параметризованных команд и хранимых процедур.

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

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

Для автоматизации отображения данных в элементе
управления без использования программирования необходимо
использовать привязку и отображение данных. Большинство элементов
управления ASP.NET поддерживают привязку данных. Различают привязку
данных с одним и с множественным значением. Помимо применения
программного кода для осуществления привязки данных Visual Studio
2005 позволяет осуществлять привязку с использованием визуальных
средств.

Использование баз данных очень часто требует фильтрации
и сортировки данных. Для этих целей в ASP.NET предназначен объект
DataView. Этот класс снабжен
возможностью фильтрации и сортировки отображаемых данных, причем эти
режимы никак не затрагивают реальные данные.

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

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

© gsu.by