一、 什么是三层架构?
生活中的三层
初始结构:
在现实生活中,如果老王开了一家饭店,前期顾客比较少,自己来招待客人、在后厨炒菜、每天去市场采购食材。但是随着顾客量的增加,饭店的生意越来越兴隆,自己一个人单干忙的不可开交。就好比我们的软件系统一样,我们的用户是浏览我们的网页的,主要的功能是体现在UI层面,用户和系统产生交互,UI层面需要接收用户的数据信息、处理逻辑、访问数据库等等。那么如果我们的业务足够复杂和庞大,我们把所有的代码都集中在一个地方,那么对于项目后期的扩展或者维护会变得异常困难。
改变成三层:
为了缓解人手不足的局面,老王便招来了若干个服务员、厨师和采购员,他们各司其职,每个人都有自己的专职工作,这样合理的分功让饭店的日常经营变得十分顺利。那么对于我们的软件开发也应该是同样的道理,我们应该按照不同的处理的业务模块来进行合理的划分,一般我们会把系统架构分为UI表现层、业务逻辑层、数据访问层这样的三层,这就是经典的三层架构。
使用三层架构的目的:
目的:解耦
服务员(UI层)请假——另找服务员
厨师(BLL层)辞职——招聘另一个厨师
采购员(DAL层)辞职——招聘另一个采购员
顾客反映:
你们店服务态度不好——服务员的问题。开除服务员
你们店菜里有虫子——厨师的问题。换厨师
UI(表现层):主要是指与用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数据。
BLL(业务逻辑层):UI层和DAL层之间的桥梁。实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等等。
DAL(数据访问层):与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),UI反映给BLL,BLL反映给DAL,DAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户)
那么在三层架构中各层之间为了能够处理一个统一的功能,相互直接是如何依赖的呢?
我们从UI表现层出发依次从上往下来看,表现层需要调用业务逻辑层,业务逻辑层需要调用数据访问层,而数据的在每个层的传输需要通过Model实体层。
那么引入了三层架构之后我们的请求流程变为了用户在UI表现层提交数据,调用业务逻辑层中的方法来处理当前功能的业务逻辑,如果要和数据库做交互,那么需要在业务逻辑层中调用数据访问层来对数据进行操作,数据访问层最终会有一个处理的结果,有可能是操作成功失败,也有可能是读取数据,数据访问将这个结果返回给业务逻辑层,业务逻辑层获取这个结果之后将它返回给UI表现层,这样三层架构的请求流程就结束了。
接下来,我们来演示三层架构的实现具体步骤:
二、 三层架构综合应用
1、在项目中添加类库MODEL
在Model层中添加项目所需的实体类:如:Product类和ProductCategory类
/// <summary> /// 产品 /// </summary> public class Product { /// <summary> /// 主键Id /// </summary> public int Id { get; set; } /// <summary> /// 产品名称 /// </summary> public string ProductName { get; set; } /// <summary> /// 市场价 /// </summary> public decimal MarketPrice { get; set; } /// <summary> /// 售价 /// </summary> public decimal SellingPrice { get; set; } /// <summary> /// 类别Id /// </summary> public int CategoryId { get; set; } /// <summary> /// 简介 /// </summary> public string Introduction { get; set; } /// <summary> /// 是否上架 /// </summary> public bool IsOnSale { get; set; } /// <summary> /// 添加时间 /// </summary> public DateTime AddTime { get; set; }
/// <summary> /// 产品类别 /// </summary> public class ProductCategory { /// <summary> /// 主键Id /// </summary> public int Id { get; set; } /// <summary> /// 名称 /// </summary> public string Name { get; set; } }
2、在项目中添加类库DAL
DAL中添加数据访问助手类:DBHelper类
public class DBHelper { //1.声明一个数据库连接字符串 private static readonly string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString; /// <summary> /// 执行增删改的方法,返回受影响的行数 /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static int ExecuteNonQuery(string sql, params SqlParameter[] pams) { //1.声明数据库连接 using (SqlConnection conn = new SqlConnection(connStr)) { //2.声明命令 using (SqlCommand cmd = new SqlCommand(sql, conn)) { //添加参数 if (pams != null && pams.Length > 0) { cmd.Parameters.AddRange(pams); } //3.打开数据库连接 if (conn.State == ConnectionState.Closed) { conn.Open(); } //4.执行命令 return cmd.ExecuteNonQuery(); } } } /// <summary> /// 执行查询,返回首行首列 /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static object ExecuteScalar(string sql, params SqlParameter[] pams) { //1.声明数据库连接 using (SqlConnection conn = new SqlConnection(connStr)) { //2.声明命令 using (SqlCommand cmd = new SqlCommand(sql, conn)) { //添加参数 if (pams != null && pams.Length > 0) { cmd.Parameters.AddRange(pams); } //3.打开数据库连接 if (conn.State == ConnectionState.Closed) { conn.Open(); } //4.执行命令 return cmd.ExecuteScalar(); } } } /// <summary> /// 执行查询,返回SqlDataReader对象 /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static SqlDataReader ExecuteReader(string sql, params SqlParameter[] pams) { //1.声明数据库连接 SqlConnection conn = new SqlConnection(connStr); //2.声明命令 using (SqlCommand cmd = new SqlCommand(sql, conn)) { //添加参数 if (pams != null && pams.Length > 0) { cmd.Parameters.AddRange(pams); } //3.打开数据库连接 if (conn.State == ConnectionState.Closed) { conn.Open(); } //4.执行命令 return cmd.ExecuteReader(CommandBehavior.CloseConnection); } } /// <summary> /// 执行查询,返回DataTable对象 /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static DataTable ExecuteTable(string sql, params SqlParameter[] pams) { //1.声明SqlDataAdapter对象 using (SqlDataAdapter sda = new SqlDataAdapter(sql, connStr)) { //2.添加参数 if (pams != null && pams.Length > 0) { sda.SelectCommand.Parameters.AddRange(pams); } DataTable dt = new DataTable(); sda.Fill(dt); return dt; } } /// <summary> /// 执行查询,返回SqlDataReader对象(存储过程) /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static SqlDataReader ExecuteReaderProc(string procName, params SqlParameter[] pams) { //1.声明数据库连接 SqlConnection conn = new SqlConnection(connStr); //2.声明命令 using (SqlCommand cmd = new SqlCommand(procName, conn)) { //指定查询命令的存储过程类型 cmd.CommandType = CommandType.StoredProcedure; //添加参数 if (pams != null && pams.Length > 0) { cmd.Parameters.AddRange(pams); } //3.打开数据库连接 if (conn.State == ConnectionState.Closed) { conn.Open(); } //4.执行命令 return cmd.ExecuteReader(CommandBehavior.CloseConnection); } } /// <summary> /// 执行增删改的方法,返回受影响的行数(存储过程版) /// </summary> /// <param name="sql"></param> /// <param name="pams"></param> /// <returns></returns> public static int ExecuteNonQueryProc(string procName, params SqlParameter[] pams) { //1.声明数据库连接 using (SqlConnection conn = new SqlConnection(connStr)) { //2.声明命令 using (SqlCommand cmd = new SqlCommand(procName, conn)) { cmd.CommandType = CommandType.StoredProcedure; //添加参数 if (pams != null && pams.Length > 0) { cmd.Parameters.AddRange(pams); } //3.打开数据库连接 if (conn.State == ConnectionState.Closed) { conn.Open(); } //4.执行命令 return cmd.ExecuteNonQuery(); } } } }
DAL中添加ProductDAL类和ProductCategoryDAL,并在该类中添加实现增删改查的方法:
ProductCategoryDAL中获取类别信息:
/// <summary> /// 获取产品类别 /// </summary> /// <returns></returns> public DataTable GetCategoryList() { //声明SQL语句 string sql = "select * from ProductCategory"; return DbHelper.ExecuteTable(sql); }
ProductDAL中获取产品信息:
/// <summary> /// 查询产品列表 /// </summary> /// <param name="productName"></param> /// <param name="categoryId"></param> /// <returns></returns> public DataTable GetProductList(string productName,int categoryId) { //定义SQL语句 string sql = "select p.Id,p.ProductName,p.MarketPrice,p.SellingPrice,pc.Name,p.IsOnSale,p.AddTime from Product as p left join ProductCategory as pc on p.CategoryId=pc.Id"; List<string> listWhere = new List<string>(); List<SqlParameter> listPams = new List<SqlParameter>(); //多条件判断 if (!string.IsNullOrWhiteSpace(productName)) { listWhere.Add("p.ProductName like @ProductName"); listPams.Add(new SqlParameter("@ProductName", $"%{productName}%")); } if (categoryId > 0) { listWhere.Add("p.CategoryId = @CategoryId"); listPams.Add(new SqlParameter("@CategoryId", categoryId)); } if (listWhere.Count > 0) { sql += " where " + string.Join(" and ", listWhere.ToArray()); } return DbHelper.ExecuteTable(sql, listPams.ToArray()); }
/// <summary> /// 根据Id获取产品 /// </summary> /// <param name="id"></param> /// <returns></returns> public Product GetProductById(int id) { string sql = "select * from Product where Id=@Id"; SqlParameter[] pams = { new SqlParameter("@Id",id) }; SqlDataReader reader = DbHelper.ExecuteReader(sql, pams); if(reader.HasRows) { if(reader.Read()) { Product model = new Product() { AddTime=Convert.ToDateTime(reader["AddTime"]), CategoryId=Convert.ToInt32(reader["CategoryId"]), Id=Convert.ToInt32(reader["Id"]), Introduction=reader["Introduction"].ToString(), IsOnSale=Convert.ToBoolean(reader["IsOnSale"]), MarketPrice=Convert.ToDecimal(reader["MarketPrice"]), ProductName=reader["ProductName"].ToString(), SellingPrice=Convert.ToDecimal(reader["SellingPrice"]) }; return model; } } return null; }
/// <summary> /// 更新产品 /// </summary> /// <param name="model"></param> /// <returns></returns> public bool Update(Product model) { string sql = "update Product set ProductName=@ProductName,MarketPrice=@MarketPrice,SellingPrice=@SellingPrice,CategoryId=@CategoryId, Introduction=@Introduction,IsOnSale=@IsOnSale where Id=@Id"; SqlParameter[] pams = { new SqlParameter("@ProductName",model.ProductName), new SqlParameter("@MarketPrice",model.MarketPrice), new SqlParameter("@SellingPrice",model.SellingPrice), new SqlParameter("@CategoryId",model.CategoryId), new SqlParameter("@Introduction",model.Introduction), new SqlParameter("@IsOnSale",model.IsOnSale), new SqlParameter("@Id",model.Id) }; return DbHelper.ExecuteNonQuery(sql, pams) > 0; }
/// <summary> /// 删除产品 /// </summary> /// <param name="id"></param> /// <returns></returns> public bool Delete(int id) { string sql = "delete from Product where Id=@Id"; SqlParameter[] pams = { new SqlParameter("@Id",id) }; return DbHelper.ExecuteNonQuery(sql, pams) > 0; }
/// <summary> /// 添加产品 /// </summary> /// <param name="model">产品实体</param> /// <returns></returns> public bool Add(Product model) { string sql = "insert into Product values(@ProductName,@MarketPrice,@SellingPrice,@CategoryId,@Introduction,@IsOnSale,@AddTime)"; SqlParameter[] pams = { new SqlParameter("@ProductName",model.ProductName), new SqlParameter("@MarketPrice",model.MarketPrice), new SqlParameter("@SellingPrice",model.SellingPrice), new SqlParameter("@CategoryId",model.CategoryId), new SqlParameter("@Introduction",model.Introduction), new SqlParameter("@IsOnSale",model.IsOnSale), new SqlParameter("@AddTime",DateTime.Now) }; return DbHelper.ExecuteNonQuery(sql, pams) > 0; }
3、在项目中添加BLL类库
BLL中添加ProductBLL和ProductCategoryBLL类,并添加业务逻辑层方法:
public class ProductCategoryBLL { ProductCategoryDAL dal = new ProductCategoryDAL(); /// <summary> /// 获取产品类别 /// </summary> /// <returns></returns> public DataTable GetCategoryList() { return dal.GetCategoryList(); } }
public class ProductBLL { ProductDAL dal = new ProductDAL(); /// <summary> /// 查询产品列表 /// </summary> /// <param name="productName"></param> /// <param name="categoryId"></param> /// <returns></returns> public DataTable GetProductList(string productName, int categoryId) { return dal.GetProductList(productName, categoryId); } /// <summary> /// 获取Id获取产品 /// </summary> /// <param name="id"></param> /// <returns></returns> public Product GetProductById(int id) { return dal.GetProductById(id); } /// <summary> /// 更新产品 /// </summary> /// <param name="model"></param> /// <returns></returns> public bool Update(Product model) { return dal.Update(model); } /// <summary> /// 删除产品 /// </summary> /// <param name="id"></param> /// <returns></returns> public bool Delete(int id) { return dal.Delete(id); } /// <summary> /// 添加产品 /// </summary> /// <param name="model">产品模型</param> /// <returns></returns> public bool Add(Product model) { return dal.Add(model); } }
4、在项目中添加Asp.NET Web应用程序,作为UI层,并添加Web窗体
在窗体中添加GridView控件,并进行数据绑定的配置:
界面效果如下:
在后台调用BLL层中获取产品类别的方法,并将数据绑定到下拉框中:
ProductBLL bllProduct = new ProductBLL(); ProductCategoryBLL bllCategory = new ProductCategoryBLL(); protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //绑定类别下拉框 BindCategory(); //绑定产品 BindProductList(); } } /// <summary> /// 绑定类别下拉框方法 /// </summary> private void BindCategory() { DataTable dt = bllCategory.GetCategoryList(); ddl_Category.DataSource = dt; ddl_Category.DataTextField = "Name"; ddl_Category.DataValueField = "Id"; ddl_Category.DataBind(); //插入默认项 ddl_Category.Items.Insert(0, new ListItem("全部", "0")); }
调用BLL层中获取产品信息的业务逻辑方法,将数据绑定到GridView中:
/// <summary> /// 绑定产品方法 /// </summary> private void BindProductList() { string productName = txt_ProductName.Text.Trim(); int categoryId = Convert.ToInt32(ddl_Category.SelectedValue); DataTable dt = bllProduct.GetProductList(productName, categoryId); gv_Product.DataSource = dt; gv_Product.DataBind(); }
为查询按钮添加一个点击事件,进行多条件查询:
/// <summary> /// 多条件搜索 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btn_Search_Click(object sender, EventArgs e) { BindProductList(); }
至此,列表查询功能完成
-----------------------------------------------------------
在GridView中进行删除按钮的处理
先在操作模板列上添加一个LinkButton服务器控件,并添加CommandName和CommandArgument这两个属性:
添加一个js确认提示:
在GirdView中添加RowCommand事件:
调用BLL层中的删除产品的方法:
protected void gv_Product_RowCommand(object sender, GridViewCommandEventArgs e) { if (e.CommandName == "del") { int id = Convert.ToInt32(e.CommandArgument); if (bllProduct.Delete(id)) { ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('删除成功')</script>"); BindProductList(); } else { ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('删除失败')</script>"); } } }
---------------------------------------------------------
添加产品功能实现:
按照要求在页面中添加控件:
在后台页面加载的时读取下拉框数据:
ProductBLL bllProduct = new ProductBLL(); ProductCategoryBLL bllCategory = new ProductCategoryBLL(); protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //绑定类别下拉框 BindCategory(); } } /// <summary> /// 绑定类别下拉框方法 /// </summary> private void BindCategory() { DataTable dt = bllCategory.GetCategoryList(); ddl_Category.DataSource = dt; ddl_Category.DataTextField = "Name"; ddl_Category.DataValueField = "Id"; ddl_Category.DataBind(); }
为按钮添加点击事件实现添加功能:
protected void btn_Save_Click(object sender, EventArgs e) { Product model = new Product() { CategoryId = Convert.ToInt32(ddl_Category.SelectedValue), Introduction = txt_Introduction.Text, IsOnSale = ck_IsOnSale.Checked, MarketPrice = Convert.ToDecimal(txt_MarketPrice.Text), ProductName = txt_ProductName.Text, SellingPrice = Convert.ToDecimal(txt_SellingPrice.Text) }; //添加成功后,弹出消息提示,并跳转至首页 if (bllProduct.Add(model)) { Page.ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('添加成功!');location.href='/Index.aspx';</script>"); } else { Page.ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('添加失败!');</script>"); } }
--------------------------------------------------
编辑产品功能实现:
按照要求在页面中添加控件:
在类别页面添加跳转到编辑页面的链接:
在后台添加获取下拉框数据的方法:
ProductBLL bllProduct = new ProductBLL(); ProductCategoryBLL bllCategory = new ProductCategoryBLL(); protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { //绑定类别下拉框 BindCategory(); //绑定产品信息 BindProductInfo(); } } /// <summary> /// 绑定类别下拉框方法 /// </summary> private void BindCategory() { DataTable dt = bllCategory.GetCategoryList(); ddl_Category.DataSource = dt; ddl_Category.DataTextField = "Name"; ddl_Category.DataValueField = "Id"; ddl_Category.DataBind(); }
添加根据id获取当前编辑的产品的方法:
/// <summary> /// 绑定产品信息方法 /// </summary> private void BindProductInfo() { int id = Convert.ToInt32(Request.QueryString["id"]); Product model = bllProduct.GetProductById(id); if (model != null) { txt_ProductName.Text = model.ProductName; txt_Introduction.Text = model.Introduction; txt_MarketPrice.Text = model.MarketPrice.ToString(); txt_SellingPrice.Text = model.SellingPrice.ToString(); ddl_Category.SelectedValue = model.CategoryId.ToString(); ck_IsOnSale.Checked = model.IsOnSale; lbl_Id.Text = model.Id.ToString(); } }
为按钮添加点击事件实现更新:
/// <summary> /// 提交修改 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btn_Save_Click(object sender, EventArgs e) { Product model = new Product() { CategoryId = Convert.ToInt32(ddl_Category.SelectedValue), Id = Convert.ToInt32(Request.QueryString["id"]), Introduction = txt_Introduction.Text, IsOnSale = ck_IsOnSale.Checked, MarketPrice = Convert.ToDecimal(txt_MarketPrice.Text), ProductName = txt_ProductName.Text, SellingPrice = Convert.ToDecimal(txt_SellingPrice.Text) }; //修改成功后,弹出消息提示,并跳转至首页 if (bllProduct.Update(model)) { Page.ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('修改成功!');location.href='/Index.aspx';</script>"); } else { Page.ClientScript.RegisterStartupScript(this.GetType(), "alert", "<script>alert('修改失败!');</script>"); } }
-----------------------------
利用三层架构与GridView相结合,GirdView的使用方法和之前基本一致,改变为三层架构之后,我们发现原本在UI层中的代码明显减少了很多,通过ADO.NET访问数据库的部分放在了数据访问层,体现出了高内聚、低耦合的思想。