Трехслойная архитектура в C# .NET

ОГЛАВЛЕНИЕ

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

Обзор

  1. Уровень в сравнении со слоем
  2. Компоненты проектирования трехуровневой/трехслойной архитектуры
  3. Демо: трехслойное приложение 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();
        }
    }
}

Заключение

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