C# WinForm项目三层架构简述

基于C#.NET的WinForm项目,我们经常使用基于三层架构,来构建项目框架,这里简单的梳理一下三层架构的相关知识

哪三层?


我们通常所说的三层框架指的是DAL、BIL和UIL三层,分别是数据层、业务逻辑层和界面层,以及与之搭配的实体类和通用类库,下面分别概述

实体类- Model

  • 我们将数据存放在数据库中,数据表的结构,我们通常会用一个类来抽象,表的属性就是类的属性,我们通常将表的一行存储在一个类中。我们在Java中,通常将其称为实体类Entity,在C#中,我们通常将其称为Model。
  • 如图,我们定义了一个数据表,表结构如下


    表结构
  • 我们根据表的信息,创建了如下的实体类
namespace mtWeightModel
{
    [Serializable] //表示可以序列化
    public class User
    {
        public int Id { get; set; }
        public String username { get; set; }
        public String password { get; set; }
        public int status { get; set; }
    }
}
  • 我们可以看到,在SQLServer数据库中,char varchar nchar nvarchar都用String类型,int就对应Int。
  • Model类库一般来说需要被DAL、BIL和UI引用。

DAL-数据访问层 - DataAccessLayer

  • 数据访问层,就是调用我们数据库访问方法,专注于数据的增删改查操作,构建SQL语句,构建参数等,以下是一个典型DAL方法
namespace mtWeightDAL
{
    /// <summary>
    /// 用户访问数据类
    /// </summary>
    public class UserService
    {
        /// <summary>
        /// 根据账号和密码比对用户信息
        /// </summary>
        /// <param name="objUser">包含用户名和密码的用户对象</param>
        /// <returns>返回用户对象信息(若无用户信息则对象为null)</returns>
        public User UserLogin(User objUser) {
            String sql = "SELECT Id,username,password,status FROM Users where username=@username and password=@password";
            SqlParameter[] param = new SqlParameter[] {
                new SqlParameter("@username",objUser.username),
                new SqlParameter("@password", objUser.password)
            };
            SqlDataReader objReader = SqlHelper.getReader(sql, param);
            if (objReader.Read())
            {
                objUser.Id = Convert.ToInt32(objReader["Id"]);
                objUser.status = Convert.ToInt32(objReader["status"]);
            }
            else {
                objUser = null;
            }
            objReader.Close();
            return objUser;
        }
    }
}
  • 这里用到了一个SqlHelper类,使我们后面要单独说的数据库帮助类
  • DAL就是根据业务需求,构建SQL语句,构建参数,调用帮助类,获取结果,DAL层被BIL层调用

BLL-业务逻辑层 - Business Logic Layer

  • BLL层索要负责的,就是处理业务逻辑上的问题,比如在调用数据库访问之前,对数据的处理、判断等。下面是一个最简单的业务逻辑方法,不处理任何信息,只做参数传递。
namespace mtWeightBLL
{
    /// <summary>
    /// 用户业务逻辑类
    /// </summary>
    public class UserManager
    {
        //创建数据访问对象
        private UserService objUserService = new UserService();

        public User UserLogin(User objUser) {
            return objUserService.UserLogin(objUser);
        }
    }
}
  • 那你可能就会有疑问,为什么不把业务逻辑和数据访问合在一起呢,偏要搞出两个层,不是多此一举么。那其实呢,我们分层解决问题的意义就是,功能专一,并且解耦我们的程序,我们在DAL层只关心我的数据库访问操作,我默认你给我的数据是合法的、正确的,那至于你如何保证数据的合法性和正确性就是你需要在BLL层里去做的了。
  • BLL层只被UIL层引用

UIL-用户表现层

  • 就是窗体Form
  • 这里有一个click事件
private void btnLogin_Click(object sender, EventArgs e)
        {
            //数据验证
            if (this.txtUsername.Text.Trim().Length == 0) {
                MessageBox.Show("请输入用户名", "登录提示");
                this.txtUsername.Focus();
            }
            if (this.txtPassword.Text.Trim().Length == 0)
            {
                MessageBox.Show("请输入密码", "登录提示");
                this.txtPassword.Focus();
            }

            // 封装对象
            User objUser = new User {
                username = this.txtUsername.Text.Trim(),
                password = this.txtPassword.Text.Trim()
            };
            try
            {
                objUser = objUserManager.UserLogin(objUser);
                if (objUser != null)
                {
                    if (objUser.status == 1)
                    {
                        Program.objCurrentUser = objUser;
                        this.DialogResult = DialogResult.OK;
                        this.Close();
                    }
                    else
                    {
                        MessageBox.Show("用户被禁用,请联系管理员", "登录提示");
                    }
                }
                else {
                    MessageBox.Show("用户名或密码错误!", "登录提示");
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("登录异常:"+ex.Message,"登录提示");
            }
        }

通用类库

  • 对于一些程序中用到的其他封装的类库,可以统一放在这里,比如一些第三方类库等

引用关系


引用关系图

数据库帮助类


  • 我们可以用过封装ADO.NET形成自己的一套方法,但是我们知道在ADO.NET中,SQLServer、Access、Mysql和Oracle使用的是不同的类,那么我们可能需要对每一个数据库封装一套帮助类。其中常用的方法包括非查询类方法(两个重载[sql],[sql,参数])和查询类方法(两个重载[sql],[sql,参数]),事务类方法、存储过程类方法等。
  • 我们通常为会先规定一个接口,然后在帮助类中实现结构。这样后期可以通过反射或工厂的方式来实现不同数据库的切换(后面另说)。
  • 下面是我定义的一个简单的数据库帮助类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;

namespace DbUtil
{
    public class SqlHelper
    {
        private static String ConnString = ConfigurationManager.ConnectionStrings["ConnString"].ToString();
        #region 格式化SQL语句
        /// <summary>
        /// 增删改非查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <returns></returns>
        public static int UPDATE(string sql) {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                return cmd.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally {
                conn.Close();
            }
        }
        /// <summary>
        /// 返回单条结果查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <returns></returns>
        public static object getSingleResult(string sql)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                return cmd.ExecuteScalar();
            }
            catch (Exception ex)
            {

                throw new Exception(ex.Message);
            }
            finally
            {
                conn.Close();
            }
        }
        /// <summary>
        /// 多条结果查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <returns></returns>
        public static SqlDataReader getReader(string sql)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch (Exception ex)
            {
                conn.Close();
                throw new Exception(ex.Message);
            }
          
        }
        /// <summary>
        /// 返回DataSet数据集方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <returns>结果集DataSet</returns>
        public static DataSet getDataSet(string sql)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            SqlDataAdapter adapter = new SqlDataAdapter(cmd);
            DataSet ds = new DataSet();
            try
            {
                conn.Open();
                adapter.Fill(ds);
                return ds;
            }
            catch (Exception ex)
            {

                throw new Exception(ex.Message);
            }
            finally {
                conn.Close();
            }

        }
        #endregion

        #region 带参数SQL语句
        /// <summary>
        /// 增删改非查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="param">SQl参数</param>
        /// <returns></returns>
        public static int UPDATE(string sql,SqlParameter[] param)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                cmd.Parameters.AddRange(param);
                return cmd.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            finally
            {
                conn.Close();
            }
        }
        /// <summary>
        /// 返回单条结果查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="param">SQL参数</param>
        /// <returns></returns>
        public static object getSingleResult(string sql, SqlParameter[] param)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                cmd.Parameters.AddRange(param);
                return cmd.ExecuteScalar();
            }
            catch (Exception ex)
            {

                throw new Exception(ex.Message);
            }
            finally
            {
                conn.Close();
            }
        }
        /// <summary>
        /// 多条结果查询类方法
        /// </summary>
        /// <param name="sql">SQL语句</param>
        /// <param name="param">SQL参数</param>
        /// <returns></returns>
        public static SqlDataReader getReader(string sql,SqlParameter[] param)
        {
            SqlConnection conn = new SqlConnection(ConnString);
            SqlCommand cmd = new SqlCommand(sql, conn);
            try
            {
                conn.Open();
                cmd.Parameters.AddRange(param);
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            catch (Exception ex)
            {
                conn.Close();
                throw new Exception(ex.Message);
            }
        }
        #endregion
    }
}

项目创建

  • 首先创建解决方案的过程中,创建默认项目Windows窗体应用程序UIL
  • 然后分别在解决方案中添加类库项目DAL、BLL和CL(通用类库)和Model
  • 然后新建一个DBUtil类库,里面放SQLHeader类
  • 下面添加引用,从下网往上添加,先给DAL添加Model和DBHelper的引用,再为BLL添加DAL和Model的引用,再为UIL添加BLL和Model引用,然后在为需要使用通用类库的项目添加CL的引用。
  • 引用完成后:



我把这个项目框架上传到这里,大家可以下载下来看一下http://down.clubunion.cn/mtWeight.zip

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容