新手学C# ——.net的三层架构之最简单实例:登录界面
三层架构包括:
1. 数据访问层(Data Access Layer, DAL):负责将底层数据传送到业务逻辑层
2. 业务逻辑层(Business Logic Layer, BLL):处理数据访问层传送的数据,并实现业务逻辑
3. 表示层(User Interface, UI):不处理任何业务,负责显示与实时更新
其中1--2--3层次依次上升。
为了使信息能够顺利由底层传送,可以采用实体类的方法,添加实体层。实体类通常与数据库中字段相互对应,拥有get和set属性。实体类的目的是用于替代DataSet,通过类的方式传递数据,并使逻辑更加清晰。
三层架构来源于MVC,其核心思想是保持三层之间的相互独立,在对其他功能进行局部修改时,可以使三层结构保持大体不变,以免进行全局的修改。换句话说,在修改某局部代码后,你自然而然的知道何处的代码应该进行修改,何处的代码可以不进行修改,如何规范修改的局部代码对上层的接口以达到减少修改量的目的。
这里不得不说的是,在三层架构中,修改局部代码对整体的变动还是很大的,层次数越多,需要修改的部分越少。当然,层次数的增加伴随着逻辑的复杂性增加和接口的复杂性增加。越是小型的项目,需要的层次数越少。
下面举一个最简单的例子:用户登录。
数据库中表Login的结构:
LoginID : 用户名
LoginPwd : 用户密码
(1)新建一个WinForm项目SchoolManager,在项目解决方案中点击右键新建三个类库(SchoolManager.DAL, SchoolManager.BLL, SchoolManager.Model),分别表示数据访问层、业务逻辑层和实体层。
(2)右键单击类库,添加引用(BLL引用DAL,UI引用BLL,三者都引用Model),表达四者之间的逻辑依赖关系。这里强调的是,UI不引用DAL。
(3)编写实体类Login.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SchoolManager.Model { public class Login { private int stuID; //学号 public int StuID { get { return stuID; } set { stuID = value; } } private string studentName; //姓名 public string StudentName { get { return studentName; } set { studentName = value; } } private string loginID; //登录名 public string LoginID { get { return loginID; } set { loginID = value; } } private string loginPwd; //登录密码 public string LoginPwd { get { return loginPwd; } set { loginPwd = value; } } private int state; public int State { get { return state; } set { state = value; } } } }
这个实体类与数据库中的表一一对应(这里没有对应的原因是实际程序中只用到了两个字段,实际数据库中的表以此类中的字段为准),也就是说,数据库中表的每一行都可以转化为这个类中的一个实例,从而在各个层中传递数据。
(4)编写DAL类LoginService.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SchoolManager.Model; using System.Data.SqlClient; namespace SchoolManager.DAL { public static class LoginService { static string connStr = @"Data Source=(local)\SQLEXPRESS;Initial Catalog=StudentInfo;Integrated Security=True;Pooling=False"; private static SqlConnection sqlConn = new SqlConnection(connStr); /// <summary> /// 根据登录名返回用户类 /// </summary> /// <param name="loginID">登录名</param> /// <returns>用户类</returns> public static Login GetLoginByLoginID(string loginID) { string sql = "select * from Login where LoginID=‘" + loginID + "‘"; try { SqlCommand sqlCmd = new SqlCommand(sql, sqlConn); sqlConn.Open(); SqlDataReader sqlReader = sqlCmd.ExecuteReader(); if (sqlReader.Read()) { Login user = new Login(); user.LoginID = Convert.ToString(sqlReader["LoginID"]); user.LoginPwd = Convert.ToString(sqlReader["LoginPwd"]); user.State = Convert.ToInt32(sqlReader["State"]); user.StudentName = Convert.ToString(sqlReader["StudentName"]); user.StuID = Convert.ToInt32(sqlReader["StuID"]); sqlReader.Close(); sqlConn.Close(); return user; } else { sqlConn.Close(); sqlReader.Close(); return null; } } catch (Exception ex) { Console.WriteLine(ex.Message); throw ex; } } } }
这里注意几点:
a)此类(以及之后的BLL,UI)引入了Model的命名空间,从而可以调用Model中的Login类并生成实例,这正是添加引用的原因所在。
b)此类引入了System.Data.SqlClient,也是这三层中唯一引用这个命名空间的层,即所有对数据库的操作都要在这一层的不同类中完成。
c)GetLoginByLoginIn()方法的返回值是Login类的一个实例,实质上是返回查询后的指定行数据。
(5)编写BLL类LoginManager.cs:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using SchoolManager.Model; using SchoolManager.DAL; namespace SchoolManager.BLL { public class LoginManager { /// <summary> /// 从数据访问层调用数据的方法 /// </summary> /// <param name="loginID">登录名</param> /// <returns>密码是否匹配</returns> public static bool GetLogin(string loginID, string loginPwd) { Login user = LoginService.GetLoginByLoginID(loginID); if (user.LoginPwd == loginPwd) { return true; } else { return false; } } } }
注意以下几点:
a)引用了DAL的命名空间,因为要调用DAL中的GetLoginByLoginIn()方法。
b)GetLogin()实际上已经完成了用户登录的逻辑功能,不能将判断式写入UI层中。
(6)编写UI层LoginForm.cs:(界面很简洁,用户名、密码输入框,确定、取消按钮)
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 SchoolManager.Model; using SchoolManager.BLL; namespace SchoolManager { public partial class LoginForm : Form { public LoginForm() { InitializeComponent(); } private void buttonLogin_Click(object sender, EventArgs e) { //检测用户名是否为空 if(string.IsNullOrEmpty(textBoxName.Text)) { MessageBox.Show("请输入用户名"); return; } //检测密码是否为空 if (string.IsNullOrEmpty(textBoxPwd.Text)) { MessageBox.Show("请输入密码"); return; } //调用业务逻辑层方法登录 if(LoginManager.GetLogin(textBoxName.Text, textBoxPwd.Text)) { MessageBox.Show("登陆成功"); } else { MessageBox.Show("用户名或密码错误"); } } private void buttonCancel_Click(object sender, EventArgs e) { this.Close(); } } }
这里有几点需要注意:
a)引用命名空间(详见上文)
b)对TextBox中是否为空的判断可以由UI层完成,也可以在BLL层中编写Validate()函数并在UI层中调用,不过还是建议在UI层中完成,毕竟界面的修改必然伴随着Validate()方法的修改
c)UI层调用GetLogin()方法之后,只是将返回的布尔值解释为登录成功/失败,没有进行逻辑上的判断。
希望本文能对.net学习的菜鸟级新手有所帮助!(本人也处于这一行列)