Разработка бизнес-приложений двухуровневой архитектуры на платформе .NET Framework

1. Классическая двухуровневая архитектура «Клиент-сервер»

По своей сути бизнес-приложение является информационной системой.

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

  • информационные системы предназначены для сбора, хранения и обработки информации. Поэтому в основе любой из них лежит среда хранения и доступа к данным. Так, в состав автоматизированной информационной системы входит база данных (БД) – систематизированное хранилище информации определенной предметной области, к которому могут иметь доступ различные прикладные программы. В зависимости от модели базы данных находятся под управлением соответствующей системы управления базами данных (СУБД) – программного средства для создания, ведения и использования БД;
  • – информационные системы ориентируются на конечного пользователя, не обладающего высокой квалификацией в области применения вычислительной техники. Поэтому клиентские приложения информационной системы должны обладать простым, удобным, легко осваиваемым интерфейсом, который предоставляет конечному пользователю все необходимые для работы функции, но в то же время не дает ему возможность выполнять какие-либо лишние действия.

Традиционным методом организации ИС является двухуровневая архитектура «Клиент-сервер». В этом случае вся прикладная часть информационной системы размещается на рабочих станциях, а на стороне сервера осуществляется только доступ к базе данных (рисунок 1).

Архитектура «Клиент-сервер»

Рисунок 1 – Архитектура «Клиент-сервер»

Компьютер (или программа), управляющий и/или владеющий каким-либо ресурсом, называют сервером этого ресурса. Компьютер (или программа), запрашивающий и пользующийся каким-либо ресурсом, называют клиентом этого ресурса.

Клиент и сервер могут находиться как на одном компьютере (ПК), так и на разных ПК в сети. Также может возникать такая ситуация, когда некоторый программный блок будет одновременно выполнять функции сервера по отношению к одному блоку и клиента по отношению к другому.

Основной принцип технологии «Клиент-сервер» заключается в разделении функций приложения как минимум на три группы:

  1. Модули интерфейса с пользователем. Также эту группу называют логикой представления. Через эту группу пользователи взаимодействуют с приложением. Независимо от конкретных характеристик логики представления (интерфейс командной строки, сложные графические пользовательские интерфейсы, интерфейсы через посредника) ее задача состоит в том, чтобы обеспечить средства для наиболее эффективного обмена информацией между пользователем и информационной системой.
  1. Модули хранения данных. Эту группу также называют бизнес-логикой. Бизнес-логика определяет, для чего конкретно предназначено приложение (например, прикладные функции, характерные для данной предметной области). Разделение приложения по границам между программами обеспечивает естественную основу для распределения приложения на нескольких компьютерах.
  1. Модули обработки данных (функции управления ресурсами). Эту группу также называют логикой доступа к данным или алгоритмами доступа к данным. Алгоритмы доступа к данным исторически рассматривались как специфический для конкретного приложения интерфейс к механизму постоянного хранения данных наподобие файловой системы или СУБД. При помощи модулей обработки данных организуется специфический для приложения интерфейс к СУБД. При помощи интерфейса приложение управляет соединениями с базой данных и запросами к ней (перевод специфических для конкретного приложения запросов на язык SQL, получение результатов и перевод этих результатов обратно в специфические для конкретного приложения структуры данных).

В соответствии с разделением функций в любом приложении выделяются следующие компоненты: компонент представления данных, прикладной компонент и компонент управления ресурсом. В классической двухуровневой архитектуре «Клиент-сервер» приходится распределять три основные части приложения по двум физическим модулям. Обычно прикладной компонент располагается на сервере (например, сервере базы данных), компонент представления данных – на стороне клиента, а компонент управления ресурсом распределяется между клиентской и серверной частями. В этом заключается основной недостаток классической двухуровневой архитектуры.

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

Чтобы избежать несогласованности различных элементов архитектуры были созданы две модификации двухзвенной архитектуры «Клиент-сервер»: «Толстый клиент» («Тонкий сервер») и «Тонкий клиент» («Толстый сервер»).

В данных архитектурах разработчики попытались выполнять обработку данных на одной из двух физических частей – либо на стороне клиента («Толстый клиент»), либо на сервере («Тонкий клиент).

Есди разрабатывается двухуровневая классическая архитектура «Клиент-сервер», то необходимо помнить следующее:

архитектура «Толстый сервер» аналогична архитектуре «Тонкий клиент» (рисунок 2);

Архитектура «Тонкий клиент»

Рисунок 2 – Архитектура «Тонкий клиент»

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

  • усложняется реализация, так как языки типа SQL не приспособлены для разработки подобного ПО и нет хороших средств отладки;
  • производительность программ, написанных на языках типа SQL, значительно ниже, чем созданных на других языках, что имеет важное значение для сложных систем;
  • программы, написанные на СУБД-языках, обычно работают недостаточно надежно; ошибка в них может привести к выходу из строя всего сервера баз данных;
  • получившиеся таким образом программы полностью непереносимы на другие системы и платформы.

архитектура «Тонкий сервер» аналогична архитектуре «Толстый клиент» (рисунок 3).

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

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

Архитектура «Толстый клиент»

Рисунок 3 – Архитектура «Толстый клиент»

Для решения перечисленных проблем используются многоуровневые (три и более уровней) архитектуры «Клиент-сервер».

2. Платформа .NET Framework

Клиентское приложение информационной системы с двухзвенной архитектурой «Клиент-сервер» обычно имеет графический интерфейс, разработанный на языках объектно-ориентированного программирования, каких как C++, C#, Delphi, Visual Basic и др. Так, интерфейс, реализуемый при помощи клиентского приложения – это компьютерная программа, устанавливаемая на клиентские компьютеры, предназначенная для работы с файлами данных через сеть.

Microsoft Visual Studio – линейка продуктов компании Microsoft, включающих интегрированную среду разработки программного обеспечения и ряд других инструментальных инструментов. Данные продукты позволяют разрабатывать как консольные приложения, так и игры и приложения с графическим интерфейсом, в том числе с поддержкой технологии Windows Forms, а также веб-сайты, веб-приложения, веб-службы как в родном, так и в управляемом кодах для всех платформ, поддерживаемых Windows, Windows Mobile, Windows CE, .NET Framework, Xbox, Windows Phone .NET Compact Framework и Silverlight.

Visual Studio включает один или несколько компонентов из следующих:

  • Visual Basic .NET, а до его появления – Visual Basic;
  • Visual C++;
  • Visual C# (включён, начиная с Visual Studio .NET);
  • Visual F# (включён, начиная с Visual Studio 2010). Перечисленные языки относятся к семейству .NET. Платформа

.NET Framework является надстройкой над операционной системой, в качестве которой может выступать любая версия Windows. Данная архитектура является основой для построения современных приложений, ориентированных на работу в среде Windows, и может использовать любой из совместимых языков программирования для написания программного кода. Особенностью .NET Framework является то, что различные модули одной и той же программной системы могут быть написаны на различных языках программирования. Одним из важнейших элементов данной архитектуры является наличие сборщика мусора, осуществляющего очистку неиспользуемых областей памяти и избавляющего программиста от проблемы «утечки памяти».

Среда разработки Visual Studio также содержит встроенный web-сервер для отладки проектов.

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

3. Доступ к данным ADO.NET

Технология доступа к базам данных в .NET называется ADO.NET (ActiveX Data Objects). ADO.NET, как часть Microsoft.NET Framework, представляет собой набор средств и слоев, позволяющих приложению легко управлять и взаимодействовать со своим файловым или серверным хранилищем данных. Рассмотрим данную технологию.

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

Модель доступа к данным в ADO представлена на рисунке 4. Приложение является потребителем данных. Объект –представитель класса DataSet – представляет набор данных – временное локальное хранилище информации. Провайдер данных Data Provider выступает в роли посредника между потребителем информации и базой данных Database – автономного и независимого от приложения хранилища информации.

Модель доступа к данным в ADO

Рисунок 4 – Модель доступа к данным в ADO

В объектной модели ADO.NET можно выделить несколько уровней.

Уровень данных. Это по сути дела базовый уровень, на котором располагаются сами данные (например, таблицы базы данных MS SQL Server). На данном уровне обеспечивается физическое хранение информации на магнитных носителях и манипуляция данными на уровне исходных таблиц (выборка, сортировка, добавление, удаление, обновление).

Уровень бизнес-логики. Это набор объектов, определяющих, с какой базой данных предстоит установить связь и какие действия необходимо будет выполнить с содержащейся в ней информацией. Для установления связи с базами данных используется объект DataConnection. Для хранения команд, выполняющих какие-либо действия над данными, используется объект DataAdapter. И, наконец, если выполнялся процесс выборки информации из базы данных, для хранения результатов выборки используется объект DataSet.

Уровень приложения. Это набор объектов, позволяющих хранить и отображать данные на компьютере конечного пользователя. Для хранения информации используется объект DataSet, а для отображения данных имеется довольно большой набор элементов управления (DataGrid, TextBox, ComboBox, Label и т. п.). В Visual Studio .Net можно вести разработку двух типов приложений. В первую очередь это традиционные Windows-приложения (на основе Windows-форм), которые реализованы в виде exe-файлов, запускаемых на компьютере пользователя. И Web-приложения (на основе Web-форм), которые работают в оболочке браузера – технология разработки будет рассмотрена в соответствующих разделах. Для хранения данных на уровне обоих типов приложений используется объект DataSet. Обмен данными между приложениями и уровнем бизнес-логики происходит с использованием формата XML, а средой передачи данных служат либо локальная сеть (Интранет), либо глобальная сеть (Интернет).

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

С целью разграничения функциональности классов ADO.NET они рассредоточены по различным пространствам имен. В ADO.NET пространства имен используются для отделения различных частей модели управляемого поставщика данных. Пространство имен System.Data включает в себя общие структуры данных, не зависящие от конкретного поставщика. В него входит класс DataSet и целое семейство связанных с ним классов (DataTable, DataColumn, DataRow, DataRelation, Constraint и т. п.). Каждый управляемый поставщик имеет свое собственное пространство имен. Так, управляемый поставщик SQL Server находится в пространстве имен System.Data.SqlClient, управляемый поставщик OLE DB – в пространстве имен System.Data.OleDb, управляемый поставщик ODBC – в пространстве имен System.Data.Odbc.

В ADO.NET есть два основных способа, обеспечивающих взаимодействие приложения с данными: использование набора данных (объект DataSet) и работа непосредственно с элементами базы данных: таблицами, представлениями, хранимыми процедурами и т. п. (объект DataCommand).

В первом способе – «отсоединенной» модели работы с данными на основе DataSet – разработчик создает в памяти компьютера некоторое пустое хранилище, загружает его данными, используя адаптер данных (объект DataAdapter), работает с этой информацией (сортирует, фильтрует, изменяет), затем, по мере необходимости, через тот же адаптер данных, возвращает все изменения в исходную базу данных.

В качестве альтернативы можно работать непосредственно с базой данных. В этой модели используется объект DataCommand, в котором содержится SQL-запрос или имя хранимой процедуры. Команда запускается на выполнение, и если команда не возвращает результата (например, удаление записей), то все действия команды выполняются непосредственно над объектами базы данных (например, удаляется запись из таблицы). Если в результате работы команды из базы данных возвращается набор записей, то используется объект DataReader для выборки данных. В некоторых случаях задача вообще не решается путем использования набора данных. Например, если требуется создать объект базы данных (типа таблица), то это можно сделать только с помощью команд (объектов DataCommand).

В данном курсе не будем рассматривать прямой доступ к данным.

Таким образом, можно сказать, что все основные компоненты ADO.NET служат для выполнения определенного круга задач:

  • создать и корректно настроить соединение с СУБД;
  • создать объект DataSet и заполнить его данными из СУБД;
  • отключиться от СУБД;
  • выполнить требуемые действия с локальной копией данных в объекте DataSet;
  • передать сделанные в DataSet изменения обратно в СУБД. Обобщенная модель данного процесса приведена на рисунке 5.

технология ADO.NET

Рисунок 5 – Обобщенная модель работы технологии ADO.NET

4. Соединение с источником данных

Для перемещения данных между их постоянным хранилищем и приложением в первую очередь необходимо создать соединение с источником данных (Connection). В арсенале ADO.NET для этих целей имеется ряд объектов:

  • SQLConnection – объект, позволяющий создать соединение с базами данных MS SQL Server;
  • OleDbConnection – объект, позволяющий создать соединение с любым источником данных (простые текстовые файлы, электронные таблицы, базы данных) через OLE DB;
  • OdbcConnection –объект, позволяющий создать соединение с ODBC-источниками данных.

Жизненный цикл объекта Connection состоит из таких этапов как:

  • объявление объекта соединения;
  • создание объекта соединения;
  • определение строки соединения;
  • использование соединения, например, для создания команды;
  • открытие соединения;
  • выполнение команды;
  • закрытие соединения;
  • обработка полученных данных;
  • изменение команды;
  • повторное открытие соединения;
  • выполнение команды;
  • закрытие соединения.

Объявление объекта соединения

Пример объявления соединений показан в листинге 1.

Листинг 1

public class Form1 : System.Windows.Forms.Form

{

private System.Data.SqlClient.SqlConnection sqlConnection1; private System.Data.OleDb.OleDbConnection oleDbConnection1; private System.Data.Odbc.OdbcConnection odbcConnection1; private System.Data.Odbc.OdbcConnection odbcConnection2;

}

Создание соединения

Операторы создания объектов соединения помещаются в блок инициализации, как показано в листинге 2.

Листинг 2

private void InitializeComponent()

{

this.sqlConnection1 =

new System.Data.SqlClient.SqlConnection();

this.oleDbConnection1 =

new System.Data.OleDb.OleDbConnection(); this.odbcConnection1 =

new System.Data.Odbc.OdbcConnection(); this.odbcConnection2 =

new System.Data.Odbc.OdbcConnection();

}

Строка соединения

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

Строки соединения управляемого поставщика SQL Server

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

  • Data Source – имя сервера баз данных;
  • Initial Catalog – база данных, находящаяся на сервере;
  • User ID – идентификатор пользователя, который должен применяться для аутентификации пользователя на сервере баз данных;
  • PWD – пароль, который должен применяться для аутентификации пользователя на сервере баз данных;

Например, строка соединения с базой данных “basa_user”, расположенной на MS SQL Server с именем “ITS-SERVER” для пользователя с именем “UserA” и паролем “123” будет выглядеть следующим образом:

this.sqlConnection1.ConnectionString = "user id=usera;data source=\"ITS-SERVER\";initial catalog=basa_user;pwd=123";

Строки соединения управляемого поставщика OLE DB

Строки соединения управляемого поставщика OLE DB похожи на строки соединения SQL Server. Все параметры строки соединения, за исключением параметра Provider (Поставщик), определяются специфическим поставщиком OLE DB. В качестве значений параметра Provider могут быть использованы такие значения как: SQLOLEDB.1 – для SQL Server; Microsoft.Jet.OLEDB.4.0 – для Microsoft Access; PostgreSQL – для базы данных PostgreSQL.

Приведем пример строки соединения для MS SQL Server:

this.oleDbConnection1.ConnectionString = @"User ID=usera;Data Source=""ITS-SERVER"";Initial Catalog=basa_user; Provider= ""SQLOLEDB.1"";pwd=123";

Строки соединения управляемого поставщика ODBC

Строки соединения управляемого поставщика ODBC немного отличаются от строк соединения SQL Server или OLE DB. Управляемый поставщик ODBC поддерживает два различных метода создания строки соединения:

  • создание строки соединения на основе имени источника данных (Data Source Name DSN);
  • использование динамических строк соединения.

Недостаток использования DSN заключается в том, что каждый компьютер должен либо быть специально настроенным, либо иметь доступ к DSN-файлам. В частности, это бывает проблематичным в таких системах, как кластер Web-серверов. С другой стороны, использование DSN позволяет сохранить определенный контроль над строками соединения. Так, если меняется местоположение сервера или аутентификационная информация, разработчику придется изменить всего лишь атрибуты DSN, а не программный код. Атрибуты DSN можно использовать также для динамического генерирования информации о соединении. В этом случае параметры строки соединения, такие как DRIVER и SERVER, можно указать непосредственно в строке соединения, а не прятать их в атрибуте DSN.

Например, строка соединения на основе имени источника данных может выглядеть так:

this.odbcConnection1.ConnectionString = "UID=usera; DSN=stud; PWD=123";

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

this.odbcConnection2.ConnectionString = "UID=usera;DRIVER=SQL Server;PWD=123;SERVER=ITS-SERVER";

Открытие и закрытие соединения

Объекты Connection имеют два базовых метода для открытия и закрытия соединения (Open и Close). Пример использования данных методов приведен в листинге 3.

Листинг 3.

private void Form1_Load(object sender, System.EventArgs e)

{

try

{

}

this.sqlConnection1.Open(); MessageBox.Show("Успешное SQL соединение"); this.sqlConnection1.Close();

catch(Exception ex)

{

MessageBox.Show("Нет SQL соединения"+ex.Message);

}

try

{

this.oleDbConnection1.Open(); MessageBox.Show("Успешное OLE DB соединение"); this.oleDbConnection1.Close();

}

catch(Exception ex)

{

MessageBox.Show("Нет OLE DB соединения"+ex.Message);

}

try

{

this.odbcConnection1.Open(); MessageBox.Show("Успешное ODBC1 соединение"); this.odbcConnection1.Close();

}

catch(Exception ex)

{

MessageBox.Show("Нет ODBC1 соединения"+ex.Message);

}

try

{

this.odbcConnection2.Open(); MessageBox.Show("Успешное ODBC2 соединение"); this.odbcConnection2.Close();

}

catch(Exception ex)

{

MessageBox.Show("Нет ODBC2 соединения"+ex.Message);

}

}

5. Отсоединенный набор данных Dataset

В основе технологии ADO.NET лежит пространство имен System.Data. Оно содержит наборы классов, интерфесов и других компонентов, представляющих собственно обрабатываемые пользователем данные.

Структура объекта DataSet

Объект DataSet состоит из нескольких связанных друг с другом структур данных. Концептуально он представляет собой полный набор реляционной информации. Внутри объекта Dataset могут храниться пять объектов:

  • DataTable – набор данных, организованный в столбцы и строки;
  • DataRow – коллекция данных, которая представляет собой одну строку таблицы DataTable, объект DataRow является фактическим хранилищем данных;
  • DataColumn – коллекция правил, описывающая данные, которые можно хранить в объектах DataRow;
  • Constraint – данный объект используется для определения бизнес – правил объекта DataTable и задает допустимость хранения определенных данных в объекте DataTable;
  • DataRelation –описание связей между объектами DataTable.

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

Работа с объектом DataSet

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

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

DataTable. Объекты DataRow доступны из объекта DataTable через свойство Rows.

Заполнение объекта DataSet

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

  • с помощью объекта DataAdapter (при этом информация, как правило, извлекается из базы данных);
  • на основе XML-документа;
  • программным путем.

Выборка строки

Пусть в наборе данных с именем ds1 существует две таблицы с именами «Таб_студ» и «Таб_гр». В таблицы «Таб_студ» и «Таб_гр» с помощью объекта DataAdapter внесены записи таблиц Студент и Группа, расположенных на сервере базы данных. Пример выборки данных из объекта DataSet по номеру строки в текстовые поля представлен в листинге 4.

Листинг 4

int n=10;

DataRow r=this.ds1.Tables["Таб_студ"].Rows[n]; textBox1.Text=r["номер_студента"].ToString(); textBox2.Text=r["фамилия"].ToString(); textBox3.Text=r["группа"].ToString(); textBox4.Text=r["стипендия"].ToString(); textBox5.Text=r["дата"].ToString();

Добавление строки

Для создания новой строки можно использовать соответствующие методы (NewRow() и Add()) объекта DataTable. Следует отметить, что метод NewRow() сам по себе не добавляет строку в объект DataTable. Для этого необходимо вызвать метод Add(), передав ему в качестве параметра объект строки. Пример добавления строки в объект DataSet приведен в листинге 5.

Листинг 5

// вставить запись

DataRow r=ds1.Tables["Таб_студ"].NewRow(); r["номер_студента"]=1; r["фамилия"]="Семенов";

r["группа"]="ИС-41"; r["стипендия"]=1500; r["дата"]="6.10.21";

ds1.Tables["Таб_студ"].Rows.Add(r);

Удаление строки

При использовании отсоединенных данных к удалению строки из коллекции предъявляется особое требование: строка должна продолжать существовать до тех пор, пока хранилище данных не будет обновлено с помощью объекта DataSet. Удаление строки может быть осуществлено, например, с помощью метода Delete() объекта DataRow. В этом случае строка удаляет себя сама. Пример удаления строки представлен в листинге 6.

Листинг 6

int n=10;

DataRow r=this.ds1.Tables["Таб_студ"].Rows[n]; r.Delete();

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

Листинг 6.1

int n=10;

DataRow r=this.ds1.Tables["Таб_студ"].Rows[n];

string s=MessageBox.Show("Удалить запись?","Внимание!", System.Windows.Forms.MessageBoxButtons.OKCancel).ToString();

if (s=="OK")

{

try

{

}

r.Delete();

MessageBox.Show("Запись удалена!");

catch(Exception ex)

{

MessageBox.Show("Запись не удалена! "+ex.Message);

}

}

Изменение строки

Индексаторы класса DataRow позволяют установить новые значения столбцов строки, например:

r["фамилия"]=”Иванов”;

Однако при определении нового значения столбца объект DataRow сгенерирует исключение в том случае, если это значение будет конфликтовать со свойством DataType объекта DataColumn.

Существуют ситуации, в которых изменения в конкретную строку DataRow необходимо вносить параллельно. Обычно это делается тогда, когда одно изменение приводит к нарушению некоторого ограничения или когда необходимо иметь возможность отмены изменений перед их внесением в базу данных. В этом случае используются методы BeginEdit(), EndEdit() и CancelEdit() класса DataRow.

Как только будет вызван метод BeginEdit(), изменения перестанут отражаться на объекте DataRow до тех пор, пока не будет вызван метод EndEdit(). Если внесенные изменения окажутся ошибочными, можно вызвать метод CancelEdit(), который вернет строку в первоначальное состояние (состояние, в котором она находилась до вызова метода BeginEdit()). Пример изменения строки показан в листинге 2.7.

Листинг 7

//изменить запись int n=10;

DataRow r=this.ds1.Tables["Таб_студ"].Rows[n]; r.BeginEdit();

r["фамилия"]=”Иванов”;

r["группа"]=”ИС-11”; r["стипендия"]=2000; r["дата"]=”1.01.21”;

r.EndEdit();

Определение схемы объекта DataSet

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

Пример создания первичных ключей приведен в листинге 8. Здесь же показано создание отношения между родительской таблицей «Таб_гр» и дочерней таблицей «Таб_студ» по полю «группа».

Листинг 8

DataTable gr=ds.Tables["Таб_гр"]; DataTable st=ds.Tables["Таб_студ"];

gr.PrimaryKey=new DataColumn[]{gr.Columns["группа"]}; st.PrimaryKey=new DataColumn[]{st.Columns["номер_студента"]}; ds.Relations.Add("gr_st",ds.Tables["Таб_гр"].Columns["группа"]

,ds.Tables["Таб_студ"].Columns["группа"],true);

Первым параметром в методе Relations.Add() является имя отношения. Следующие два параметра представляют собой имена столбцов родительской и дочерней таблиц соответственно. Нак онец, последний параметр является булевой переменной, определяющей необходимость создания ограничений для этого отношения. При создании ограничения объекту DataSet сообщается, что каждое значение уникального ключа родительской таблицы должно присутствовать в дочернем объекте DataTable.

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

Таблица 1 – Методы перехода между таблицами

Название

метода

Тип возвращаемого

значения

Назначение
GetChildRows() массив типа DataRow Переход от родительской таблицы к подчиненной
GetParentRow() DataRow Переход от подчиненной таблицы к родительской
GetParentRows() массив типа DataRow Переход от подчиненной таблицы к родительской

6. Объект DataAdapter

Объект DataAdapter – один из важнейших объектов ADO.NET. Этот объект является посредником между источником данных и набором данных DataSet. В приложениях DataAdapter обеспечивает считывание информации из базы данных и пересылку ее в DataSet, возврат изменений, сделанных пользователем, в исходную базу данных. Задача модификации данных решается через использование команд на основе SQL-запросов и хранимых процедур. В Visual Studio имеется несколько типов адаптеров данных:

  • System.Data.Odbc.OdbcDataAdapter – используется для работы с ODBC –источниками данных;
  • System.Data.OleDb.OleDbDataAdapter – используется для работы с любым источником данных, доступным через OLE DB – провайдера;
  • System.Data.SqlClient.SqlDataAdapter – используется для работы с данными, хранящимися в SQL Server.

Каждый объект DataAdapter обеспечивает обмен данными между одной таблицей источника данных (базы данных) и одним объектом DataTable в наборе данных DataSet. Если DataSet содержит несколько таблиц (объектов DataTable), то необходимо иметь и несколько адаптеров данных.

Когда требуется заполнить данными таблицу в DataSet, вызывается соответствующий метод (Fill) объекта DataAdapter, который выполняет SQL-запрос или хранимую процедуру. Точно также, когда необходимо модифицировать базу данных, вызывается соответствующий метод (Update) объекта DataAdapter, который вызывает на исполнение соответствующий SQL-запрос или хранимую процедуру. В результате этого все изменения, внесенные пользователем в таблицы набора данных, будут возвращены в соответствующие таблицы базы данных.

Заполнение объекта DataSet

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

Объект DataAdapter является связующим звеном между объектом DataSet и хранилищем данных. С помощью объекта DataAdapter можно заполнить объект DataSet информацией из хранилища данных, а также обновлять хранилище данных на основе объекта DataSet. Фактически объект DataSet представляет собой метакоманду. Так, объект DataAdapter состоит из четырех объектов Command, каждый из которых выполняет определенную задачу по модификации данных в базе данных: SelectCommand, InsertCommand, UpdateCommand и DeleteCommand. Объект DataAdapter используется каждый раз, когда объект DataSet нуждается в непосредственном взаимодействии с источником данных.

Один из наиболее важных моментов заключается в том, что объект DataAdapter содержит объекты Command для каждой из основных операций, производимых над базами данных. Основное предназначение объекта DataAdapter заключается в формировании «моста» между объектом DataSet и базой данных.

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

Для заполнения DataSet информацией из базы данных необходимо:

  • создать экземпляр класса DataAdapter, который является специализированным классом, выполняющим функцию контейнера для команд, осуществляющих чтение и запись информации в базу данных;
  • создать экземпляр класса DataSet; только что созданный объект DataSet является пустым;
  • вызвать метод Fill() объекта DataAdapter для заполнения объекта DataSet данными из запроса, определенного в объекте Command.

Пример заполнения набора данных ds1 показан в листинге 9.

Листинг 9

System.Data.Odbc.OdbcDataAdapter da1; System.Data.Odbc.OdbcDataAdapter da2; System.Data.DataSet ds1;

da1=new System.Data.Odbc.OdbcDataAdapter("SELECT * FROM студент",con1);

da2=new System.Data.Odbc.OdbcDataAdapter("SELECT группа FROM группа",con1);

da1.Fill(ds1,"Таб_студ"); //заполнение данными набора данных da2.Fill(ds1,"Таб_гр");

da1.Update(ds1,"Таб_студ"); //сохранение изменений в БД da2.Update(ds1,"Таб_гр");

Адаптеры данных и объекты Command

Используя объект DataAdapter, можно читать, добавлять, модифицировать и удалять записи в источнике данных. Чтобы определить, как каждая из этих операций должна выполняться, DataAdapet поддерживает следующие четыре свойства:

  • SelectCommand – описание команды, которая обеспечивает выборку нужной информации из базы данных;
  • InsertCommand – описание команды, которая обеспечивает добавление записей в базу данных;
  • UpdateCommand – описание команды, которая обеспечивает обновление записей в базе данных;
  • DeleteCommand – описание команды, которая обеспечивает удаление записей из базы данных.

Каждая из этих команд реализована в виде SQL-запроса или хранимой процедуры, как показано в листинге 10.

Листинг 10

System.Data.Odbc.OdbcConnection con1; System.Data.Odbc.OdbcDataAdapter da1; System.Data.Odbc.OdbcCommand sellCmd; System.Data.Odbc.OdbcCommand delCmd; System.Data.Odbc.OdbcCommand insCmd; System.Data.Odbc.OdbcCommand updCmd; System.Data.DataSet ds1;

string selQry,delQry,insQry,updQry;

con1=new System.Data.Odbc.OdbcConnection(); da1=new System.Data.Odbc.OdbcDataAdapter(); ds1=new DataSet();

// команда выборки

selCmd=new System.Data.Odbc.OdbcCommand();

selQry="SELECT номер_студента, фамилия, группа, стипендия, дата FROM студент";

selCmd.Connection=con1; selCmd.CommandText=selQry; da1.SelectCommand=selCmd;

// команда выборки

selCmd=new System.Data.Odbc.OdbcCommand();

selQry="SELECT номер_студента, фамилия, группа, стипендия,

дата FROM студент WHERE группа=?";

selCmd.Connection=con1; selCmd.CommandText=selQry;

selCmd.Parameters.Add("p0",System.Data.Odbc.OdbcType.Char,50,

"группа");

selCmd.Parameters["p0"].Value="ИС-11"; da1.SelectCommand=selCmd;

// команда удаления

delCmd=new System.Data.Odbc.OdbcCommand(); delQry="DELETE FROM студент WHERE номер_студента=?"; delCmd.Connection=con1;

delCmd.CommandText=delQry; delCmd.Parameters.Add("p1",System.Data.Odbc.OdbcType.Int,4,

"номер_студента");

da1.DeleteCommand=delCmd;

// команда вставки

insCmd=new System.Data.Odbc.OdbcCommand(); insQry="INSERT INTO студент VALUES(?,?,?,?,?)";

insCmd.Connection=con1; insCmd.CommandText=insQry;

insCmd.Parameters.Add("p2",System.Data.Odbc.OdbcType.Int,4,

"номер_студента"); insCmd.Parameters.Add("p3",System.Data.Odbc.OdbcType.VarChar,

50,"фамилия"); insCmd.Parameters.Add("p4",System.Data.Odbc.OdbcType.VarChar,

50,"группа"); insCmd.Parameters.Add("p5",System.Data.Odbc.OdbcType.Int,4,

"стипендия"); insCmd.Parameters.Add("p6",System.Data.Odbc.OdbcType.DateTime,

4,"дата");

da1.InsertCommand=insCmd;

// команда изменения

updCmd=new System.Data.Odbc.OdbcCommand();

updQry="UPDATE студент SET фамилия=?, группа=?, стипендия=?, дата=? WHERE номер_студента=?";

updCmd.Connection=con1; updCmd.CommandText=updQry;

updCmd.Parameters.Add("p7",System.Data.Odbc.OdbcType.VarChar, 50,"фамилия");

updCmd.Parameters.Add("p8",System.Data.Odbc.OdbcType.VarChar,

50,"группа"); updCmd.Parameters.Add("p9",System.Data.Odbc.OdbcType.Int,4,

"стипендия"); updCmd.Parameters.Add("p10",System.Data.Odbc.OdbcType.DateTime

,4,"дата"); updCmd.Parameters.Add("p11",System.Data.Odbc.OdbcType.Int,4,

"номер_студента");

da1.UpdateCommand=updCmd;

7. Доступ к данным в Windows-формах

Привязка данных в ADO.NET

Механизм привязки данных в ADO.NET должен поддерживать управляемые данными настольные (Windows) приложения и Webприложения. С этой целью механизм привязки данных позволяет связать с источником данных любое свойство элемента управления, предназначенное для записи.

Windows-формы поддерживают два типа привязки данных. Для элементов управления, содержащих единственное значение (таких, как TextBox, CheckBox и т.п.), Windows–формы поддерживают простую привязку данных, а для элементов управления, содержащих несколько значений (таких, как ListBox, ComboBox, DataGrid и т.п.) – сложную привязку данных.

Простая привязка данных

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

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

В приложениях ADO.NET свойство DataSource обычно указывает на объект DataTable или DataView, а свойство DataMember — на столбец, данные которого необходимо извлечь.

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

{Элемент_управления}.DataBindings.Add(“{Свойство}“,

{DataSource},”{DataMember}”);

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

textBox1.DataBindings.Add("Text",ds1.Tables["Таб_студ"],"фамилия" );

Здесь в тестовое поле будет помещено значение поля «фамилия» первой записи таблицы «Таб_студ» набора данных ds1.

Сложная привязка данных к элементам управления

Для привязки данных к списку необходимо определить значения трех свойств объекта ListBox:

  • DataSource – экземпляр класса, реализующего интерфейс List (например, класс DataTable);
  • DisplayMember – свойство объекта – источника (DataSource), которое будет отображаться в элементе управления;
  • ValueMember – идентификатор, хранящийся в элементе управления и определяющий строку объекта – источника, к которой происходит обращение.

Аналогично формируется привязка данных к объекту ComboBox.

Значение поля, указанного в свойстве элемента управления ValueMember, соответствующее выбранному пользователем элементу, хранится в свойстве SelectValue элемента управления ListBox или ComboBox. Оно отличается от значения свойства SelectItem, которое хранит значение поля, указанного в свойстве DisplayMember. В листингах 11 и 12 приведены примеры привязки данных к списку и раскрывающемуся списку соответственно.

Листинг 11

this.listBox1.DataSource=ds1.Tables["Таб_студ"]; this.listBox1.DisplayMember="фамилия"; this.listBox1.ValueMember="номер_студента";

Листинг 12

this.comboBox1.DataSource=ds1.Tables["Таб_гр"]; this.comboBox1.DisplayMember="группа"; this.comboBox1.ValueMember="группа";

Привязка данных к элементу управления DataGrid

Привязка данных к элементу управления DataGrid отличается от привязки данных к остальным элементам управления Windowsформы. При простой привязке данных элементу управления ставится в соответствие атомарное значение, при сложной привязке данных – список значений. Особенность привязки данных к элементу управления DataGrid заключается в том, что разработчик имеет дело с более обширным множеством данных. Элемент управления DataGrid позволяет установить значение свойства DataSource равным фактическому источнику данных (такому, как

объект DataTable, DataSet, DataView и т.п.). Если изменить значение свойства DataSource на этапе выполнения, оно вступит в силу только после перезагрузки элемента управления DataGrid, осуществляемой с помощью метода DataDrid.SetDataBinding(). В качестве параметров метод SetDataBinding() принимает новые значения свойств DataSoiurce и DataMember. Значение свойства DataMember – это именованная сущность, с которой будет связан элемент управления DataGrid. Привязка данных к элементу управления DataGrid осуществляется с помощью следующего оператора:

this.dataGrid1.SetDataBinding(ds1,"Таб_студ");

Аутентификация пользователя в Windows-приложении

Для ввода имени пользователя, пароля, имени источника данных (DSN) и других параметров соединения с базой данных в Windows-приложении создадим дополнительную форму в существующем проекте работы с БД. При запуске Windows-приложения сначала появится форма для ввода параметров соединения с БД, как показано на рисунок 14. Для создания новой формы в текущем проекте достаточно поставить курсор на имя проекта в окне Solution Explorer, нажать правую кнопку мыши и выбрать пункт: Add / Add Windows Form. В новой форме разместим метки, текстовые поля и кнопку, как показано на рисунке 15.

ввод параметров соединения БД

Рисунок 14 – Примерный видформы для ввода параметров соединения БД

элементы формы для ввода параметров соединения с БД

Рисунок 15 – Структурные элементы формы для ввода параметров соединения с БД

В программный код внесем описание переменных для сохранения имени источника данных и пароля:

public string dsn,pw;

и напишем обработчик события нажатия кнопки (листинг 13):

Листинг 13

private void button1_Click(object sender, System.EventArgs e)

{

dsn=this.textBox1.Text; pw=this.textBox2.Text; this.Dispose();

}

Для передачи параметров соединения в существующую форму

(Form1) внесем изменения:

1. Опишем переменные для аутентификации пользователя:

string v_dsn,v_pw;

2. В конструктор формы добавим параметры:

public Form1(string dsn,string pw) { InitializeComponent(dsn,pw);

}

3. В блок инициализации также добавим параметры:

private void InitializeComponent(string dsn, string pw){ v_dsn=dsn;

v_pw=pw;

}

4. В методе Main сначала откроем форму для аутентификации пользователя, а затем введенные в эту форму параметры соединения с БД передадим в основную форму:

static void Main() {

Form2 f2=new Form2(); Application.Run(f2);

Application.Run(new Form1(f2.dsn,f2.pw));

}

5. Параметры аутентификации используем при открытии

формы для создания строки соединения с БД:

private void Form1_Load(object sender, System.EventArgs e){

con1.ConnectionString= "PWD="+v_pw+";DSN="+v_dsn;

}

8. Этапы разработки приложения, использующего ADO.NET

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

  1. Создать соединение с базой данных. Соединение описывается как объект класса DbConnection, в нем необходимо задать информацию о соединении – так называемую строку соединения.
  2. Создать объект класса DbCommand. В этом объекте следует задать команду для выполнения в СУБД и ее параметры (при необходимости). Команда может быть разных типов: SQL-запросом либо хранимой процедурой.
  3. Созданная команда может не возвращать никаких данных, а, например, выполнять операции обновления или удаления данных. В этом случае команду следует просто запустить на исполнение одним из методов Execute().
  4. Если же созданная команда относится к одной из разновидностей команды SELECT, то она будет возвращать в качестве результата выборку данных. Тогда нужно определиться с тем, куда эти данные следует поместить. Если данные понадобится в дальнешем использовать без поддержания связи с источником, то создается объект DataAdapter, и с его помощью данные сохраняются в DataSet или в DataTable. Если же необходим только однократный, но очень быстрый и не требующий лишней памяти просмотр данных в одном направлении, используется объект DataReader (работа с ним не рассматривалась продробно), который требует постоянного соединения в источником, позволяет двигаться по набору данных только вперед и только на чтение, но действия эти выполняются значительно быстрее, чем при использовании DataSet.
  5. Полученный объект DataSet или DataReader, заполненный результатами выборки из хранилища, следует задать как источник данных какого-либо элемента управления формы или страницы (например, DataGridView), либо же вывести информацию на экран другим способом.

9. Практическое задание № 1. Разработка бизнес-приложений на основе Windows-форм

Задание: База данных из двух связанных таблиц «Телефонная книжка» создана в Microsoft Access. Реализовать с помощью управляемого провайдера OLE DB доступ к этой базе данных.

9.1. Создание базы данных в Microsoft Access

База данных Organizer состоит из двух связанных таблиц:

Contacts и Phones. Их структура приведена в таблице 1.

Таблица 1 – Структура таблиц базы данных

Название поля Тип поля Размер поля Примечание Назначение поля
Таблица Contacts
Id Счетчик Длинное целое Ключевое поле Номер записи
Fam Текстовый 20 Фамилия
Name Текстовый 20 Имя
Таблица Phones
Phone Id Счетчик Длинное целое Ключевое поле Номер записи
ContactId Числовой Длинное целое Идентификатор

контакта

Phone Текстовый 20 Телефон

Таблицы связываются между собой по номеру записи в таблице контактов. Данная связь показана на рисунке 1.

Связывание таблиц БД

Рисунок 1 – Связывание таблиц БД

9.2. Доступ к данным с помощью управляемого провайдера OLE DB

Прежде всего, следует поместить на форму компонент oleDbConnection и настроить его свойство ConnectionString, выбрав пункт <Новое подключение…>. Открывается диалоговое окно, представленное на рисунке 2. С помощью кнопки Изменить… в правом верхнем углу окна надо установить вид источника данных (Data source) как Файл базы данных Microsoft Access. Далее с помощью кнопки Обзор… в строке Имя файла базы данных нужно указать путь и имя файла с базой данных. Кнопка Проверить подключение позволяет проверить правильность установления соединения.

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

Примечание. После нажатия кнопки Next мастером настройки будет предложен вариант работы с базой данных, при котором при каждом запуске программе будет создаваться копия базы данных в папке приложения. На данном шаге следует нажать кнопку «NO»!

Установление соединения с базой данных

Рисунок 2 – Установление соединения с базой данных

Конфигурирование адаптера данных

Рисунок 3 – Конфигурирование адаптера данных

Далее мастер предлагает задать команды SQL для загрузки данных в приложение. Кнопка Query Builder… данного окна предлагает визуальный метод построения данной команды. В открывшемся окне Add Table следует добавить в Query Builder главную таблицу БД – таблицу Contacts, а затем отметить в этой таблице все ее столбцы (рисунок 4).

Аналогичным образом добавляется адаптер и для второй таблицы БД – таблицы Phones. Процесс создания данного адаптера аналогичен адаптеру таблицы Contacts (в окно Построитель запросов, естественно, нужно добавлять таблицу Phones).

Следующим шагом является формирование набора данных –

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

команда SQL для доступа к таблице

Рисунок 4 – Построение команды SQL для доступа к таблице Contacts

Добавление в проект набора данных

Рисунок 5 – Добавление в проект набора данных (DataSet)

После этого в сгенерированный набор данных можно добавлять другие необходимые элементы: остальные таблицы БД, связи между ними, запросы и т. п. Для этого нужно в окне Обозреватель решений выбрать (дважды щелкнуть мышью) элемент DataSet1.xsd. Так, для нашего приложения в набор данных следует добавить отношение – связь между таблицами БД: в окне DataSet1.xsd в контекстном меню выбирается пункт ДобавитьОтношение… Открывается диалоговое окно редактирования отношения, где можно задать имя создаваемого отношения, родительскую и подчиненную таблицы, ключевые поля, а также режимы автоматических изменений дочерней таблицы при изменении родительской таблицы. Для разрабатываемого приложения создается отношение с именем FK_Contacts_Phones. Его настройки приведены на рисунке 6.

9.3. Визуальное проектирование диалогового окна

Внешний вид работающего приложения приведен на рисунке 7.

Для отображения данных, полученных из базы, в программе будут использоваться компоненты DataGridView, по одному для каждой таблицы базы данных. Имена их по умолчанию устанавливаются как dataGridView1 и dataGridView2.

Создание отношения между таблицами

Рисунок 6 – Создание отношения между таблицами

Главная форма приложения

Рисунок 7 – Главная форма приложения

Компонентам-таблицам dataGridView1 и dataGridView2 нужно установить свойство DataSource, задающее источник данных для отображения в таблице, в «contactsBindingSource» и «phonesBindingSource» соответственно. Кроме того, при необходимости можно отредактировать заголовки и другие настройки столбцов таблиц (свойство Columns). В данном приложении в столбце ContactId компонента dataGridView2 для наглядности отображается не числовой идентификатор человека, которому принадлежит телефон, а его фамилия, причем для отображения используется тип столбца «DataGridViewComboBoxColumn» (свойство ColumnType). Настройка остальных свойств данного столбца приведена на рисунке 8.

Настройка столбца ContactId

Рисунок 8 – Настройка столбца ContactId компонента data-GridView2

9.4. Проектирование программного кода

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

Load:

private void Form1_Load(object sender, EventArgs e)

{

oleDbConnection1.Open(); //открыть соединение

//заполнить таблицы в объекте DataSet oleDbDataAdapter1.Fill(dataSet1.Contacts); oleDbDataAdapter2.Fill(dataSet1.Phones);

}

При закрытии формы необходимо отключить соединение с базой данных:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)

{ oleDbConnection1.Close(); //закрыть соединение

}

10. Практическое задание № 2. Доступ к данным в Windows-формах по технологии ADO.NET

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

10.1. Визуальное проектирование диалогового окна

Внешний вид работающего приложения приведен на рисунке 1.

Настройки таблиц dataGridView1 и dataGridView1 приведены в описании предыдущей лабораторной работы.

Текстовые поля FamTextBox и NameTextBox используются для добавления новой записи или редактирования записи, выделенной в таблице. Для удобства редактирования при выделении строки в таблице данные об имени и фамилии человека копируются в соответствующие текстовые поля.

Главная форма приложения

Рисунок 1 – Главная форма приложения

10.2. Проектирование программного кода

Обновление содержимого главного окна приложения

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

Для обновления содержимого формы следует добавить в класс формы метод UpdateContacts(). Тип возврата метода – void, вид доступа – private, параметров нет. Программный код метода следующий:

private void UpdateContacts()

{

//обновить содержимое таблиц базы данных oleDbDataAdapter2.Update(dataSet1); oleDbDataAdapter1.Update(dataSet1);

//очистить DataSet

dataSet1.Clear();

//заполнить таблицы в объекте DataSet oleDbDataAdapter1.Fill(dataSet1.Contacts); oleDbDataAdapter2.Fill(dataSet1.Phones);

//очистить содерижмое списка фамилий FamComboBox.Items.Clear();

//заполнить список фамилий значениями из таблицы foreach (DataRow row in dataSet1.Contacts.Rows)

FamComboBox.Items.Add(row["Fam"]);

//в списке фамилий - ни одна фамилия не выделена FamComboBox.Text = "";

}

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

Выпадающий список FamComboBox содержит список фамилий людей. При изменении содержимого базы данных его также нужно обновить. Сначала его строки методом Clear() очищаются, а затем заполняются заново фамилиями из обновленной таблицы (метод Add()). Цикл foreach используется для просмотра всех записей таблицы.

Так как обновление данных должно выполняться и при запуске программы, то содержимое обработчика события FormLoad изменится следующим образом:

private void Form1_Load(object sender, EventArgs e)

{

oleDbConnection1.Open(); //открыть соединение UpdateContacts(); //обновить главное окно

}

Добавление нового контакта

Добавление нового контакта происходит тогда, когда пользователь щелкает кнопку Добавить, расположенную в верхней части главного окна приложения (рисунок 3). Предварительно пользователь должен ввести имя и фамилию человека в поля Имя и Фамилия соответственно.

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

private void AddContactButton_Click(object sender, EventArgs

e)

{

//если текстовые поля не пусты,

if (FamTextBox.Text != "" && NameTextBox.Text != "")

{

//то создать новую запись в таблице Contacts, DataRow row = dataSet1.Contacts.NewRow();

//заполнить ее столбцы row["Fam"] = FamTextBox.Text; row["Name"] = NameTextBox.Text;

//и добавить запись в таблицу dataSet1.Contacts.Rows.Add(row);

//обновить содержимое главного окна UpdateContacts();

}

}

Здесь нужно убедиться в том, что пользователь ввел как имя, так и фамилию. Если поле FamTextBox или NameTextBox пустое, обработчик события завершает свою работу без выполнения других дополнительных действий. В том случае, когда пользователь ввел все необходимые данные, обработчик события создает новую строку в таблице Contacts. Для этого используется метод NewRow(). На следующем этапе введенные строки имени и фамилии записываются в соответствующие столбцы строки. Подготовленная таким способом строка добавляется в таблицу.

Далее обработчик события последовательно вызывает метод с именами UpdateContacts(), который предназначен для обновления содержимого формы приложения. Этот метод описан далее.

Добавление номера телефона

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

private void AddPhoneButton_Click(object sender, EventArgs e)

{

//если поле ввода телефона не пустое

//и выбрана какая-либо фамилия

if (PhoneTextBox.Text != "" && FamComboBox.Text != "")

{

try

{

//создать новую запись таблицы Phones DataRow row = dataSet1.Phones.NewRow();

//заполнить столбец "номер телефона" row["Phone"] = PhoneTextBox.Text;

//получить из выпадающего списка фамилию,

//для которой добавляется телефон

string fio = FamComboBox.SelectedItem.ToString();

//составить условие для поиска этого человека

//в таблице Contacts

string str = "Fam='" + fio + "'";

//получить id этого человека в таблице Contacts DataRow[] contacts =

dataSet1.Contacts.Select(str);

//заполнить столбец "ContactId" добавляемой

записи

row["ContactId"] = contacts[0]["id"];

//добавить запись в таблицу dataSet1.Phones.Rows.Add(row);

//обновить форму UpdateContacts();

}

catch (Exception)

{

}

}

}

Здесь вначале проверяется, что поле нового телефона PhoneTextBox не пустое и в выпадающем списке выбран человек, для которого будет добавляться телефон. Если это так, то путем вызова метода NewRow() в таблице Phones создается новая строка как объект класса DataRow.

Номер добавляемого телефона сохраняется в столбце Phone. Что же касается столбца ContactId, то в него нужно записать идентификатор строки таблицы Contacts (грубо говоря, порядковый номер человека, которому добавляется телефон). Для этого прежде всего нужно получить фамилию человека из списка FamComboBox с помощью свойства SelectedItem, затем найти этого человека по фамилии в таблице Contacts и получить его id. Поиск данных в таблице осуществляется с помощью метода Select(), где в качестве параметра выступает условие отбора данных. Это условие записывается в строку str и имеет вид: Fam = ‘<фамилия человека>’. Столбец ContactId новой записи заполняется найденным значением.

Заполненная строка добавляется в таблицу Phones методом Add().

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

Выбор записей в таблицах

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

Номер выбранной записи будет сохраняться в переменных: RowId для таблицы Contacts и PhoneId для таблицы Phones. Эти переменные следует добавить в класс главной формы либо визуальным способом, либо вручную следующим образом:

private int RowId; private int PhoneId;

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

private void dataGridView1_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)

{

try

{

//получить номер выделенной строки RowId = e.RowIndex;

//отобразить фамилию и имя выбранного человека

//в текстовых полях FamTextBox.Text =

dataSet1.Contacts.Rows[RowId]["Fam"].ToString(); NameTextBox.Text =

dataSet1.Contacts.Rows[RowId]["Name"].ToString();

}

catch (Exception)

{

}

}

Когда пользователь выбирает строку в таблице, этот обработчик вначале определяет индекс строки, которая стала выделенной в результате выполнения этой операции, и записывает его в переменную RowId. Здесь параметр e обработчика содержит параметры, описывающие характеристики нажатия мышью заголовка строки, в том числе и номер строки (свойство RowIndex). После получения номера выбранной строки выполняется копирование полей фамилии и имени человека из таблицы в соответствующие текстовые поля.

Код аналогичного обработчика для таблицы Phones выглядит следующим образом:

private void dataGridView2_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)

{

поле

try

{

//получить номер выделенной строки PhoneId = e.RowIndex;

//найти запись с полученным номером в DataSet

DataRow p = dataSet1.Phones.Rows[PhoneId];

//отобразить номер телефона этой записи в текстовом

PhoneTextBox.Text = p["Phone"].ToString();

//получить связанные записи из таблицы Contacts

списке

DataRow[] fams = p.GetParentRows(dataSet1.Relations

["FK_Contacts_Phones"]);

//выбрать фамилию человека (из текущей записи) в

FamComboBox.SelectedItem = fams[0]["Fam"].ToString();

}

catch (Exception)

{

}

}

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

Т.к. фамилии находятся в таблице Contacts, то нужно получить список записей, связанных с выбранной через отношение FK_Contacts_Phones (см. предыдущую лабораторную работу). Делается это с помощью метода GetParentRows(). Далее из полученных «родительских» записей извлекается поле фамилии (“Fam”), и данная фамилия становится выбранной в выпадающем списке (свойство SelectedItem).

Редактирование записей таблицы Contacts

Для того чтобы отредактировать имя или фамилию человека, нужно выделить требуемую строку в таблице DataGridView1, содержащей записи таблицы Contacts. После этого только что рассмотренный обработчик события RowHeaderMouseClick запишет имя и фамилию в текстовые поля редактирования Имя и Фамилия соответственно.

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

private void EditButton_Click(object sender, EventArgs e)

{

//если выбрана строка в таблице

if (dataGridView1.SelectedRows.Count != 0)

{

//получить содержимое выбранной строки DataRow row = dataSet1.Contacts.Rows[RowId];

//изменить фамилию и имя на введенные значения row["Fam"] = FamTextBox.Text;

row["Name"] = NameTextBox.Text;

//сохранить изменения и обновить содержимое формы UpdateContacts();

}

else

MessageBox.Show("Выберите строку для редактирования",

"Ошибка");

}

Здесь проверяется, выбрана ли строка для редактирования. Если нет, то выдается сообщение об ошибке и обработчик завершает работу. Иначе надо получть содержимое выбранной строки в объект типа DataRow. Далее значения полей Fam и Name этой строки заменяются на новые, введенные пользователем в текстовых полях, и вызывается метод UpdateContacts() для сохранения изменений в БД и обновления таблиц на форме.

Т.к. таблицы БД связаны между собой, и при связывании было указано каскадное изменение и удаление записей, то после сохранения изменений в БД эти изменения отобразятся и в таблице Phones.

Изменение номера телефона

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

private void EditPhoneButton_Click(object sender, EventArgs e)

{

//если выбрана запись для редактирования if (dataGridView2.SelectedRows.Count != 0)

{

try

{

//получить содержимое выбранной строки DataRow row = dataSet1.Phones.Rows[PhoneId];

//изменить номер телефона на введенное значение row["Phone"] = PhoneTextBox.Text;

//получить из выпадающего списка фамилию,

//для которой добавляется телефон

string fio = FamComboBox.SelectedItem.ToString();

//составить условие для поиска этого человека

//в таблице Contacts

string str = "Fam='" + fio + "'";

//получить id этого человека в таблице Contacts DataRow[] contacts =

dataSet1.Contacts.Select(str);

//заполнить столбец "ContactId" редактируемой записи row["ContactId"] = contacts[0]["id"];

}

else

}

catch (Exception)

{

}

//сохранить изменения и обновить содержимое формы UpdateContacts();

MessageBox.Show("Выберите строку для редактирования",

"Ошибка");

}

Начало обработчика идентично предыдущему: если выбрана строка для редактирования, то получить ее содержимое и изменение значение номера телефона.

Далее, если нужно изменить и человека, к которому этот номер телефона относится, то используется технология, описанная в пункте 5.2.3: поиск человека в таблице Contacts с помощью метода Select(), получение его порядкового номера (id) и заполнение этим значением поля ContactId таблицы Phones.

Удаление записей таблицы Contacts

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

private void DelContactButton_Click(object sender, EventArgs e)

{

//если выбрана запись для удаления

if (dataGridView1.SelectedRows.Count != 0)

{

if (MessageBox.Show("Вы действительно хотите удалить запись?", "Подтверждение", MessageBoxButtons.YesNo)

== DialogResult.Yes)

{

try

{

}

//удалить выбранную строку dataSet1.Contacts.Rows[RowId].Delete();

catch (Exception)

{

}

//обновить БД и ее содержимое на форме UpdateContacts();

}

}

else

MessageBox.Show("Выберите строку для удаления","Ошибка");

}

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

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

Удаление номера телефона

Для удаления номера телефона предназначена кнопка Удалить, расположенная возле списка телефонов. Далее приведен обработчик событий для этой кнопки:

private void DelPhoneButton_Click(object sender, EventArgs e)

{

//если выбрана запись для удаления

if (dataGridView2.SelectedRows.Count != 0)

{

if (MessageBox.Show("Вы действительно хотите удалить запись?", "Подтверждение", MessageBoxButtons.YesNo) ==

DialogResult.Yes)

{

try

{

}

//удалить выбранную строку dataSet1.Phones.Rows[PhoneId].Delete();

catch (Exception)

{

}

//обновить БД и ее содержимое на форме UpdateContacts();

}

}

else

MessageBox.Show("Выберите строку для удаления", "Ошибка");

}

Код обработчика идентичен предыдущему (естественно, работа идет с таблицей Phones и компонентом dataGridView2).

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

В данном приложении приведен пример фильтра, который позволяет просматривать номера телефона одного, выбранного человека. Фамилия этого человека выбирается в выпадающем списке FamComboBox. Режим фильтрации включается флажком FilterCheckBox. Код обработки щелчка по этому флажку приведен ниже:

private void FilterCheckBox_CheckedChanged(object sender, EventArgs e)

{

//если установлен флажок и выбрана фамилия

if (FilterCheckBox.Checked && FamComboBox.Text != "")

{

//получить выбранную фамилию

string fio = FamComboBox.SelectedItem.ToString();

//составить условие для поиска нужного человека

//в таблице Contacts

string str = "Fam='" + fio + "'";

//найти нужного человека в таблице Contacts DataRow[] contacts = dataSet1.Contacts.Select(str);

//составить условие для фильтра

str = "ContactId=" + contacts[0]["id"];

//применить фильтр phonesBindingSource.Filter = str;

}

else

}

//отменить фильтрацию phonesBindingSource.Filter = "";

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

Так как фильтрация будет задаваться для таблицы Phones, а поле «фамилия» находится в таблице Contacts, необходимо получить идентификатор (порядковый номер) требуемой фамилии, чтобы потом найти его в таблице телефонов. Получить выбранную фамилию из выпадающего списка можно методом SelectedItem, а отобрать запись с этой фамилией из таблицы Contacts – методом Select().

Далее из полученной записи выделяется значение поля id, на основе которого задается условие для фильтрации: ContactId = <значение поля id>. Сформированный фильтр применяется к компоненту phonesBindingSource с помощью свойства Filter. Этот компонент генерируется автоматически при добавлении адаптеров доступа к базе данных.

Если флажок FilterCheckBox не отмечен или в списке не выбрана фамилия, свойству Filter присваивается значение пустой строки, т. е. фильтрация отменяется.