上一篇博客小编简单介绍了一下近期在软件开发过程中由三层架构演变而来的“七层架构”基本理论点。理论知识与产生结果之间还夹杂着一个重要的点---实践。用实践来检验理论知识,丰富知识内涵、加深对其理解。接下来小编用一个简单的小实例来为您分享由三层架构演变而来的“七层架构”。
【1】首先,搭建七层结构的框架;
搭建结构可以仿照小编前面的博文<三层实践>来完成哦。
【2】其次,设置好层与层之间的引用关系;
具体的引用关系,请参看<上篇博客>。
【3】再次,添加需要的类;
添加方式与<三层实践>相同哦。
【4】最后,设计UI并进行编码。
这一步骤也与<三层实践>是大抵相同的,这里不再重复赘述。
接下来主要以员工登录功能需求为例,为您分享“七层架构”是如何实现这一功能需求的。探索层与层之间是如何取得联系的、参数是如何传递的、每一层有着哪些更为具体的作用。
❉❉ 首先,在D层下新建的类中封装需要的SqlHelper方法,用于创建数据库的连接。
public class SQLHelper
{
//声明对象
private SqlConnection conn = null; //SqlConnection 打开连接
private SqlCommand cmd = null; //SqlCommand对象允许你指定在数据库上执行的操作的类型。比如,你能够对数据库中的行数据执行select,insert,modify以及delete命令。
//SqlCommand对象能被用来支持断开连接数据管理的情况,可以只单独使用SqlCommand对象。也可以与SqlDataAdapter一起实现断开数据连接,实现操作数据库的应用程序
private SqlDataReader sdr = null; //读取只进的行流的方式
//构造方法
public SQLHelper()
{
string connStr = ConfigurationManager.AppSettings["connStr"]; //定义连接数据库字符串参数。connStr 是配置文件里连接数据库的关键字,引用这一句可以连接数据库
conn = new SqlConnection(connStr); //实例化一个连接,将连接数据库的字符串参数传递给连接对象
}
// 将数据库连接打开
private SqlConnection GetConn()
{
if (conn.State == ConnectionState.Closed) //如果连接状态为关闭
{
conn.Open(); //则打开
}
return conn;
}
/// <summary>
/// 执行 不带参数 的 增删改SQL语句 或者 存储过程
/// </summary>
/// <param name="cmdText">增删改SQL</param>
/// <param name="ct">命令类型</param>
/// <returns>返回受影响的行数</returns>
public int ExecuteNonQuery(string cmdText, CommandType ct)
//增删改 SQL 命令类型
{
int res;
try
{
cmd = new SqlCommand(cmdText, GetConn()); //打开连接
cmd.CommandType = ct;
res = cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (conn.State == ConnectionState.Open)
{
conn.Close(); //关闭数据库 与上面相对应
}
}
return res;
}
/// <summary>
/// 执行 带参数的的 增删改SQL语句 或者 存储过程
/// </summary>
/// <param name="cmdText">增删改SQL</param>
/// <param name="paras">要查询的参数</param>
/// <param name="ct">命令类型</param>
/// <returns>返回受影响的行数</returns>
public int ExecuteNonQuery(string cmdText, SqlParameter[] paras, CommandType ct)
{
int res;
using (cmd = new SqlCommand(cmdText, GetConn())) //使用using,在三层实践篇中有介绍哦
{
cmd.CommandType = ct;
cmd.Parameters.AddRange(paras);
res = cmd.ExecuteNonQuery();
}
return res;
}
/// <summary>
/// 执行 不带参数的 查询SQL语句 或 存储过程
/// </summary>
/// <param name="cmdText">查询SQL语句或存储过程</param>
/// <param name="ct">命令类型</param>
/// <returns></returns>
public DataTable ExecuteQuery(string cmdText, CommandType ct)
{
DataTable dt = new DataTable();
cmd = new SqlCommand(cmdText, GetConn());
cmd.CommandType = ct;
using (sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
return dt;
}
/// <summary>
/// 执行 带参数的 查询SQL语句 或 存储过程
/// </summary>
/// <param name="cmdText">查询SQL语句或存储过程</param>
/// <param name="paras">参数集合</param>
/// <param name="ct">命令类型</param>
/// <returns></returns>
public DataTable ExecuteQuery(string cmdText, SqlParameter[] paras, CommandType ct)
{
DataTable dt = new DataTable();
cmd = new SqlCommand(cmdText, GetConn());
cmd.CommandType = ct;
cmd.Parameters.AddRange(paras);
using (sdr = cmd.ExecuteReader(CommandBehavior.CloseConnection))
{
dt.Load(sdr);
}
return dt;
}
#endregion
}
❀❀ 在封装的SqlHelper方法中,为什么要把对数据库的 “查”操作与“增删改”操作的方法分开来写呢?
最本质的原因是,Sql Server执行Select语句与Insert、Delete、Update语句时,返回的结果是不一样的。前者返回的结果是一个临时表,而后者返回的结果是受到影响的行数。
❉❉其次,在每一层中编写实现功能的方法。
[1] 实体层:定义字段、属性,用于实现功能过程中的传参。
namespace Entity
{
public enum StaffLoginState//采用枚举的方法,列登录时可能出现的情况
{
OK,//成功
NoError,//失败
}
public class StaffInfo //StaffInfo类型所需要的字段。字段名称最好与数据库表中字段保持一致,方便区分。
{
private int adminid;
public int adminID
{
get { return adminid; }
set { adminid = value; }
}
private string adminname;
public string adminName
{
get { return adminname; }
set { adminname = value; }
}
private string password;
public string Password
{
get { return password; }
set { password = value; }
}
private string head;//级别
public string Head
{
get { return head; }
set { head = value; }
}
private string isdelete;//是否已注销
public string Isdelete
{
get { return isdelete; }
set { isdelete = value; }
}
}
}
[2] 接口层:只是用来定义实现功能所需的方法名。这一层只有抽象的方法名,而没有具体实现功能的方法体。
namespace IDAL
{
public partial interface StaffIDAL
{
StaffInfo SelectByID(int id);//定义一个类型为Staffinfo 名为SelectByID 带参数的方法。这是根据ID查询员工信息的方法,用于员工登录等功能。
}
}
[3] 数据访问层:D层实现接口层中定义的方法。该层中的方法名 要与接口层中定义的方法名保持一致,且有具体实现功能的方法体。
namespace DAL
{
public partial class StaffDAL:StaffIDAL //实现接口的方法
{
#region 根据员工ID 获取员工信息---用于员工登录
public StaffInfo SelectByID(int id)
{
StaffInfo staff = null ;//声明一个用户
SQLHelper sqlHelper = new SQLHelper(); //实例化类 跳转到SQLHelper方法
string sql = @"select * from Staff_Info where adminID=@adminID and isdelete='否'";//构造Select查询语句 匹配数据库
SqlParameter p = new SqlParameter("@adminID", id);//为语句构造参数
DataTable dt = sqlHelper.ExecuteQuery(sql, p,CommandType.Text); //连接数据库 执行查询得到结果
if (dt.Rows.Count >0) //判断数据表里是否查到结果
{
staff = new StaffInfo()//若用户存在
{
//获取用户信息
adminID=Convert.ToInt32(dt.Rows[0][0].ToString()),
Password=dt.Rows [0][2].ToString(),
adminName=dt.Rows[0][1].ToString(),
Head=dt.Rows[0][3].ToString(),
};
}
return staff;//返回一个用户对象
}
#endregion
}
}
[4] 工厂层:设置配置文件信息,选取所需的数据库;创建相应的接口对象来实现接口层中的方法。
在App.config中设置配置文件信息:
上面是从网上查到的配置文件信息的两种方法,用其中的一组(appSettings 或 connectionStrings)就可以实现目前所需的功能。至于这两种有什么更为具体的区别,目前小编还不是很清楚哦。
配置文件的方式不同,就会影响到SQLHelper中连接数据库的方法。以上两种配置文件的形式分别对应下面的SQLHelper中连接数据库所需拼接的字符串:
在工厂层下的类中编写代码:
//需要添加的引用。
using System.Reflection;
using System.Configuration;
namespace Factory
{
public class StaffFactory
{
private string StrDB = ConfigurationManager.AppSettings["DB"]; //接收来自配置文件的数据
public IDAL.StaffIDAL CreateUser()
{
string ClassName = StrDB + "." + "StaffDAL"; //这里的StaffDAL是D层的类名
return (IDAL.StaffIDAL)Assembly.Load(StrDB).CreateInstance(ClassName); //反射加工厂的应用
}
}
}
这里在添加引用时,需要先在工厂层的引用下 添加该程序集的引用。
工厂+反射的应用,是为在大型项目开发过程中方便更换数据库。
[5] 业务逻辑层:进行逻辑判断。
namespace BLL
{
public partial class StaffBLL
{
private StaffIDAL idal;
StaffFactory fact = new StaffFactory();//实例化工厂对象
public StaffBLL () //构造方法
{
idal = fact.CreateUser();
}
public StaffLoginState Login(int id,string pwd,out string head,out string adminame)
{
//使用out传值 需要给参数定义初值
head = null;//定义head为空值
adminame = null;
//根据用户名进行对象查询
StaffIDAL idal = fact.CreateUser();//调用工厂方法创建接口
StaffInfo staff = idal.SelectByID(id);//接收D层的返回值
if (staff ==null) //判断是否存在用户对象
{
//用户不存在
//用户名或密码错误
return StaffLoginState.NoError;
}
else
{
//用户存在
head = staff.Head;//返回用户头衔 及姓名
adminame = staff.adminName;
return StaffLoginState.OK;
}
}
}
}
[6] 外观层:解耦U层和B层,接收和传递参数。
namespace Facade
{
public class StaffFacade
{
StaffBLL staffBll = new StaffBLL();//实例化B层对象
//用户登录
public StaffLoginState Login(int id,string pwd,out string head,out string username)
{
return staffBll.Login(id, pwd, out head, out username);//接收B层返回的值,并将其传给U层
}
}
}
[7] 显示层:与用户直接关联,接收用户输入的信息,并返回结果。
namespace UI
{
public partial class frmLoginStaff : Form
{
public frmLoginStaff()
{
InitializeComponent();
}
//声明静态变量 用于其他功能获取登录的用户名 传值
// 登录的方法
private void Login()
{
if (txtUserID.Text.Trim ()==""||txtPWD .Text=="")
{
MessageBox.Show("请将登录信息填写完整");
return;//停止后面代码的执行 返回到if之前
}
//声明变量,接收返回的值
string head;
string adminame;
//获取用户输入的信息
int ID = Convert.ToInt32(txtUserID.Text.Trim());
string pwd = txtUserID.Text;
StaffFacade staffFacade = new StaffFacade();
StaffLoginState state = staffFacade.Login(ID, pwd, out head, out adminame);
switch (state)
{
//登录成功
case StaffLoginState.OK:
staff.Head = head;
staff.Adminid = ID;
staff.adminName = adminame;
this.Hide();
MessageBox.Show("登录成功");
frmMainStaff main = new frmMainStaff();
main.Tag = head;//判断用户级别,用于给主窗体传值
main.Show();
break;
//登录失败
case StaffLoginState.NoError:
MessageBox.Show("用户不存在或输入信息错误");
txtPWD.Text = "";
txtUserID.Text = "";
break;
default:
break;
}
}
}
}
到此为止,基本的编码工作就完成啦。点击登录,迫切的希望看到“登录成功”弹出框,然后并没有。经过不断调试,出现的最大问题就是依赖项,找不到文件。解决这问题可是花费了小编不少精力呢。
❀❀按照三层架构的基本理论,UI层只需引用与其最直接关联的BLL层即可,而没有引用DAL层。之前在用三层架构做项目时就出现了缺少依赖项,找不到文件的问题。当时解决办法就是简单的上网查查,通过复制DAL层文件到UI层Debug文件夹下来解决的。这次用七层做项目又遇到了这个问题,这一次在解决这个问题的时候真是花费了不少时间呢。这次的解决办法没有采用复制文件的方式,而是在UI层中增加对DAL层引用。
❀❀为什么UI层还需要引用与其间接相关的DAL层呢?
【在此之前也尝试过BLL层、Facade层等凡是没有引用DAL层的都引用了一遍,但又会出现新Bug】
最本质的原因就是DAL层中的方法才是真正实现业务功能的最底层、最元(元:不可再分)始的方法,它包含与数据库直接关联的信息。按照理论点的话,只有直接依赖的层需要引用,没有间接引用。但实际上U层需要的文件应该是产生在U层debug下,在没有添加D层的引用时,生成的U层文件中没有D层真正实现功能的文件。虽然U层最直接依赖的是Facade层,但D层dll文件包含真正实现功能的方法。换句话说,Facade层中封装的方法 是对与其直接关联的层中方法的封装,是可拆分的方法;每层中的方法拆分后,最小单位的方法便是DAL层中的方法,它是不可再分的封装的方法。所以实际做项目过程中还是间接引用了D层,在生成解决方案时,D层中的文件也同时放在了U层中。这样程序在运行过程中就可以直接从UI层的Debug文件路径下找到DAL层中实现功能需求的.dll文件了。或者复制D层文件、更改输出路径到U层下。但个人还是倾向将U层添加D层引用。
到此为止,小编为您分享的“七层架构”就要告一段落啦。实践出真知,实践能让我们对理论知识有进一步的认识与理解,丰富知识的内涵。
感谢您的浏览,希望能对您有所帮助哦!