Цель лекции: познакомиться с принципами организации подключения Web-приложения к базе данных, рассмотреть основные возможности модели доступа к данным ADO.NET, изучить примеры выполнения наиболее типовых действий с данными в базе данных, таких как добавление, удаление, редактирование, а также поиск, сортировка и фильтрация.
Деятельность большинства Web-приложений сосредоточена на извлечении, отображении и модификации данных. Эти задачи кажутся достаточно прямолинейными, но за последнее десятилетие способы использования данных постоянно менялись. Разработчики перешли от простых клиентских приложений с локальными базами данных к распределительным системам, основанным на централизованных базах данных и выделенных серверах. В то же время развивались технологии доступа к данным. Сегодня свыше 95% всех информационных систем так или иначе используют базы данных. Вот почему вопросы организации взаимодействия приложения с базой данных являются актуальными, а внимание ведущих разработчиков программного обеспечения приковано к этой проблеме. Данная лекция посвящена изучению вопросов, связанных с организацией взаимодействия Web-приложений с базами данных.
На сегодняшний день
Рассмотрим архитектуру
Так как
На рис 10.1 показаны классы, составляющие объектную модель
(рис 10.1) Иерархия объектов ADO.NETОбъекты, расположенные в левой части, называются подсоединенными и необходимы для управления соединением, транзакциями, выборкой данных и передачей изменений данных в БД. Они непосредственно взаимодействуют с базой данных. Объекты, расположенные в правой части, называются отсоединенными. Они позволяют работать с данными автономно. DataAdapter объекта DataSet для заполнения последнего набором данных, извлеченных непосредственно из БД.
Одной из основных идей, лежащих в основе
(рис 10.2) Уровни моделей поставщиков ADO.NETКак видно из рис 10.2, приложение .NET взаимодействует с базой данных посредством поставщиков данных. Каждый
При построении приложения, использующего доступ к данным, необходимо сперва попытаться найти "родного" поставщика данных для .NET. Когда же такового не существует, можно воспользоваться OleDb, если существует соответствующий драйвер для источника данных, к которому устанавливается подключение. Т. к. технология OleDb существует достаточно давно, создано много драйверов для различных источников данных, поддерживающих ее. Если в системе не установлен соответствующий драйвер OleDb, его можно попытаться найти на сайте разработчика той базы данных, к которой осуществляется доступ, либо на сайте Microsoft. То же самое справедливо и для
Каждый поставщик .NET реализует одинаковые базовые классы - Connection, Transaction, DataAdapter, Command, Parameter, DataReader имена которых зависят от поставщика. Например, у поставщика SQL Server существует объект SqlDataAdapter, у поставщика OleDb — OleDbDataAdapter и т. д.
У каждого поставщика данных существует собственное пространство имен. Хотя все поставщики относятся к пространству имен System.Data, каждый из них содержит свой подраздел этого пространства, который содержит объекты, специфичные для данного поставщика. Например, объект SqlDataAdapter находится в пространстве имен System.Data.SqlClient.
Все поставщики данных .NET реализуют одинаковые базовые функции, поэтому код, создаваемый для доступа к данным, выглядит приблизительно одинаково независимо от поставщика. Это означает, что, рассматривая использование интерфейсов отдельного поставщика, мы фактически рассматриваем возможность использования этих же интерфейсов применительно и к другим поставщикам данных.
При организации доступа к данным с помощью
Объект Connection представляет соединение с источником данных. С его помощью можно задать тип источника данных, его местонахождение, параметры доступа и ряд других атрибутов. Перед тем как начать взаимодействие с источником данных, необходимо установить подключение к нему с помощью объекта Connection.
Объект Command представляет запрос к источнику данных, вызов хранимой процедуры или прямой запрос на возврат содержимого конкретной таблицы. Как известно, существует несколько типов запросов. Часть из них возвращают данные, извлекаемые из источника данных, другие изменяют записи, третьи - управляют структурой БД. С помощью объекта Command возможно выполнить любой из перечисленных типов запросов. Различия в поведении объекта Command начинают проявляться тогда, когда необходимо исполнить тот или иной запрос. Так, например, при необходимости исполнения запроса, не возвращающего записи, необходимо использовать метод ExecuteNonQuery объекта Command, а при извлечении данных из БД - метод ExecuteReader, который, в свою очередь, возвращает объект DataReader, позволяющий просматривать полученные в результате запроса записи.
Объект DataReader предназначен для максимально быстрой выборки и просмотра возвращаемых запросом записей. Однако он позволяет просматривать весь результирующий набор записей путем перемещения от одной записи к другой и, таким образом, просматривать только одну запись за раз. Кроме того, DataReader не имеет возможности для обновления значений записей и поэтому может работать в режиме "только для чтения", за счет чего обладает высокой производительностью.
Объект Transaction позволяет осуществлять группировку записей в логическую единицу работы, называемой транзакцией. Транзакция логически объединяет несколько различных действий, связанных с манипулированием данными, в единое целое. В процессе выполнения действий, осуществляемых в рамках транзакции, СУБД обычно кэширует изменения, вносимые этими действиями в данные до момента завершения транзакции. Это позволяет производить отмену любых изменений, выполненных в рамках транзакции, в случае, если хотя бы одно из действий транзакции завершилось неудачно.
Объект Parameter позволяет вводить в запрос элемент, значение которого может быть задано непосредственно перед исполнением запроса. За счет этого отпадает необходимость каждый раз изменять текст самого запроса.
Объект DataAdapter представляет собой связующее звено между 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. Такая возможность исключает необходимость хранения одного набора данных в двух разных структурах.
Более подробно использование всех объектов модели
Для того, чтобы приложение .NET могло осуществлять взаимодействие с источником данных, необходимо установить соединение с ним. Наиболее типичным сценарием работы Web-приложения является следующий:
Рассмотрим эти этапы более подробно.
Как уже говорилось выше, для подключения к источнику данных с использованием
Итак, для того, чтобы подключиться к базе данных во время выполнения программы, необходимо создать объект Connection, а также задать его свойства, определяющие текущие параметры подключения. Основным параметром, устанавливающим необходимые опции для подключения к БД, является
В качестве примера рассмотрим создание небольшого Web-приложения для работы с базой данных, ER-модель которой представлена на рис 10.3.
(рис 10.3) ER-модель фрагмента базы данныхСоздадим базы данных в соответствии с моделью, изображенной на рис 10.3, в формате SQL Server Express и Access 2003. Для этого исполним следующие
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, выполнится при любом исходе попытки подключения, что гарантирует освобождение ресурсов сервера, не дожидаясь момента, когда память будет освобождена сборщиком мусора.
Установление соединения является необходимым условием подключения к базе данных. Однако подключение необходимо для того, чтобы выполнять определенные команды над элементами базы. Рассмотрим возможности выполнения команд над источниками данных.
Одним из основных элементов из набора классов Command. Для того, чтобы использовать класс Command, необходимо установить тип команды, установить текст запроса SQL и привязать ее к соединению с БД.
Существует 3 типа команд класса Command:
CommandType.Text |
Выполнение прямого оператора SQL, текст которого устанавливается в свойстве CommandText. Это значение по умолчанию. |
CommandType.StoredProcedure |
CommandText. |
CommandType.TableDirect |
Выполнение опроса всей таблицы базы данных, имя которой задается в свойстве CommandText. Этот тип команды используется для обратной совместимости с некоторыми драйверами OLE DB и не поддерживается поставщиком данных SQL Server. |
Пример создания объекта Command для выполнения
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 необходимо закрыть модуль чтения.
В данном примере используется класс .
(рис 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);
Как видно, в данном случае, строка tb_Delete ввести следующий текст "Какой-то товар' OR '1'='1", то в результате будут удалены все записи из таблицы "Товары", т. к. текст tb_Delete, будет
"DELETE FROM Товары WHERE НаименованиеТовара= 'Какой-то товар' OR '1'='1'"
Как видно из этого примера, наименование введенного товара становится неважным, т. к. условие всегда будет истинным за счет добавления оператора OR с заведомо верным условием. Можно привести примеры и более сложных атак внедрением SQL, приводящих к еще более серьезным и масштабным последствиям и создающих реальную угрозу функционирования всего Web-приложения.
Решений данной проблемы может быть несколько. Во-первых, можно постараться проанализировать текст, введенный пользователем, до того как он будет подставлен в текст Exception.
Можно придумать и массу других алгоритмов анализа вводимых данных, но все равно они не могут гарантировать стопроцентной безопасности вашего приложения.
Лучшей практикой защиты от атаки внедрением SQL является использование
Command через коллекцию Parameters. Например, команда удаления записи из таблицы "Товары", использованная выше, будет выглядеть следующим образом при использовании параметра:
DELETE FROM Товары WHERE НаименованиеТовара=@ProductName
Здесь @ProductName представляет собой параметр, значение которого должно быть установлено до того, как будет запущено выполнение запроса. Синтаксис
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. Так, предыдущий пример использования такой атаки в данном случае не работает.
При использовании сложной СУБД, реализующей полный набор функций клиент-серверной СУБД, разработчик приложения, взаимодействующего с базой данных, получает в свое распоряжение несколько дополнительных очень мощных инструментов, позволяющих облегчить, ускорить и обезопасить процесс взаимодействия с базой данных по сравнению с локальными базами данных. Одними из основных таких возможностей являются способы организации и использования хранимых процедур и триггеров. Вопросы создания и программирования хранимых процедур и триггеров, а также всевозможные вопросы, связанные с особенностями их реализации в конкретных СУБД, выходят за рамки данного курса. Желающим более подробно ознакомиться с особенностями реализации такого рода элементов рекомендуется ознакомиться с , .
Здесь же будут в основном рассмотрены вопросы использования хранимых процедур при работе с SQL Server без объяснения того, как именно создавалась та или иная
Хранимые процедуры обладают рядом преимуществ, главными из которых являются такие, как повышение быстродействия приложения при использовании хранимых процедур для обработки данных, повышение безопасности приложения при работе с данными.
В реальных приложениях, если используется СУБД, поддерживающая возможности использования хранимых процедур, рекомендуется воспользоваться этим и создавать приложение с их применением. Кроме того, рекомендуется все наиболее типичные и часто возникающие действия, которые связаны с обработкой данных, содержащихся в базе данных, а также операции вставки, удаления и изменения производить с помощью вызова предварительно созданных хранимых процедур.
Рассмотрим пример создания и использования простой хранимой процедуры, позволяющей добавить новый товар в таблицу "Товары" базы данных. Текст хранимой процедуры, реализованной в СУБД 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().
Из данного примера видно, что при добавлении нового параметра, выступающего в роли параметра хранимой процедуры, необходимо обязательно указывать его тип, а также длину (для строковых типов это количество символов, для числовых - количество байт).
Рассмотренная ранее логика взаимодействия клиентского приложения с базой данных, основанная на соединении, оправдана для реализации односторонней связи с базой данных. Это может быть либо получение данных, либо выполнение запросов, связанных с внесением изменений в нее. Реализация же сложных операций взаимодействия с БД при этом является очень трудоемким процессом и требует написания большого количества программного кода, его отладки и т. д.
Основной идеей использования
Объект DataSet представляет собой контейнер, содержащий объекты DataTable и Relation. DataTable представляет собой таблицу, состоящую из строк и столбцов. Строки таблицы представлены объектом DataRow, который, в свою очередь, представляет собой коллекцию столбцов таблицы (объект DataColumn ). Данные в DataSet отсоединены от БД. Все изменения данных кэшируются в объектах DataRow. При возникновении необходимости передачи изменений в данных объекта DataSet существует возможность передачи только изменившейся части данных, что позволяет значительно экономить ресурсы канала связи, т. к. в этом случае передается гораздо меньший объем данных.
Обобщая все вышесказанное, можно сделать вывод о том, что использование объекта DataSet в ряде случаев оказывается более эффективным, чем DataReader. Наиболее типичными ситуациями, в которых рекомендуется применять объект DataSet, являются следующие:
DataSet позволяет легко сохранять данные в файле XML. При этом возможны варианты сохранения либо только данных, либо только структуры данных, либо и того и другого.DataReader обеспечивает перемещение по набору только вперед. С помощью же DataSet возможна организация постраничного просмотра данных.DataSet, в отличие от DataReader, содержит средства организации сортировки и фильтрации данных.Более подробная информация относительно сценариев использования .
Выше уже говорилось о том, что DataSet состоит из таблиц, которые, в свою очередь, состоят из строк, а они, в свою очередь, - из столбцов. Каждый из перечисленных элементов реализован в виде класса. Для управления автономными изменениями DataSet отслеживает информацию о версии каждого объекта DataRow. Это означает, что когда происходит редактирование строки, ее исходное значение сохраняется в памяти, а строка помечается как измененная. Аналогичные действия происходят и при добавлении и изменении строк DataTable. В дальнейшем в базу данных можно перенести значения только тех строк, которые были затронуты изменениями. Таким образом, DataSet никогда не поддерживает постоянного соединения с базой данных.
Для извлечения данных из базы данных и наполнения ими объекта DataSet необходимо использовать еще один объект DataAdapter, который помимо прочего позволяет обновлять данные БД на основе внесенных в DataSet изменений.
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 будет использовать уже существующее соединение.
После того как необходимый 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 %>.
Такого же эффекта можно добиться, если использовать построитель выражений. Для этого необходимо в режиме редактирования дизайна открыть свойства объекта (рис 10.12). В открывшемся окне, изображенном на рис 10.13, необходимо выбрать категорию AppSettings и в свойстве AppSetting установить значение MySetting. Теперь значение определенное в свойстве MySetting файла web.config, будет отображаться на странице.

(рис 10.13) Вызов построителя выражений(рис 10.12) Окно построителя выраженийКонечно, отображение значения свойства, определенного в файле web.config, имеет незначительное практическое значение. Однако извлечение таким образом строки подключения к базе данных, определенной в web.config, и запись ее значения в свойство ConnectionString объекта , обеспечивающего будет рассмотрено ниже.
Для отображения набора значений необходимо использовать привязку с множественным значением. Привязываемый набор значений должен содержаться в объекте, способном хранить коллекции значений (например, объекты ArrayList, Hashtable и др.).
Каждый элемент, поддерживающий возможность множественной привязки, содержит следующий набор свойств:
DataSource |
Объект, содержащий привязываемые данные. |
DataSourceID |
Свойство, которое содержит идентификатор объекта, использующегося для подключения к источнику данных. В отличие от 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, .
GridView представляет собой таблицу, способную привязываться к данным и отображать содержимое таблиц. Поддерживает выбор, редактирование, сортировку и перемещение по страницам данных. предназначен для показа одной записи из таблицы за раз. Поддерживает редактирование и перемещение по страницам. практически аналогичен , с той лишь разницей, что основан на шаблонах, и это делает его более гибким при отображении информации.
Самым мощным элементом по количеству возможностей является 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. Среди них такие как:
- позволяет подключаться к любому источнику данных, который имеет поставщика данных
- позволяет подключаться к пользовательскому классу доступа к данным. Этот подход целесообразно применять при построении крупномасштабных и сложных приложений. Подробнее об использовании пользовательских классов доступа к данным и элементе
AccessDataSource - позволяет подключаться к базам данных Microsoft Access.
XmlDataSource - позволяет подключаться к XML-файлу.
- позволяет подключаться к файлу Web.Sitemap, содержащему описание навигационной структуры Web-приложения.
Для использования элемента управления источниками данных достаточно перетащить его с панели Toolbox в область Web-страницы. При этом он отображается в виде серого прямоугольника, содержащего внутри наименование элемента управления источником данных.
(рис 10.16) Использование элемента управления источником данныхДля конфигурирования элемента можно воспользоваться мастером, запускаемым при нажатии на ссылку Configure Data Source.
На первом шаге конфигурирования элемента необходимо определить
(рис 10.17) Первый шаг настройки элемента SqlDataSource на подключение к базе данныхНа втором шаге мастер предлагает сохранить сгенерированную строку подключения к БД в файле web.config. При необходимости можно отказаться, а также изменить наименование переменной, содержащей строку подключения.
Третий шаг мастера позволяет определить запросы к БД, используемые для выполнения операций над данными. В частности, предлагается выбрать таблицу и сгенерировать запрос на выборку данных из нее. Здесь же можно определить свои собственные запросы на выборку, добавление, удаление и обновление данных или указать используемую хранимую процедуру.
Мастер может также сгенерировать стандартные запросы, используемые для добавления, удаления и обновления значений в связанном источнике данных. Для построения этих запросов можно воспользоваться кнопкой Advanced. При этом будет открыто окно, в котором можно установить два свойства. Свойство Generate INSERT, UPDATE, and DELETE statements задает режим создания соответствующих запросов. Свойство Use optimistic concurrency задает режим определения тех строк, которые были изменены с момента последней загрузки данных в объект DataSet.

(рис 10.19) Второй шаг мастера настройки SqlDataSource на подключение к БД(рис 10.18) Третий шаг мастера настройки SqlDataSource на подключение к БД
(рис 10.20) Окно генерации запросов на вставку, обновление и удаление данных из БДЧетвертый шаг мастера позволяет протестировать созданный запрос и, после нажатия на кнопку Finish, завершить настройку элемента управления источником данных.
В результате настройки параметров объекта ).
(рис 10.21) Окно свойств элемента SqlDataSource, установленных с помощью мастераDataSourceMode — задает режим, используемый для извлечения строк. Возможные значения — DataSet и DataReader.
ConnectionString — задает строку подключения к источнику данных.
DeleteCommandType — задает тип запроса Delete. Возможные значения - Text и StoredProcedure.
DeleteQuery — задает запрос, используемый для удаления данных из связанного источника.
Последние два параметра применяются также и для других запросов - на изменение, добавление и выборку данных.
При необходимости можно изменить любой запрос. Для этого нужно щелкнуть на кнопку
, расположенную в строке редактируемого запроса. В результате будет открыто окно редактирования запроса. Пример такого окна для редактирования запроса на изменение данных изображен на рис 10.22.
(рис 10.22) Пример окна редактирования запроса на изменение данныхПосле того как настройки элемента управления источником данных завершены, необходимо связать с ним элемент отображения данных на экране. Для этого нужно указать имя объекта показана возможность привязки GridView к и установка несколько специфических режимов отображения данных (удаление, редактирование, выбор строк, сортировка данных в столбцах).
(рис 10.23) Привязка GridView к элементу управления источником данных и задание специфических режимов их отображенияСледует иметь в виду, что каждый элемент управления источником данных может использоваться для связи только с одной таблицей базы данных. Если в приложении требуется независимо извлекать или обновлять информацию из нескольких таблиц, необходимо применять несколько элементов управления источниками данных.
При использовании данного подхода к привязке данных вызов метода DataBind() в коде приложения становится необязательным.
В большинстве реальных приложений работы с данными невозможно применять жестко закодированные запросы, т. к. в зависимости от используемого режима они должны содержать различные данные. Для решения этой задачи задействуются параметры, применение которых уже рассматривалось в разделе "Использование
Поместим на форму элементы DropDownList и . Настроим данные элементы на подключение к источнику данных, содержащему список товаров. Определения этих элементов выглядят следующим образом:
<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 и еще один - . Настроим в соответствии со следующим определением:
<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>
Ключевыми элементами настройки .

(рис 10.25) Результат работы приложения, содержащего главную и подчиненную таблицы(рис 10.24) Диалоговое окно редактирования параметров запросаВ данном примере значение параметра ) нужно щелкнуть по кнопке
окна.
Допустимые типы источников данных параметров можно выбирать из списка. При этом допустимы следующие значения списка:
Control |
Свойство элемента управления. |
Cookie |
Значение Cookie набора. |
Form |
Переменная формы. С помощью данного типа источника данных параметра можно извлекать значения, отправленные странице элементом ввода. |
Profile |
Значение текущего профиля пользователя. |
QueryString |
Строковое значение запроса. Позволяет извлекать значение из текущей строки запроса. |
Session |
Значение состояния сеанса. |
предоставляет возможность изменения данных, содержащихся в источниках данных. Для того, чтобы воспользоваться этой возможностью, необходимо определить соответствующие запросы, выполняющие операции вставки, обновления и удаления записей. Все они создаются по аналогии с запросом Select на основе параметризованного запроса. Выше уже упоминалось об имеющихся в возможностях построения запросов данного типа. В качестве примера добавим возможность изменения данных в страницу просмотра товаров и заказов, созданную ранее. Для этого выполним нижеследующие операции.
Определим в запрос на обновление данных ( UpdateQuery ). Текст запроса будет выглядеть следующим образом:
UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество = @Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE (КодОперации = @КодОперации)
При составлении запроса важно давать имена параметрам таким образом, чтобы они совпадали с именами столбцов таблицы. Это объясняется тем, что ASP.NET автоматически отправляет значения параметрам, взятым из столбцов, у которых имена совпадают с именами параметров.
В данном же примере присутствует еще одна особенность, которую следует учитывать. Она состоит в том, что ранее для удобства отображения данных был использован запрос, в котором происходит внутреннее соединение данных из таблиц "Контрагенты" и "Закупки", - он необходим для подмены номеров контрагентов их наименованиями. Поэтому при отображении информации в GridView мы видим наименования контрагентов вместо их номеров. Однако, это создаст некоторые трудности при реализации обновления данных. Дело в том, что теперь мы не можем реализовать обновление данных контрагентов, так как они находятся в таблице "Контрагенты", а связан с таблицей "Закупки". Все, что мы можем сделать, - это изменить номер текущего контрагента в редактируемой операции закупки. При этом необходимо будет вводить не наименование контрагента, а его номер.
Именно этим объясняется наличие в запросе параметра КодКонтрагента = @Контрагент, обновляющего и нформацию о контрагенте текущей операции закупки.
Добавим в ).
(рис 10.26) Добавление столбца в GridView с помощью интеллектульного дескриптораВ открывшемся окне (рис 10.27) установим тип поля CommandField, а также убедимся, что установлен флажок Edit/Update. Если требуется добавить также и возможность удаления элемента, необходимо установить флажок Delete.
(рис 10.27) Окно добавления нового столбца к GridViewПосле этого в ).
(рис 10.28) Редактирование содержимого таблицы "Закупки" с помощью GridViewПри обновлении данных в базе могут возникать серьезные затруднения, вызванные возможностью одновременной работы большого количества пользователей с одними и теми же данными. Это особенно актуально для Web-приложений, количество пользователей которых может быть в несколько раз больше, чем для традиционных win32-приложений. Достаточно подробное описание возможных проблем и путей их решения можно найти в . Остановимся лишь на некоторых из них, наиболее актуальных и востребованных.
Если посмотреть на текст запроса на обновление данных таблицы "Закупки", использованный в предыдущем примере, можно заметить, что очень важным его элементом является строка WHERE (КодОперации = @КодОперации). Она позволяет обновить только ту строку, которая соответствует значению, определенному в параметре "КодОперации". Этот код хорошо работает, если в один и тот же момент времени одну запись пытается обновить только один человек. Если же представить, что в момент редактирования нами этой записи другой пользователь сможет изменить значение поля "КодОперации", то попытка обновления таблицы "Закупки", предпринимаемая нами, завершится неудачей, т. к. в таблице больше не существует строки с тем значением кода операции, которое было взято в качестве параметра в момент начала редактирования.
Поэтому, чтобы быть уверенным в том, что попытка обновления завершится успешно, необходимо использовать лишь те значения первичных ключей, которые доступны только для чтения. Кроме того, параметр, который используется для хранения исходного значения ключевого поля, необходимого для обновления записи, рекомендуется помечать префиксом. Задать префикс можно с помощью свойства OldValuesParameterFormatString объекта . Обычно в качестве префикса используется значение original_. Так, в приведенном выше примере строка WHERE с учетом префикса может быть записана следующим образом:
WHERE КодОперации=@original_КодОперации
Даже при использовании ключей, доступных только для чтения, в ситуации, описанной выше, когда два пользователя одновременно редактируют одну и ту же запись в базе данных, может возникнуть конфликт. Суть его состоит в том, что изменения того пользователя, который позже других выполнит команду переноса сделанных им изменений в базу данных, перезапишут изменения других пользователей, редактировавших эту запись вместе с ним, но выполнивших команду обновления чуточку раньше. Для разрешения этой ситуации можно реализовать строгую проверку параллелизма при выполнении обновления данных в БД. Одним из таких способов является выполнение обновления только в том случае, если значения каждого из полей обновляемой таблицы совпадают с оригинальными значениями, взятыми в момент начала редактирования. Запрос, который реализует такой подход для примера, приведенного выше, выглядит следующим образом:
UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество = @Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE (КодОперации = @original_КодОперации AND ДатаОперации= @original_ДатаОперации AND Количество=@originalКоличество AND Цена=@original_Цена AND КодКонтрагента=@original_Контрагент)
Проблема в данном случае состоит в том, что использование префикса возможно только для ключевых полей таблицы, определенных в свойстве DataKeyNames объекта GridView. Понятно, что невозможно определить все поля таблицы в качестве ключевых. Поэтому лучшим решением будет использование свойства ConflictDetection объекта равным значению CompareAllValues.
Рассмотренные способы привязки элементов отображения к источникам данных, а также возможности объекта целесообразно применять только в относительно небольших приложениях. При необходимости создания сложного Web-приложения использование может привести к возникновению многих проблем, вызванных недостатками данного элемента. К недостаткам можно отнести следующие:
SqlDataSource. Это, с одной стороны, крайне неэффективно при использовании одинаковых запросов на разных страницах, т. к. в этом случае необходимо дублировать одинаковые элементы на разных страницах. С другой стороны, поддерживать такой код крайне неудобно, т.к. может возникнуть ситуация, в которой изменения придется вносить в несколько элементов SqlDataSource . Для решения этих и многих других проблем, возникающих при использовании SqlDataSource , необходимо при разработке приложения использовать принципы построения При создании сложных приложений, ориентированных на активное взаимодействие с пользователями, большое значение имеет отображение данных и возможности настройки этого отображения. 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.
Логика взаимодействия сложных информационных систем с базами данных обычно бывает достаточно сложной. Сложность эта многократно увеличивается вместе с увеличением сложности информационной системы. Если не обеспечить унифицированные механизмы и интерфейсы доступа к данным, поддержание и развитие информационной системы становится очень трудоемким, а подчас и просто невозможным. Выходом из данной ситуации является создание промежуточного слоя, обеспечивающего взаимодействие приложения с базой данных на основе принципов
В сложных профессиональных приложениях код доступа к базе данных не встраивается в само приложение, а инкапсулируется в отдельный выделенный класс. Для выполнения операции с базой данных приложению нужно создать экземпляр этого класса и вызвать соответствующий метод. При создании такого класса будем следовать ряду рекомендаций.
Современные приложения создаются на основе объектно-ориентированного подхода. Основным строительным блоком приложения в данном подходе является класс. Все обрабатываемые приложением данные хранятся в объектах, которые также содержат и методы для их обработки. Тем не менее большинство используемых СУБД сегодня построены на принципах реляционной модели данных. Принципы обработки данных в объектных и реляционных моделях отличаются, это необходимо учитывать при организации взаимодействия с базой данных. Ниже рассматриваются некоторые вопросы организации такого взаимодействия.
В хорошо организованном приложении создается отдельный класс для каждой таблицы либо логически взаимосвязанной группы таблиц базы данных. Этот класс используется для организации взаимодействия приложения с базой данных, реализуя все необходимые операции манипуляции этими данными. Совокупность таких классов представляет собой как бы промежуточный программный слой, который находится между классами, содержащими данные и методы для их обработки, и данными, сохраненными в базе данных. Набор таких классов, реализующих логику взаимодействия приложения с базой данных, может быть реализован в виде отдельной сборки, которая может быть откомпилирована. Это целесообразно в том случае, когда аналогичную логику необходимо реализовать в нескольких приложениях.
Рассмотрим пример реализации взаимодействия Web-приложения с таблицей "Товары" базы данных "TestDb" с использованием принципов трехуровневой архитектуры.
Прежде всего, создадим класс Product, который содержит поля, соответствующие полям таблицы "Товары". Доступ к этим полям должен осуществляться посредством соответствующих свойств. В типичном объектно-ориентированном приложении такие классы выполняют роль хранилища данных, доступ к которым предоставляют методам, осуществляющим их обработку. В достаточно большом и сложном приложении классы желательно размещать отдельно от основного кода программы. Это позволяет легко отделять код основной программы от кода, который может быть перенесен в другие приложения и повторно использоваться для облегчения и ускорения создания приложения. Для создания класса рекомендуется выполнить команду Website $$\Rightarrow$$ 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) Результат работы программы трехуровневого взаимодействия с БДСоздание пользовательского кода, реализующего возможность взаимодействия с базой данных, — достаточно трудоемкий процесс. К тому же такой способ позволяет связывать визуальные элементы с данными только в программном коде. Для получения возможности создания такой связи в режиме редактирования страницы можно использовать объект . Этот объект позволяет создавать связь между элементами управления, расположенными на Web-странице, и компонентами доступа к данным, реализованным в виде пользовательских классов. Но для этого необходимо, чтобы пользовательский класс доступа к данным подчинялся следующим правилам:
IEnumerable.Использование ObjetDataSource в ряде случаев бывает гораздо удобнее применения таких элементов доступа к данным, как или AccessDataSource. Скажем, в предыдущем примере для доступа к таблице "Товары" можно воспользоваться пользовательским классом и объектом . Для этого необходимо выполнить нижеследующие шаги.
Поместить на форму объект , перетащив его с панели Toolbox, после чего прикрепить к нему класс, отвечающий за извлечение данных, - в нашем случае это ProductsDB. Для этого достаточно установить значение свойства TypeName равным ProductsDB. После этого необходимо определить свойства SelectMethod, DeleteMethod, UpdateMethod и InsertMethod, используемые для выполнения соответствующих операций над данными в БД. В качестве значений этих свойств нужно установить имя метода, выполняющего соответствующие операции. Так, в качестве значения свойства SelectMethod в данном примере установим имя метода, извлекающего данные из БД, - GetProducts. Этот метод удовлетворяет всем критериям - он возвращает объект, представляющий все данные через общедоступные свойства.
Имена этих свойств и будут использованы в качестве имен столбцов при выводе информации на экран в табличной форме. После того как связь с классом, извлекающим данные из базы данных, установлена, необходимо добавить на Web-форму элемент GridView и связать их, установив в свойстве DataSourceID этого элемента имя объекта . Определение и 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 для организации доступа к БДКак видно из этого примера, с точки зрения пользователя использование .
Деятельность большинства Web-приложений основывается на работе с данными - извлечении, отображении и модификации. Основным источником данных в большинстве современных приложений служит база данных. Поэтому вопросы организации взаимодействия приложения с базой данных является весьма актуальными.
Одной из наиболее современных технологий доступа к данным является
Программная модель
Все поставщики данных .NET реализуют одинаковые базовые функции, поэтому программный код, создаваемый для доступа к данным, выглядит приблизительно одинаково независимо от поставщика.
При организации взаимодействия Web-приложения с базой данных нужно использовать определенный сценарий, который предусматривает web.config.
Весь программный код взаимодействия с базой данных, потенциально способный вызывать исключения, необходимо помещать внутри блока try catch.
Для обслуживания соединения с базой данных расходуются ресурсы компьютера, на котором выполняется Web-приложение. В связи с этим рекомендуется открывать соединение как можно позже, а закрывать как можно раньше, сразу после того, как все необходимые действия с объектами базы данных были выполнены. Программный код взаимодействия с базой данных необходимо строить таким образом, чтобы открытое соединение закрывалось при любом исходе соединения с ней.
Command.
Обращение к полям таблицы может происходить как по имени, так и с помощью индекса.
В GridView.
При взаимодействии Web-приложения с базой данных в реальных условиях очень часто возникает необходимость динамического формирования
Традиционная логика взаимодействия клиентского приложения с базой данных основана на использовании соединений и оправдана при реализации односторонней связи с БД. Реализация сложных алгоритмов взаимодействия приложения с БД является достаточно трудоемким процессом и требует написания большого количества программного кода.
Важнейшим классом DataSet, используемый совместно с классом DataAdapter.
Для автоматизации отображения данных в элементе управления без использования программирования необходимо использовать привязку и отображение данных. Большинство элементов управления ASP.NET поддерживают привязку данных. Различают привязку данных с одним и с множественным значением. Помимо применения программного кода для осуществления привязки данных Visual Studio 2005 позволяет осуществлять привязку с использованием визуальных средств.
Использование баз данных очень часто требует фильтрации и сортировки данных. Для этих целей в ASP.NET предназначен объект DataView. Этот класс снабжен возможностью фильтрации и сортировки отображаемых данных, причем эти режимы никак не затрагивают реальные данные.
Логика взаимодействия сложных информационных систем с базами данных обычно бывает достаточно сложной. Сложность эта многократно увеличивается вместе с увеличением сложности информационной системы. Если не обеспечить унифицированные механизмы и интерфейсы доступа к данным, поддержание и развитие информационной системы становится очень трудоемким, а подчас и просто невозможным. Выходом из данной ситуации является создание промежуточного слоя, обеспечивающего взаимодействие приложения с базой данных на основе принципов
В сложных профессиональных приложениях код доступа к базе данных не встраивается в само приложение, а инкапсулируется в отдельный выделенный класс. Для выполнения операции с базой данных приложению необходимо создать экземпляр этого класса и вызвать соответствующий метод. Особое значение при организации взаимодействия объектного приложения с базой данных имеет организация взаимодействия объектов с реляционными таблицами БД.
Цель лекции: познакомиться с принципами организации подключения Web-приложения к базе данных, рассмотреть основные возможности модели доступа к данным ADO.NET, изучить примеры выполнения наиболее типовых действий с данными в базе данных, таких как добавление, удаление, редактирование, а также поиск, сортировка и фильтрация.
Деятельность большинства Web-приложений сосредоточена на извлечении, отображении и модификации данных. Эти задачи кажутся достаточно прямолинейными, но за последнее десятилетие способы использования данных постоянно менялись. Разработчики перешли от простых клиентских приложений с локальными базами данных к распределительным системам, основанным на централизованных базах данных и выделенных серверах. В то же время развивались технологии доступа к данным. Сегодня свыше 95% всех информационных систем так или иначе используют базы данных. Вот почему вопросы организации взаимодействия приложения с базой данных являются актуальными, а внимание ведущих разработчиков программного обеспечения приковано к этой проблеме. Данная лекция посвящена изучению вопросов, связанных с организацией взаимодействия Web-приложений с базами данных.
На сегодняшний день
Рассмотрим архитектуру
Так как
На рис 10.1 показаны классы, составляющие объектную модель
(рис 10.1) Иерархия объектов ADO.NETОбъекты, расположенные в левой части, называются подсоединенными и необходимы для управления соединением, транзакциями, выборкой данных и передачей изменений данных в БД. Они непосредственно взаимодействуют с базой данных. Объекты, расположенные в правой части, называются отсоединенными. Они позволяют работать с данными автономно. DataAdapter объекта DataSet для заполнения последнего набором данных, извлеченных непосредственно из БД.
Одной из основных идей, лежащих в основе
(рис 10.2) Уровни моделей поставщиков ADO.NETКак видно из рис 10.2, приложение .NET взаимодействует с базой данных посредством поставщиков данных. Каждый
При построении приложения, использующего доступ к данным, необходимо сперва попытаться найти "родного" поставщика данных для .NET. Когда же такового не существует, можно воспользоваться OleDb, если существует соответствующий драйвер для источника данных, к которому устанавливается подключение. Т. к. технология OleDb существует достаточно давно, создано много драйверов для различных источников данных, поддерживающих ее. Если в системе не установлен соответствующий драйвер OleDb, его можно попытаться найти на сайте разработчика той базы данных, к которой осуществляется доступ, либо на сайте Microsoft. То же самое справедливо и для
Каждый поставщик .NET реализует одинаковые базовые классы - Connection, Transaction, DataAdapter, Command, Parameter, DataReader имена которых зависят от поставщика. Например, у поставщика SQL Server существует объект SqlDataAdapter, у поставщика OleDb — OleDbDataAdapter и т. д.
У каждого поставщика данных существует собственное пространство имен. Хотя все поставщики относятся к пространству имен System.Data, каждый из них содержит свой подраздел этого пространства, который содержит объекты, специфичные для данного поставщика. Например, объект SqlDataAdapter находится в пространстве имен System.Data.SqlClient.
Все поставщики данных .NET реализуют одинаковые базовые функции, поэтому код, создаваемый для доступа к данным, выглядит приблизительно одинаково независимо от поставщика. Это означает, что, рассматривая использование интерфейсов отдельного поставщика, мы фактически рассматриваем возможность использования этих же интерфейсов применительно и к другим поставщикам данных.
При организации доступа к данным с помощью
Объект Connection представляет соединение с источником данных. С его помощью можно задать тип источника данных, его местонахождение, параметры доступа и ряд других атрибутов. Перед тем как начать взаимодействие с источником данных, необходимо установить подключение к нему с помощью объекта Connection.
Объект Command представляет запрос к источнику данных, вызов хранимой процедуры или прямой запрос на возврат содержимого конкретной таблицы. Как известно, существует несколько типов запросов. Часть из них возвращают данные, извлекаемые из источника данных, другие изменяют записи, третьи - управляют структурой БД. С помощью объекта Command возможно выполнить любой из перечисленных типов запросов. Различия в поведении объекта Command начинают проявляться тогда, когда необходимо исполнить тот или иной запрос. Так, например, при необходимости исполнения запроса, не возвращающего записи, необходимо использовать метод ExecuteNonQuery объекта Command, а при извлечении данных из БД - метод ExecuteReader, который, в свою очередь, возвращает объект DataReader, позволяющий просматривать полученные в результате запроса записи.
Объект DataReader предназначен для максимально быстрой выборки и просмотра возвращаемых запросом записей. Однако он позволяет просматривать весь результирующий набор записей путем перемещения от одной записи к другой и, таким образом, просматривать только одну запись за раз. Кроме того, DataReader не имеет возможности для обновления значений записей и поэтому может работать в режиме "только для чтения", за счет чего обладает высокой производительностью.
Объект Transaction позволяет осуществлять группировку записей в логическую единицу работы, называемой транзакцией. Транзакция логически объединяет несколько различных действий, связанных с манипулированием данными, в единое целое. В процессе выполнения действий, осуществляемых в рамках транзакции, СУБД обычно кэширует изменения, вносимые этими действиями в данные до момента завершения транзакции. Это позволяет производить отмену любых изменений, выполненных в рамках транзакции, в случае, если хотя бы одно из действий транзакции завершилось неудачно.
Объект Parameter позволяет вводить в запрос элемент, значение которого может быть задано непосредственно перед исполнением запроса. За счет этого отпадает необходимость каждый раз изменять текст самого запроса.
Объект DataAdapter представляет собой связующее звено между 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. Такая возможность исключает необходимость хранения одного набора данных в двух разных структурах.
Более подробно использование всех объектов модели
Для того, чтобы приложение .NET могло осуществлять взаимодействие с источником данных, необходимо установить соединение с ним. Наиболее типичным сценарием работы Web-приложения является следующий:
Рассмотрим эти этапы более подробно.
Как уже говорилось выше, для подключения к источнику данных с использованием
Итак, для того, чтобы подключиться к базе данных во время выполнения программы, необходимо создать объект Connection, а также задать его свойства, определяющие текущие параметры подключения. Основным параметром, устанавливающим необходимые опции для подключения к БД, является
В качестве примера рассмотрим создание небольшого Web-приложения для работы с базой данных, ER-модель которой представлена на рис 10.3.
(рис 10.3) ER-модель фрагмента базы данныхСоздадим базы данных в соответствии с моделью, изображенной на рис 10.3, в формате SQL Server Express и Access 2003. Для этого исполним следующие
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, выполнится при любом исходе попытки подключения, что гарантирует освобождение ресурсов сервера, не дожидаясь момента, когда память будет освобождена сборщиком мусора.
Установление соединения является необходимым условием подключения к базе данных. Однако подключение необходимо для того, чтобы выполнять определенные команды над элементами базы. Рассмотрим возможности выполнения команд над источниками данных.
Одним из основных элементов из набора классов Command. Для того, чтобы использовать класс Command, необходимо установить тип команды, установить текст запроса SQL и привязать ее к соединению с БД.
Существует 3 типа команд класса Command:
CommandType.Text |
Выполнение прямого оператора SQL, текст которого устанавливается в свойстве CommandText. Это значение по умолчанию. |
CommandType.StoredProcedure |
CommandText. |
CommandType.TableDirect |
Выполнение опроса всей таблицы базы данных, имя которой задается в свойстве CommandText. Этот тип команды используется для обратной совместимости с некоторыми драйверами OLE DB и не поддерживается поставщиком данных SQL Server. |
Пример создания объекта Command для выполнения
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 необходимо закрыть модуль чтения.
В данном примере используется класс .
(рис 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);
Как видно, в данном случае, строка tb_Delete ввести следующий текст "Какой-то товар' OR '1'='1", то в результате будут удалены все записи из таблицы "Товары", т. к. текст tb_Delete, будет
"DELETE FROM Товары WHERE НаименованиеТовара= 'Какой-то товар' OR '1'='1'"
Как видно из этого примера, наименование введенного товара становится неважным, т. к. условие всегда будет истинным за счет добавления оператора OR с заведомо верным условием. Можно привести примеры и более сложных атак внедрением SQL, приводящих к еще более серьезным и масштабным последствиям и создающих реальную угрозу функционирования всего Web-приложения.
Решений данной проблемы может быть несколько. Во-первых, можно постараться проанализировать текст, введенный пользователем, до того как он будет подставлен в текст Exception.
Можно придумать и массу других алгоритмов анализа вводимых данных, но все равно они не могут гарантировать стопроцентной безопасности вашего приложения.
Лучшей практикой защиты от атаки внедрением SQL является использование
Command через коллекцию Parameters. Например, команда удаления записи из таблицы "Товары", использованная выше, будет выглядеть следующим образом при использовании параметра:
DELETE FROM Товары WHERE НаименованиеТовара=@ProductName
Здесь @ProductName представляет собой параметр, значение которого должно быть установлено до того, как будет запущено выполнение запроса. Синтаксис
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. Так, предыдущий пример использования такой атаки в данном случае не работает.
При использовании сложной СУБД, реализующей полный набор функций клиент-серверной СУБД, разработчик приложения, взаимодействующего с базой данных, получает в свое распоряжение несколько дополнительных очень мощных инструментов, позволяющих облегчить, ускорить и обезопасить процесс взаимодействия с базой данных по сравнению с локальными базами данных. Одними из основных таких возможностей являются способы организации и использования хранимых процедур и триггеров. Вопросы создания и программирования хранимых процедур и триггеров, а также всевозможные вопросы, связанные с особенностями их реализации в конкретных СУБД, выходят за рамки данного курса. Желающим более подробно ознакомиться с особенностями реализации такого рода элементов рекомендуется ознакомиться с , .
Здесь же будут в основном рассмотрены вопросы использования хранимых процедур при работе с SQL Server без объяснения того, как именно создавалась та или иная
Хранимые процедуры обладают рядом преимуществ, главными из которых являются такие, как повышение быстродействия приложения при использовании хранимых процедур для обработки данных, повышение безопасности приложения при работе с данными.
В реальных приложениях, если используется СУБД, поддерживающая возможности использования хранимых процедур, рекомендуется воспользоваться этим и создавать приложение с их применением. Кроме того, рекомендуется все наиболее типичные и часто возникающие действия, которые связаны с обработкой данных, содержащихся в базе данных, а также операции вставки, удаления и изменения производить с помощью вызова предварительно созданных хранимых процедур.
Рассмотрим пример создания и использования простой хранимой процедуры, позволяющей добавить новый товар в таблицу "Товары" базы данных. Текст хранимой процедуры, реализованной в СУБД 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().
Из данного примера видно, что при добавлении нового параметра, выступающего в роли параметра хранимой процедуры, необходимо обязательно указывать его тип, а также длину (для строковых типов это количество символов, для числовых - количество байт).
Рассмотренная ранее логика взаимодействия клиентского приложения с базой данных, основанная на соединении, оправдана для реализации односторонней связи с базой данных. Это может быть либо получение данных, либо выполнение запросов, связанных с внесением изменений в нее. Реализация же сложных операций взаимодействия с БД при этом является очень трудоемким процессом и требует написания большого количества программного кода, его отладки и т. д.
Основной идеей использования
Объект DataSet представляет собой контейнер, содержащий объекты DataTable и Relation. DataTable представляет собой таблицу, состоящую из строк и столбцов. Строки таблицы представлены объектом DataRow, который, в свою очередь, представляет собой коллекцию столбцов таблицы (объект DataColumn ). Данные в DataSet отсоединены от БД. Все изменения данных кэшируются в объектах DataRow. При возникновении необходимости передачи изменений в данных объекта DataSet существует возможность передачи только изменившейся части данных, что позволяет значительно экономить ресурсы канала связи, т. к. в этом случае передается гораздо меньший объем данных.
Обобщая все вышесказанное, можно сделать вывод о том, что использование объекта DataSet в ряде случаев оказывается более эффективным, чем DataReader. Наиболее типичными ситуациями, в которых рекомендуется применять объект DataSet, являются следующие:
DataSet позволяет легко сохранять данные в файле XML. При этом возможны варианты сохранения либо только данных, либо только структуры данных, либо и того и другого.DataReader обеспечивает перемещение по набору только вперед. С помощью же DataSet возможна организация постраничного просмотра данных.DataSet, в отличие от DataReader, содержит средства организации сортировки и фильтрации данных.Более подробная информация относительно сценариев использования .
Выше уже говорилось о том, что DataSet состоит из таблиц, которые, в свою очередь, состоят из строк, а они, в свою очередь, - из столбцов. Каждый из перечисленных элементов реализован в виде класса. Для управления автономными изменениями DataSet отслеживает информацию о версии каждого объекта DataRow. Это означает, что когда происходит редактирование строки, ее исходное значение сохраняется в памяти, а строка помечается как измененная. Аналогичные действия происходят и при добавлении и изменении строк DataTable. В дальнейшем в базу данных можно перенести значения только тех строк, которые были затронуты изменениями. Таким образом, DataSet никогда не поддерживает постоянного соединения с базой данных.
Для извлечения данных из базы данных и наполнения ими объекта DataSet необходимо использовать еще один объект DataAdapter, который помимо прочего позволяет обновлять данные БД на основе внесенных в DataSet изменений.
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 будет использовать уже существующее соединение.
После того как необходимый 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 %>.
Такого же эффекта можно добиться, если использовать построитель выражений. Для этого необходимо в режиме редактирования дизайна открыть свойства объекта (рис 10.12). В открывшемся окне, изображенном на рис 10.13, необходимо выбрать категорию AppSettings и в свойстве AppSetting установить значение MySetting. Теперь значение определенное в свойстве MySetting файла web.config, будет отображаться на странице.

(рис 10.13) Вызов построителя выражений(рис 10.12) Окно построителя выраженийКонечно, отображение значения свойства, определенного в файле web.config, имеет незначительное практическое значение. Однако извлечение таким образом строки подключения к базе данных, определенной в web.config, и запись ее значения в свойство ConnectionString объекта , обеспечивающего будет рассмотрено ниже.
Для отображения набора значений необходимо использовать привязку с множественным значением. Привязываемый набор значений должен содержаться в объекте, способном хранить коллекции значений (например, объекты ArrayList, Hashtable и др.).
Каждый элемент, поддерживающий возможность множественной привязки, содержит следующий набор свойств:
DataSource |
Объект, содержащий привязываемые данные. |
DataSourceID |
Свойство, которое содержит идентификатор объекта, использующегося для подключения к источнику данных. В отличие от 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, .
GridView представляет собой таблицу, способную привязываться к данным и отображать содержимое таблиц. Поддерживает выбор, редактирование, сортировку и перемещение по страницам данных. предназначен для показа одной записи из таблицы за раз. Поддерживает редактирование и перемещение по страницам. практически аналогичен , с той лишь разницей, что основан на шаблонах, и это делает его более гибким при отображении информации.
Самым мощным элементом по количеству возможностей является 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. Среди них такие как:
- позволяет подключаться к любому источнику данных, который имеет поставщика данных
- позволяет подключаться к пользовательскому классу доступа к данным. Этот подход целесообразно применять при построении крупномасштабных и сложных приложений. Подробнее об использовании пользовательских классов доступа к данным и элементе
AccessDataSource - позволяет подключаться к базам данных Microsoft Access.
XmlDataSource - позволяет подключаться к XML-файлу.
- позволяет подключаться к файлу Web.Sitemap, содержащему описание навигационной структуры Web-приложения.
Для использования элемента управления источниками данных достаточно перетащить его с панели Toolbox в область Web-страницы. При этом он отображается в виде серого прямоугольника, содержащего внутри наименование элемента управления источником данных.
(рис 10.16) Использование элемента управления источником данныхДля конфигурирования элемента можно воспользоваться мастером, запускаемым при нажатии на ссылку Configure Data Source.
На первом шаге конфигурирования элемента необходимо определить
(рис 10.17) Первый шаг настройки элемента SqlDataSource на подключение к базе данныхНа втором шаге мастер предлагает сохранить сгенерированную строку подключения к БД в файле web.config. При необходимости можно отказаться, а также изменить наименование переменной, содержащей строку подключения.
Третий шаг мастера позволяет определить запросы к БД, используемые для выполнения операций над данными. В частности, предлагается выбрать таблицу и сгенерировать запрос на выборку данных из нее. Здесь же можно определить свои собственные запросы на выборку, добавление, удаление и обновление данных или указать используемую хранимую процедуру.
Мастер может также сгенерировать стандартные запросы, используемые для добавления, удаления и обновления значений в связанном источнике данных. Для построения этих запросов можно воспользоваться кнопкой Advanced. При этом будет открыто окно, в котором можно установить два свойства. Свойство Generate INSERT, UPDATE, and DELETE statements задает режим создания соответствующих запросов. Свойство Use optimistic concurrency задает режим определения тех строк, которые были изменены с момента последней загрузки данных в объект DataSet.

(рис 10.19) Второй шаг мастера настройки SqlDataSource на подключение к БД(рис 10.18) Третий шаг мастера настройки SqlDataSource на подключение к БД
(рис 10.20) Окно генерации запросов на вставку, обновление и удаление данных из БДЧетвертый шаг мастера позволяет протестировать созданный запрос и, после нажатия на кнопку Finish, завершить настройку элемента управления источником данных.
В результате настройки параметров объекта ).
(рис 10.21) Окно свойств элемента SqlDataSource, установленных с помощью мастераDataSourceMode — задает режим, используемый для извлечения строк. Возможные значения — DataSet и DataReader.
ConnectionString — задает строку подключения к источнику данных.
DeleteCommandType — задает тип запроса Delete. Возможные значения - Text и StoredProcedure.
DeleteQuery — задает запрос, используемый для удаления данных из связанного источника.
Последние два параметра применяются также и для других запросов - на изменение, добавление и выборку данных.
При необходимости можно изменить любой запрос. Для этого нужно щелкнуть на кнопку
, расположенную в строке редактируемого запроса. В результате будет открыто окно редактирования запроса. Пример такого окна для редактирования запроса на изменение данных изображен на рис 10.22.
(рис 10.22) Пример окна редактирования запроса на изменение данныхПосле того как настройки элемента управления источником данных завершены, необходимо связать с ним элемент отображения данных на экране. Для этого нужно указать имя объекта показана возможность привязки GridView к и установка несколько специфических режимов отображения данных (удаление, редактирование, выбор строк, сортировка данных в столбцах).
(рис 10.23) Привязка GridView к элементу управления источником данных и задание специфических режимов их отображенияСледует иметь в виду, что каждый элемент управления источником данных может использоваться для связи только с одной таблицей базы данных. Если в приложении требуется независимо извлекать или обновлять информацию из нескольких таблиц, необходимо применять несколько элементов управления источниками данных.
При использовании данного подхода к привязке данных вызов метода DataBind() в коде приложения становится необязательным.
В большинстве реальных приложений работы с данными невозможно применять жестко закодированные запросы, т. к. в зависимости от используемого режима они должны содержать различные данные. Для решения этой задачи задействуются параметры, применение которых уже рассматривалось в разделе "Использование
Поместим на форму элементы DropDownList и . Настроим данные элементы на подключение к источнику данных, содержащему список товаров. Определения этих элементов выглядят следующим образом:
<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 и еще один - . Настроим в соответствии со следующим определением:
<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>
Ключевыми элементами настройки .

(рис 10.25) Результат работы приложения, содержащего главную и подчиненную таблицы(рис 10.24) Диалоговое окно редактирования параметров запросаВ данном примере значение параметра ) нужно щелкнуть по кнопке
окна.
Допустимые типы источников данных параметров можно выбирать из списка. При этом допустимы следующие значения списка:
Control |
Свойство элемента управления. |
Cookie |
Значение Cookie набора. |
Form |
Переменная формы. С помощью данного типа источника данных параметра можно извлекать значения, отправленные странице элементом ввода. |
Profile |
Значение текущего профиля пользователя. |
QueryString |
Строковое значение запроса. Позволяет извлекать значение из текущей строки запроса. |
Session |
Значение состояния сеанса. |
предоставляет возможность изменения данных, содержащихся в источниках данных. Для того, чтобы воспользоваться этой возможностью, необходимо определить соответствующие запросы, выполняющие операции вставки, обновления и удаления записей. Все они создаются по аналогии с запросом Select на основе параметризованного запроса. Выше уже упоминалось об имеющихся в возможностях построения запросов данного типа. В качестве примера добавим возможность изменения данных в страницу просмотра товаров и заказов, созданную ранее. Для этого выполним нижеследующие операции.
Определим в запрос на обновление данных ( UpdateQuery ). Текст запроса будет выглядеть следующим образом:
UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество = @Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE (КодОперации = @КодОперации)
При составлении запроса важно давать имена параметрам таким образом, чтобы они совпадали с именами столбцов таблицы. Это объясняется тем, что ASP.NET автоматически отправляет значения параметрам, взятым из столбцов, у которых имена совпадают с именами параметров.
В данном же примере присутствует еще одна особенность, которую следует учитывать. Она состоит в том, что ранее для удобства отображения данных был использован запрос, в котором происходит внутреннее соединение данных из таблиц "Контрагенты" и "Закупки", - он необходим для подмены номеров контрагентов их наименованиями. Поэтому при отображении информации в GridView мы видим наименования контрагентов вместо их номеров. Однако, это создаст некоторые трудности при реализации обновления данных. Дело в том, что теперь мы не можем реализовать обновление данных контрагентов, так как они находятся в таблице "Контрагенты", а связан с таблицей "Закупки". Все, что мы можем сделать, - это изменить номер текущего контрагента в редактируемой операции закупки. При этом необходимо будет вводить не наименование контрагента, а его номер.
Именно этим объясняется наличие в запросе параметра КодКонтрагента = @Контрагент, обновляющего и нформацию о контрагенте текущей операции закупки.
Добавим в ).
(рис 10.26) Добавление столбца в GridView с помощью интеллектульного дескриптораВ открывшемся окне (рис 10.27) установим тип поля CommandField, а также убедимся, что установлен флажок Edit/Update. Если требуется добавить также и возможность удаления элемента, необходимо установить флажок Delete.
(рис 10.27) Окно добавления нового столбца к GridViewПосле этого в ).
(рис 10.28) Редактирование содержимого таблицы "Закупки" с помощью GridViewПри обновлении данных в базе могут возникать серьезные затруднения, вызванные возможностью одновременной работы большого количества пользователей с одними и теми же данными. Это особенно актуально для Web-приложений, количество пользователей которых может быть в несколько раз больше, чем для традиционных win32-приложений. Достаточно подробное описание возможных проблем и путей их решения можно найти в . Остановимся лишь на некоторых из них, наиболее актуальных и востребованных.
Если посмотреть на текст запроса на обновление данных таблицы "Закупки", использованный в предыдущем примере, можно заметить, что очень важным его элементом является строка WHERE (КодОперации = @КодОперации). Она позволяет обновить только ту строку, которая соответствует значению, определенному в параметре "КодОперации". Этот код хорошо работает, если в один и тот же момент времени одну запись пытается обновить только один человек. Если же представить, что в момент редактирования нами этой записи другой пользователь сможет изменить значение поля "КодОперации", то попытка обновления таблицы "Закупки", предпринимаемая нами, завершится неудачей, т. к. в таблице больше не существует строки с тем значением кода операции, которое было взято в качестве параметра в момент начала редактирования.
Поэтому, чтобы быть уверенным в том, что попытка обновления завершится успешно, необходимо использовать лишь те значения первичных ключей, которые доступны только для чтения. Кроме того, параметр, который используется для хранения исходного значения ключевого поля, необходимого для обновления записи, рекомендуется помечать префиксом. Задать префикс можно с помощью свойства OldValuesParameterFormatString объекта . Обычно в качестве префикса используется значение original_. Так, в приведенном выше примере строка WHERE с учетом префикса может быть записана следующим образом:
WHERE КодОперации=@original_КодОперации
Даже при использовании ключей, доступных только для чтения, в ситуации, описанной выше, когда два пользователя одновременно редактируют одну и ту же запись в базе данных, может возникнуть конфликт. Суть его состоит в том, что изменения того пользователя, который позже других выполнит команду переноса сделанных им изменений в базу данных, перезапишут изменения других пользователей, редактировавших эту запись вместе с ним, но выполнивших команду обновления чуточку раньше. Для разрешения этой ситуации можно реализовать строгую проверку параллелизма при выполнении обновления данных в БД. Одним из таких способов является выполнение обновления только в том случае, если значения каждого из полей обновляемой таблицы совпадают с оригинальными значениями, взятыми в момент начала редактирования. Запрос, который реализует такой подход для примера, приведенного выше, выглядит следующим образом:
UPDATE Закупки SET ДатаОперации = @ДатаОперации, Количество = @Количество, Цена = @Цена, КодКонтрагента = @Контрагент WHERE (КодОперации = @original_КодОперации AND ДатаОперации= @original_ДатаОперации AND Количество=@originalКоличество AND Цена=@original_Цена AND КодКонтрагента=@original_Контрагент)
Проблема в данном случае состоит в том, что использование префикса возможно только для ключевых полей таблицы, определенных в свойстве DataKeyNames объекта GridView. Понятно, что невозможно определить все поля таблицы в качестве ключевых. Поэтому лучшим решением будет использование свойства ConflictDetection объекта равным значению CompareAllValues.
Рассмотренные способы привязки элементов отображения к источникам данных, а также возможности объекта целесообразно применять только в относительно небольших приложениях. При необходимости создания сложного Web-приложения использование может привести к возникновению многих проблем, вызванных недостатками данного элемента. К недостаткам можно отнести следующие:
SqlDataSource. Это, с одной стороны, крайне неэффективно при использовании одинаковых запросов на разных страницах, т. к. в этом случае необходимо дублировать одинаковые элементы на разных страницах. С другой стороны, поддерживать такой код крайне неудобно, т.к. может возникнуть ситуация, в которой изменения придется вносить в несколько элементов SqlDataSource . Для решения этих и многих других проблем, возникающих при использовании SqlDataSource , необходимо при разработке приложения использовать принципы построения При создании сложных приложений, ориентированных на активное взаимодействие с пользователями, большое значение имеет отображение данных и возможности настройки этого отображения. 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.
Логика взаимодействия сложных информационных систем с базами данных обычно бывает достаточно сложной. Сложность эта многократно увеличивается вместе с увеличением сложности информационной системы. Если не обеспечить унифицированные механизмы и интерфейсы доступа к данным, поддержание и развитие информационной системы становится очень трудоемким, а подчас и просто невозможным. Выходом из данной ситуации является создание промежуточного слоя, обеспечивающего взаимодействие приложения с базой данных на основе принципов
В сложных профессиональных приложениях код доступа к базе данных не встраивается в само приложение, а инкапсулируется в отдельный выделенный класс. Для выполнения операции с базой данных приложению нужно создать экземпляр этого класса и вызвать соответствующий метод. При создании такого класса будем следовать ряду рекомендаций.
Современные приложения создаются на основе объектно-ориентированного подхода. Основным строительным блоком приложения в данном подходе является класс. Все обрабатываемые приложением данные хранятся в объектах, которые также содержат и методы для их обработки. Тем не менее большинство используемых СУБД сегодня построены на принципах реляционной модели данных. Принципы обработки данных в объектных и реляционных моделях отличаются, это необходимо учитывать при организации взаимодействия с базой данных. Ниже рассматриваются некоторые вопросы организации такого взаимодействия.
В хорошо организованном приложении создается отдельный класс для каждой таблицы либо логически взаимосвязанной группы таблиц базы данных. Этот класс используется для организации взаимодействия приложения с базой данных, реализуя все необходимые операции манипуляции этими данными. Совокупность таких классов представляет собой как бы промежуточный программный слой, который находится между классами, содержащими данные и методы для их обработки, и данными, сохраненными в базе данных. Набор таких классов, реализующих логику взаимодействия приложения с базой данных, может быть реализован в виде отдельной сборки, которая может быть откомпилирована. Это целесообразно в том случае, когда аналогичную логику необходимо реализовать в нескольких приложениях.
Рассмотрим пример реализации взаимодействия Web-приложения с таблицей "Товары" базы данных "TestDb" с использованием принципов трехуровневой архитектуры.
Прежде всего, создадим класс Product, который содержит поля, соответствующие полям таблицы "Товары". Доступ к этим полям должен осуществляться посредством соответствующих свойств. В типичном объектно-ориентированном приложении такие классы выполняют роль хранилища данных, доступ к которым предоставляют методам, осуществляющим их обработку. В достаточно большом и сложном приложении классы желательно размещать отдельно от основного кода программы. Это позволяет легко отделять код основной программы от кода, который может быть перенесен в другие приложения и повторно использоваться для облегчения и ускорения создания приложения. Для создания класса рекомендуется выполнить команду Website $$\Rightarrow$$ 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) Результат работы программы трехуровневого взаимодействия с БДСоздание пользовательского кода, реализующего возможность взаимодействия с базой данных, — достаточно трудоемкий процесс. К тому же такой способ позволяет связывать визуальные элементы с данными только в программном коде. Для получения возможности создания такой связи в режиме редактирования страницы можно использовать объект . Этот объект позволяет создавать связь между элементами управления, расположенными на Web-странице, и компонентами доступа к данным, реализованным в виде пользовательских классов. Но для этого необходимо, чтобы пользовательский класс доступа к данным подчинялся следующим правилам:
IEnumerable.Использование ObjetDataSource в ряде случаев бывает гораздо удобнее применения таких элементов доступа к данным, как или AccessDataSource. Скажем, в предыдущем примере для доступа к таблице "Товары" можно воспользоваться пользовательским классом и объектом . Для этого необходимо выполнить нижеследующие шаги.
Поместить на форму объект , перетащив его с панели Toolbox, после чего прикрепить к нему класс, отвечающий за извлечение данных, - в нашем случае это ProductsDB. Для этого достаточно установить значение свойства TypeName равным ProductsDB. После этого необходимо определить свойства SelectMethod, DeleteMethod, UpdateMethod и InsertMethod, используемые для выполнения соответствующих операций над данными в БД. В качестве значений этих свойств нужно установить имя метода, выполняющего соответствующие операции. Так, в качестве значения свойства SelectMethod в данном примере установим имя метода, извлекающего данные из БД, - GetProducts. Этот метод удовлетворяет всем критериям - он возвращает объект, представляющий все данные через общедоступные свойства.
Имена этих свойств и будут использованы в качестве имен столбцов при выводе информации на экран в табличной форме. После того как связь с классом, извлекающим данные из базы данных, установлена, необходимо добавить на Web-форму элемент GridView и связать их, установив в свойстве DataSourceID этого элемента имя объекта . Определение и 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 для организации доступа к БДКак видно из этого примера, с точки зрения пользователя использование .
Деятельность большинства Web-приложений основывается на работе с данными - извлечении, отображении и модификации. Основным источником данных в большинстве современных приложений служит база данных. Поэтому вопросы организации взаимодействия приложения с базой данных является весьма актуальными.
Одной из наиболее современных технологий доступа к данным является
Программная модель
Все поставщики данных .NET реализуют одинаковые базовые функции, поэтому программный код, создаваемый для доступа к данным, выглядит приблизительно одинаково независимо от поставщика.
При организации взаимодействия Web-приложения с базой данных нужно использовать определенный сценарий, который предусматривает web.config.
Весь программный код взаимодействия с базой данных, потенциально способный вызывать исключения, необходимо помещать внутри блока try catch.
Для обслуживания соединения с базой данных расходуются ресурсы компьютера, на котором выполняется Web-приложение. В связи с этим рекомендуется открывать соединение как можно позже, а закрывать как можно раньше, сразу после того, как все необходимые действия с объектами базы данных были выполнены. Программный код взаимодействия с базой данных необходимо строить таким образом, чтобы открытое соединение закрывалось при любом исходе соединения с ней.
Command.
Обращение к полям таблицы может происходить как по имени, так и с помощью индекса.
В GridView.
При взаимодействии Web-приложения с базой данных в реальных условиях очень часто возникает необходимость динамического формирования
Традиционная логика взаимодействия клиентского приложения с базой данных основана на использовании соединений и оправдана при реализации односторонней связи с БД. Реализация сложных алгоритмов взаимодействия приложения с БД является достаточно трудоемким процессом и требует написания большого количества программного кода.
Важнейшим классом DataSet, используемый совместно с классом DataAdapter.
Для автоматизации отображения данных в элементе управления без использования программирования необходимо использовать привязку и отображение данных. Большинство элементов управления ASP.NET поддерживают привязку данных. Различают привязку данных с одним и с множественным значением. Помимо применения программного кода для осуществления привязки данных Visual Studio 2005 позволяет осуществлять привязку с использованием визуальных средств.
Использование баз данных очень часто требует фильтрации и сортировки данных. Для этих целей в ASP.NET предназначен объект DataView. Этот класс снабжен возможностью фильтрации и сортировки отображаемых данных, причем эти режимы никак не затрагивают реальные данные.
Логика взаимодействия сложных информационных систем с базами данных обычно бывает достаточно сложной. Сложность эта многократно увеличивается вместе с увеличением сложности информационной системы. Если не обеспечить унифицированные механизмы и интерфейсы доступа к данным, поддержание и развитие информационной системы становится очень трудоемким, а подчас и просто невозможным. Выходом из данной ситуации является создание промежуточного слоя, обеспечивающего взаимодействие приложения с базой данных на основе принципов
В сложных профессиональных приложениях код доступа к базе данных не встраивается в само приложение, а инкапсулируется в отдельный выделенный класс. Для выполнения операции с базой данных приложению необходимо создать экземпляр этого класса и вызвать соответствующий метод. Особое значение при организации взаимодействия объектного приложения с базой данных имеет организация взаимодействия объектов с реляционными таблицами БД.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.