Создание трехуровневого приложения (C#Builder)

Трубецкой Алексей
mailto: alextr438@yandex.ru

Краткое изложение идеологии трехуровневых приложений

Трехуровневое приложение включает следующие уровни:

  1. уровень представления
  2. уровень бизнес-логики
  3. уровень доступа к данным
Уровень представления обычно представляет собой какую-либо разновидность "тонкого" (thin) клиента, который только вызывает сервер, отображает данные и принимает ввод от пользователя.

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

Уровень доступа к данным обычно представляет собой собственно БД.

На практике эти три уровня, как правило, располагаются на разных машинах.

Трехуровневая модель (three-tier model) позволяет обеспечить работу гораздо большего числа клиентов, чем двухуровневая модель, состоящая только из БД и клиентского приложения ("толстого" (fat) клиента).
Трехуровневая модель является более гибкой, т.к. при изменении алгоритмов обработки данных часто достаточно изменить ПО на машине промежуточного звена, которая может обслуживать множество клиентских машин. Кроме того, мощные машины промежуточного звена могут выдерживать значительно большие нагрузки, чем клиентские машины. Введение промежуточного звена также облегчает синхронизацию доступа к БД.



Создание простого трехуровневого приложения для работы с Microsoft SQL Server

Среда, используемая для создания приложения - C#Builder.
Приведенная программа, конечно, не предназначена для практического использования и служит только в качестве примера создания трехуровневых приложений.

В данном примере клиентское приложение будет представлено в виде exe-файла, а уровни бизнес-логики и доступа к данным - в виде dll-библиотек. Все три проекта поместим в одну группу проектов:
  1. Уровень доступа к данным - база данных Microsoft SQL Server.
  2. Уровень бизнес-логики - библиотеки DataAccess.dll и BusinessLogic.dll
    Модуль BusinessLogic устанавливает соединение с сервером, запрашивает данные определенного типа и отправляет запрос на сохранение изменений, сделанных пользователем. Модуль DataAccess является реализацией класса DataAccess, наследующего класс DataSet. Объект класса DataAccess содержит данные, находящиеся в таблице Books.
  3. Клиентское приложение уровня представления
    Приложение, которое мы назовем Client, будет только отображать данные и посылать запрос на сохранение изменений, произведенных пользователем.

Схема взаимодействия между классами приложения:
взаимодействие классов


Пошаговое описание создания приложения:

1. Создаем группу проектов ProjectGroup1

2. Включаем в эту группу проектов 3 новых проекта: Client.exe (тип application), BusinessLogic.dll (тип class library) и DataAccess.dll (тип class library). Изменяем созданное по умолчанию пространство имен каждого проекта на ThreeTierApp. Таким образом, все три проекта будут находиться в одном пространстве имен.

3. Программа будет загружать данные из определенной таблицы, поэтому в качестве примера создадим очень простую таблицу Books (например, с помощью Enterprise Manager):
Таблица Books

Далее создаем соединение для этой таблицы в Data Explorer (View->Data Explorer) и перетаскиваем ее мышью на главное окно программы. При этом автоматически создаются компоненты bdpConnection1 и bdpDataAdapter1, которые можно использовать для генерации класса, наследующего DataSet, который будет содержать данные из таблицы Books. Для генерации класса, наследующего DataSet, нужно щелкнуть правой кнопкой по компоненту bdpAdapter1 и выбрать пункт меню "Generate Typed DataSet" (в окне диалога, появляющегося при генерации DataSet следует убрать галочку "add this dataset to the designer", а в качестве имени dataset указать DataAccess).

Получившийся класс DataAccess переместим в проект DataAccess.dll, т.е поместим этот класс вместо класса, созданного по умолчанию для модуля DataAccess, затем удалим из модуля Client файлы, связанные с dataset.

Теперь можно удалить компоненты bdpConnection1 и bdpDataAdapter1, т.к. для соединения с БД мы будем использовать объекты классов SglConnection и SqlDataAdapter. Конечно, можно было совсем не создавать компоненты bdpConnection1 и bdpDataAdapter1, но тогда пришлось бы вручную писать код доступа к таблице Books. При создании же компонентов bdpConnection1 и bdpDataAdapter1 (в результате перетаскивания таблицы из Data Explorer на главное окно) код доступа к таблице генерируется автоматически.

4. Создаем класс BusinessLogic, который будет содержать следующий код:
namespace ThreeTierApp {
///
/// Summary description for BusinessLogic.
///

public class BusinessLogic {
private System.Data.SqlClient.SqlDataAdapter sqlDataAdapter1;
private System.Data.SqlClient.SqlCommand sqlSelectCommand1;
private System.Data.SqlClient.SqlCommand sqlInsertCommand1;
private System.Data.SqlClient.SqlCommand sqlUpdateCommand1;
private System.Data.SqlClient.SqlCommand sqlDeleteCommand1;
private System.Data.SqlClient.SqlConnection sqlConnection1;
private DataAccess dataSet;
      public BusinessLogic() {
this.sqlDataAdapter1 = new System.Data.SqlClient.SqlDataAdapter();
this.sqlSelectCommand1 = new System.Data.SqlClient.SqlCommand();
this.sqlInsertCommand1 = new System.Data.SqlClient.SqlCommand();
this.sqlUpdateCommand1 = new System.Data.SqlClient.SqlCommand();
this.sqlDeleteCommand1 = new System.Data.SqlClient.SqlCommand();
this.sqlConnection1 = new System.Data.SqlClient.SqlConnection();
this.dataSet = new DataAccess();
((System.ComponentModel.ISupportInitialize)(this.dataSet)).BeginInit();
//
// sqlDataAdapter1
//
this.sqlDataAdapter1.DeleteCommand = this.sqlDeleteCommand1;
this.sqlDataAdapter1.InsertCommand = this.sqlInsertCommand1;
this.sqlDataAdapter1.SelectCommand = this.sqlSelectCommand1;
this.sqlDataAdapter1.TableMappings.AddRange(new System.Data.Common.DataTableMapping[]{
new System.Data.Common.DataTableMapping("Table", "Books",
new System.Data.Common.DataColumnMapping[] {
new System.Data.Common.DataColumnMapping("name", "name"),
new System.Data.Common.DataColumnMapping("author", "author"),
new System.Data.Common.DataColumnMapping("price", "price")})});
this.sqlDataAdapter1.UpdateCommand = this.sqlUpdateCommand1;
//
// sqlSelectCommand1
//
this.sqlSelectCommand1.CommandText = "SELECT name, author, price FROM dbo.Books";
this.sqlSelectCommand1.Connection = this.sqlConnection1;
//
// sqlInsertCommand1
//
this.sqlInsertCommand1.CommandText = "INSERT INTO dbo.Books(name, author, price)" +
"VALUES (@name, @author, @price);" +
"SELECT name, author, price FROM dbo.Books WHERE (name = @name)";
this.sqlInsertCommand1.Connection = this.sqlConnection1;
this.sqlInsertCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter("@name",
System.Data.SqlDbType.VarChar, 10, "name"));
this.sqlInsertCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter("@author",
System.Data.SqlDbType.VarChar, 10, "author"));
this.sqlInsertCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter("@price",
System.Data.SqlDbType.VarChar, 4, "price"));
//
// sqlUpdateCommand1
//
this.sqlUpdateCommand1.CommandText = @"UPDATE dbo.Books SET name = @name," +
"author = @author, price = @price WHERE (name = @Original_name) AND" +
"(author = @Original_author) AND (price = @Original_price OR" +
"@Original_price IS NULL AND price IS NULL); SELECT name, author, price" +
"FROM dbo.Books WHERE (name = @name)";
this.sqlUpdateCommand1.Connection = this.sqlConnection1;
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@name",
System.Data.SqlDbType.VarChar, 10, "name"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@author",
System.Data.SqlDbType.VarChar, 10, "author"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@price",
System.Data.SqlDbType.VarChar, 4, "price"));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_name",
System.Data.SqlDbType.VarChar, 10,
System.Data.ParameterDirection.Input, false,
((System.Byte)(0)), ((System.Byte)(0)), "name",
System.Data.DataRowVersion.Original, null));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_author",
System.Data.SqlDbType.VarChar, 10,
System.Data.ParameterDirection.Input, false,
((System.Byte)(0)), ((System.Byte)(0)), "author",
System.Data.DataRowVersion.Original, null));
this.sqlUpdateCommand1.Parameters.Add(
new System.Data.SqlClient.SqlParameter("@Original_price",
System.Data.SqlDbType.VarChar, 4,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "price", System.Data.DataRowVersion.Original,
null));
//
// sqlDeleteCommand1
//
this.sqlDeleteCommand1.CommandText = "DELETE FROM dbo.Books WHERE" +
(name = @Original_name) AND (author = @Original" +
"_author) AND (price = @Original_price OR @Or" +
"iginal_price IS NULL AND price IS NULL)";
this.sqlDeleteCommand1.Connection = this.sqlConnection1;
this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@Original_name", System.Data.SqlDbType.VarChar, 10,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "name", System.Data.DataRowVersion.Original,
null));
this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@Original_author", System.Data.SqlDbType.VarChar, 10,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "author", System.Data.DataRowVersion.Original,
null));
this.sqlDeleteCommand1.Parameters.Add(new System.Data.SqlClient.SqlParameter(
"@Original_price", System.Data.SqlDbType.VarChar, 4,
System.Data.ParameterDirection.Input, false, ((System.Byte)(0)),
((System.Byte)(0)), "price", System.Data.DataRowVersion.Original,
null));
//
// sqlConnection1
//
this.sqlConnection1.ConnectionString = "data source=localhost;initial catalog=Books;" +
"integrated security=SSPI;persist secur" +
"ity info=False;workstation id=MM-4;packet size=4096";
//
// dataSet
//
this.dataSet.DataSetName = "DataAccess";
this.dataSet.Locale = new System.Globalization.CultureInfo("ru-RU");
this.dataSet.Namespace = "http://www.tempuri.org/DataAccess.xsd";
((System.ComponentModel.ISupportInitialize)(this.dataSet)).EndInit();
}
      public DataAccess GetData() {
DataAccess data = new DataAccess();
sqlDataAdapter1.Fill(data);
return data;
}
      public DataAccess UpdateData(DataAccess dataChanges) {
if (dataChanges != null) {
sqlDataAdapter1.Update(dataChanges);
return dataChanges;
}
else {
return null;
}
}
}
}
Таким образом, класс BusinessLogic, размещенный в модуле BusinessLogic, содержит ссылку на экземпляр класса DataAccess.

5. В класс WinForm (модуль Client) также помещаем ссылку на объект класса DataAccess, который назовем dataSet Не забудьте проинициализировать ссылку, добавив в метод InitializeComponent() строку
this.dataSet = new SimpleDataAccess();
В список зависимостей модулей Client и BusinessLogic (Project Manager->References) добавляем библиотеку DataAccess.dll. А для модуля Client добавляем также ссылку на BusinessLogic.dll.

6. Создаем интерфейс для приложения Client: помещаем на главное окно приложения элемент DataGrid и две кнопки: Load и Save. Для элемента DataGrid в качестве источника данных (свойство DataSource) указываем dataSet.
Добавляем код, обрабатывающий события, возникающие при нажатии на кнопку Load или на кнопку Save:
private void btnLoad_Click(object sender, System.EventArgs e) {
BusinessLogic ds = new BusinessLogic();
dataSet.Merge(ds.GetData());
}
private void btnSave_Click(object sender, System.EventArgs e) {
if (dataSet.HasChanges()) {
BusinessLogic ds = new BusinessLogic();
DataAccess diffData = new DataAccess();
      diffData.Merge(dataSet.GetChanges());
diffData = ds.UpdateData(diffData);
dataSet.Merge(diffData);
}
}

Главное окно приложения будет выглядеть так:
Главное окно программы

Или после загрузки данных:
Главное окно после загрузки данных

Примечание: когда Вы нажимаете кнопку "Load", у Вас уже должен быть доступ к SQL серверу.

Скачать реализацию приложения


Опубликовал admin
31 Июл, Суббота 2004г.



Программирование для чайников.