Трехслойная архитектура в C# .NET
ОГЛАВЛЕНИЕ
Обзор
- Уровень в сравнении со слоем
- Компоненты проектирования трехуровневой/трехслойной архитектуры
- Демо: трехслойное приложение windows в C#.NET
1. Уровень в сравнении со слоем
1.1 Уровень: Уровень показывает физическое разделение компонентов, которые могут означать разные сборки, такие как DLL, EXE и т.д., на одном и том же сервере или нескольких серверах.
Как видно на рисунке выше, уровень данных не имеет контроля над уровнем представления, но есть промежуточный уровень, называемый бизнес-уровнем, несущий главную ответственность за передачу данных из уровня данных на уровень представления и добавляющий заданную бизнес-логику в данные.
Если выделять каждый уровень по его функциональности, то получится следующий вывод:
1.2 Слой: Слой показывает логическое разделение компонентов, такое как наличие отдельных пространств имен и классов для уровня доступа к базе данных, уровня бизнес-логики и уровня интерфейса пользователя.
2. Компоненты проектирования трехуровневой/трехслойной архитектуры
Уровень – это сумма всех физических компонентов. Можно выделить три уровня – уровень данных, бизнес-уровень и уровень представления.
- Уровень данных, по сути, является сервером, хранящим все данные приложения. Уровень данных содержит таблицы базы данных, файлы XML и другие средства хранения данных приложения.
- Бизнес-уровень работает как мост между уровнем данных и уровнем представления. Все данные проходят через бизнес-уровень перед их передачей уровню представления. Бизнес-уровень - сумма слоя бизнес-логики, слоя доступа к данным, объекта значения и других компонентов, используемых для добавления бизнес-логики.
- Уровень представления – уровень, на котором пользователи взаимодействуют с приложением. Уровень представления содержит общий код интерфейса пользователя, отделенный код (код, содержащийся в отдельном файле, что позволяет отделить разметку от поведения, которое реализовано в коде) и конструкторов, используемых для представления информации пользователю.
Рисунок выше – смесь трехуровневой и трехслойной архитектуры. Здесь четко видно разницу между уровнем и слоем. Так как компоненты не зависят друг от друга, они легко сопровождаются без изменения всего кода.
Этот подход действительно очень важен, когда несколько разработчиков работают над одним и тем же проектом, и некоторые модули нужно повторно использовать в другом проекте. В некотором смысле, можно распределить работу между разработчиками и сопровождать ее в дальнейшем без особых проблем.
Тестирование – очень важный вопрос для архитектуры, когда рассматривается написание тестовых примеров для проекта. Так как она похожа на модульную архитектуру, очень удобно тестировать каждый модуль и отслеживать ошибки без прохождения через весь код.
3. Демо: трехслойное приложение в C#.NET
Рассмотрим все модули один за другим, чтобы лучше разобраться в них.
dbConnection
Этот класс используется для выполнения действий в базе данных, таких как запросы к базе данных на выборку, обновление и удаление. Он проверяет, открыто подключение к базе данных или нет. Если подключение к базе данных не открыто, он открывает подключение и выполняет запрос к базе данных. Результаты запроса к базе данных получаются и передаются в таблицу данных в этом классе.
Этот класс берет значения параметров базы данных из файла app.config, поэтому он гибко управляет параметрами базы данных.
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
namespace ThreeLayerDemo.Core
{
public class dbConnection
{
private SqlDataAdapter myAdapter;
private SqlConnection conn;
/// <constructor>
/// инициализация подключения
/// </constructor>
public dbConnection()
{
myAdapter = new SqlDataAdapter();
conn = new SqlConnection(ConfigurationManager.ConnectionStrings
["dbConnectionString"].ConnectionString);
}
/// <method>
/// открытие подключения к базе данных, если оно было закрыто или разорвано
/// </method>
private SqlConnection openConnection()
{
if (conn.State == ConnectionState.Closed || conn.State ==
ConnectionState.Broken)
{
conn.Open();
}
return conn;
}
/// <method>
/// запрос на выборку
/// </method>
public DataTable executeSelectQuery(String _query, SqlParameter[] sqlParameter)
{
SqlCommand myCommand = new SqlCommand();
DataTable dataTable = new DataTable();
dataTable = null;
DataSet ds = new DataSet();
try
{
myCommand.Connection = openConnection();
myCommand.CommandText = _query;
myCommand.Parameters.AddRange(sqlParameter);
myCommand.ExecuteNonQuery();
myAdapter.SelectCommand = myCommand;
myAdapter.Fill(ds);
dataTable = ds.Tables[0];
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeSelectQuery - Query:
" + _query + " \nException: " + e.StackTrace.ToString());
return null;
}
finally
{
}
return dataTable;
}
/// <method>
/// запрос на вставку
/// </method>
public bool executeInsertQuery(String _query, SqlParameter[] sqlParameter)
{
SqlCommand myCommand = new SqlCommand();
try
{
myCommand.Connection = openConnection();
myCommand.CommandText = _query;
myCommand.Parameters.AddRange(sqlParameter);
myAdapter.InsertCommand = myCommand;
myCommand.ExecuteNonQuery();
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeInsertQuery - Query:
" + _query + " \nException: \n" + e.StackTrace.ToString());
return false;
}
finally
{
}
return true;
}
/// <method>
/// запрос на обновление
/// </method>
public bool executeUpdateQuery(String _query, SqlParameter[] sqlParameter)
{
SqlCommand myCommand = new SqlCommand();
try
{
myCommand.Connection = openConnection();
myCommand.CommandText = _query;
myCommand.Parameters.AddRange(sqlParameter);
myAdapter.UpdateCommand = myCommand;
myCommand.ExecuteNonQuery();
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeUpdateQuery - Query:
" + _query + " \nException: " + e.StackTrace.ToString());
return false;
}
finally
{
}
return true;
}
}
}
Слой доступа к базе данных
Слой доступа к базе данных (DAO) строит запрос на основе параметров, полученных от слоя бизнес-логики, и передает их классу dbConnection для выполнения. И происходит возврат результатов из класса dbConnection в слой бизнес-логики.
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace ThreeLayerDemo.Core
{
public class UserDAO
{
private dbConnection conn;
/// <constructor>
/// конструктор UserDAO
/// </constructor>
public UserDAO()
{
conn = new dbConnection();
}
/// <method>
/// Получение электронного письма пользователя по имени или фамилии и возврат таблицы данных
/// </method>
public DataTable searchByName(string _username)
{
string query = string.Format("select * from [t01_user]
where t01_firstname like @t01_firstname or t01_lastname
like @t01_lastname ");
SqlParameter[] sqlParameters = new SqlParameter[2];
sqlParameters[0] = new SqlParameter("@t01_firstname", SqlDbType.VarChar);
sqlParameters[0].Value = Convert.ToString(_username);
sqlParameters[1] = new SqlParameter("@t01_lastname", SqlDbType.VarChar);
sqlParameters[1].Value = Convert.ToString(_username);
return conn.executeSelectQuery(query, sqlParameters);
}
/// <method>
/// Получение электронного письма пользователя по идентификатору и возврат таблицы данных
/// </method>
public DataTable searchById(string _id)
{
string query = "select * from [t01_id] where t01_id = @t01_id";
SqlParameter[] sqlParameters = new SqlParameter[1];
sqlParameters[0] = new SqlParameter("@t01_id", SqlDbType.VarChar);
sqlParameters[0].Value = Convert.ToString(_id);
return conn.executeSelectQuery(query, sqlParameters);
}
}
}
Объект-значение
Объект-значение – это просто класс с содержимым методов GET и SET. Он используется для передачи данных от одного класса другому. Он непосредственно связан со слоем бизнес-логики и со слоем представления. Как видно на рисунке, объекты-значения устанавливаются в слое бизнес-логики и читаются из слоя представления.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ThreeLayerDemo.Core
{
public class UserVO
{
private int _idUser;
private string _firstname;
private string _lastname;
private string _email;
/// <constructor>
/// конструктор UserVO
/// </constructor>
public UserVO()
{
//
// Что нужно сделать: добавьте сюда логику конструктора
//
}
public int idUser
{
get
{
return _idUser;
}
set
{
_idUser = value;
}
}
public string firstname
{
get
{
return _firstname;
}
set
{
_firstname = value;
}
}
public string lastname
{
get
{
return _lastname;
}
set
{
_lastname = value;
}
}
public string email
{
get
{
return _email;
}
set
{
_email = value;
}
}
}
}
Слой бизнес-логики
Слой бизнес-логики (BUS) работает как мост между уровнем представления и DAO. Все значения пользователя, полученные от уровня представления, передаются в BUS. Результаты, полученные от DAO, - это внутрирядные данные в формате таблицы данных, но в BUS они преобразуются в объекты-значения (VO). Слой бизнес-логики (BUS) – это самый важный класс во всей архитектуре, так как он содержит всю бизнес-логику программы. Всякий раз, когда пользователь хочет обновить бизнес-логику программы, ему нужно обновить только этот класс.
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
namespace ThreeLayerDemo.Core
{
/// <summary>
/// краткое описание для UserBUS
/// </summary>
public class UserBUS
{
private UserDAO _userDAO;
/// <constructor>
/// конструктор UserBUS
/// </constructor>
public UserBUS()
{
_userDAO = new UserDAO();
}
/// <method>
/// Получение электронного письма пользователя по имени или фамилии и возврат VO
/// </method>
public UserVO getUserEmailByName(string name)
{
UserVO userVO = new UserVO();
DataTable dataTable = new DataTable();
dataTable = _userDAO.searchByName(name);
foreach (DataRow dr in dataTable.Rows)
{
userVO.idUser = Int32.Parse(dr["t01_id"].ToString());
userVO.firstname = dr["t01_firstname"].ToString();
userVO.lastname = dr["t01_lastname"].ToString();
userVO.email = dr["t01_email"].ToString();
}
return userVO;
}
/// <method>
/// Получение электронного письма пользователя по идентификатору и возврат таблицы данных
/// </method>
public UserVO getUserById(string _id)
{
UserVO userVO = new UserVO();
DataTable dataTable = new DataTable();
dataTable = _userDAO.searchById(_id);
foreach (DataRow dr in dataTable.Rows)
{
userVO.idUser = Int32.Parse(dr["t01_id"].ToString());
userVO.firstname = dr["t01_firstname"].ToString();
userVO.lastname = dr["t01_lastname"].ToString();
userVO.email = dr["t01_email"].ToString();
}
return userVO;
}
}
}
Слой представления
Слой представления - это единственный слой, непосредственно связанный с пользователем. В этом вопросе он действительно важен для целей маркетинга. Слой представления используется для получения данных от пользователя и передачи их слою бизнес-логики для дальнейшей обработки, и когда данные получены в объекте-значении, он отвечает за представление объекта-значения в надлежащей форме, которая понятна пользователю.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ThreeLayerDemo.Core;
namespace ThreeLayerDemo
{
public partial class frmLogin : Form
{
private UserBUS _userBUS;
public frmLogin()
{
InitializeComponent();
_userBUS = new UserBUS();
}
private void btnSearch_Click(object sender, EventArgs e)
{
UserVO _userVO = new UserVO();
_userVO = _userBUS.getUserEmailByName(txtUsername.Text);
if (_userVO.email == null)
MessageBox.Show("No Match Found!", "Not Found",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
else
MessageBox.Show(_userVO.email ,"Result",
MessageBoxButtons.OK,MessageBoxIcon.Information);
}
private void btnCancel_Click(object sender, EventArgs e)
{
Close();
}
}
}
Заключение
Надеемся, что это пояснение поможет начинающему, специально ищущему стандартный подход. Есть также некоторые методы, которые гораздо лучше описанной выше архитектуры, преимущественно с пропуском слоя доступа к базе данных и класса объекта-значения, и делающие ее динамической, что действительно удобно для сопровождения в случае частого изменения базы данных.