第3 章 系统架构 在进行具有一定规模的数据库应用系统开发时,如果仍采用传统的代码编写方式,将一 个功能模块的所有代码都写在Form 中的做法是非常不合适的。因为以这种方式编写的代 码,在面临需求变更时,所引发的修改往往是灾难性的。即使是一个数据库IP地址的变更 都可能会导致数十甚至上百处的代码变化(每个涉及连接数据库的代码都要修改连接字符 串)。所有的代码堆砌在一个窗体内,职责繁杂造成代码可读性、可维护性、可移植性差,违 反了单一职责原则;在面临需求变更时,必须通读所有代码,再在合适的位置做修改,违反了 对修改关闭、对扩展开放的开放-关闭原则;所有代码没有任何抽象,不对接口编程,代码耦 合度太高,无法适应较大的需求变更,违反了依赖倒转原则。 例如,第8章的在线考试系统属于中小型应用系统,该系统分为两个子系统:教师端子 系统和学生端子系统。首先,两个子系统都访问同一个数据库,其中有许多访问数据库的操 作及业务逻辑都是相同的,如果再像传统的编写方式一样将代码都放在各个Form 中,那就 无法实现代码的复用,必然造成大量的冗余,为后续扩展和维护带来麻烦。此外,本在线考 试系统面对的客户不同,其对于软件产品的经济投入也会不同,必然涉及不同的用户采用不 同类型数据库的情况,如果代码不做到充分解耦,在面对数据库类型变更时,几乎要把所有 访问数据库的代码修改一遍,显然是不现实的。 采用一些典型架构开发软件,其目的就是为了使软件代码更具可维护性、可扩展性、可 复用性,从而令系统更为灵活,可以适应任何合理的需求变更。 3.1 三层架构简介 在软件体系架构设计中,分层式结构是最常见也是最重要的一种结构,微软推荐的分层 式结构一般分为三层,即三层架构(3-TierApplication),这三层从下至上分别为:数据访问 层(DataAccessLayer,DAL)、业务逻辑层(BusinessLogicLayer,BLL)以及表示层(User Interface,UI),如图3-1所示。 1.数据访问层 数据访问层又称持久层,该层负责访问数据库,通俗点讲就是该层负责实现对数据库各 表的增、删、改、查操作,当然数据存储方式不一定是数据库,也可能是文本文件、XML文档 等。该层不负责任何业务逻辑的处理,更不涉及任何界面元素。 2.业务逻辑层 业务逻辑层又称领域层。该层是整个系统的核心,负责处理所有的业务流程,从简单的 数据有效性验证到复杂的对一整条业务链的处理。例如,商城购物,从查询商品到添加购物 68 基于C# 的管理信息系统开发(第 3 版) 图3- 1 三层架构示意图 车,再到下订单,直至付款结束等过程。当然,不排除个别软件项目业务逻辑简单,导致业务 逻辑层代码较少的情况。例如,本章由于篇幅所限,所提供的示例业务逻辑简单,就会出现 业务逻辑层“瘦小”的现象。当业务逻辑层需要访问数据库时,它会通过调用数据访问层来 实现,而不直接访问数据库。这样可以使业务逻辑层的实现与具体数据库无关,从而有效解 耦。业务逻辑层同样不涉及任何界面元素。 3. 表示层 表示层即用户界面,表示层可以是WinForm 、WebForm 甚至是控制台,该层负责用户 与系统的交互,接收用户的输入及事件触发。理想状态下,该层不应包含系统的业务逻辑, 即使有逻辑代码,也应只与界面元素有关。例如,根据用户的身份控制按钮的可用性等。具 体的业务逻辑可通过调用业务逻辑层来完成,该层不能直接调用数据访问层,更不能直接访 问数据库。 如此分层具有以下优点。 (1)分散关注:开发人员可以只关注自己所负责一层的技术实现。例如,负责数据访 问层的开发人员,可以不需要关心系统的任何业务逻辑,更不用关心界面的设计。只需要关 心所访问的数据库类型及表结构,最大限度地实现对各数据表的增、删、改、查操作即可。如 此,对于开发人员的技术要求可以降到最低,项目经理也可以根据团队成员的专长,合理为 其分配擅长的领域工作。 (2)松散耦合:三层之间呈线性调用,业务逻辑层的实现不依赖于数据访问层的具体 实现,表示层的实现同样不依赖于业务逻辑层的具体实现,可以很容易用新的实现来替换原 有层次的实现,而不会对其他层造成影响。 (3)逻辑复用:个别层代码所生成的组件(动态链接库文件DLL)可以直接被其他项目 所使用。例如,某系统最初只有WinForm 版本,随着业务扩展,逐渐有了Web版、手机版等 需求,但各版本功能一致,业务逻辑相同。这样在新建Web版和手机版项目时,只需将原 WinForm 版项目中的业务逻辑层和数据访问层组件引用到新项目中,直接使用即可,无须 再重写代码。 当然,在获得优点的同时,分层也不可避免地会付出一些“代价”,其缺点如下。 (1)降低了系统性能:原本在不采用分层的情况下,UI 可以直接访问数据库,但现在要 通过层层调用才能达到同样的目的,必然会在运行效率上有所降低。 第 3 章 系统架构 (2)容易导致级联修改:用户某些需求的变化,如用户觉得某个功能模块目前所维护 的信息不够,需要再加入一些信息,势必导致UI的变化,为了存储这些数据也会导致相应 数据表字段的增加,从而数据访问层和业务逻辑层都会受到影响,这就是级联修改。 3.简单三层架构 2 简单三层架构即基本三层架构,其结构如图3-1所示。现以一个小示例介绍简单三层 架构的代码编写方式。 示例所用数据库及业务需求如下。 要求设计一个通讯录软件,实现对联系人类型的定制以及联系人的管理,即实现对联系 人类型和联系人的增、删、改、查功能。 数据库名为MyDb,其中有两个数据表,名为LinkmanType(联系人类型)表和 Linkmen(联系人)表,表结构分别如表3-1和表3-2所示。 表3- 1 LinkmanType(联系人类型)表结构 字段名数据类型长度主键含义 typeId nvarchar 20 是联系人类型编号 typeName nvarchar 20 否联系人类型名称 表3- 2 Linkmen(联系人)表结构 字段名数据类型长度主键含义 lkmId int 4 是联系人编号(自动加1) lkmName nvarchar 10 否联系人姓名 lkmMPNum nvarchar 12 否联系人移动电话 lkmOPNum nvarchar 20 否联系人办公室电话 lkmEmail nvarchar 30 否联系人电子邮箱 lkmCompName nvarchar 50 否联系人单位名称 typeId nvarchar 20 否联系人类型编号 3.2.1 数据访问层 1.创建一个空解决方案 打开VisualStudio2019,单击“创建新项目”,在弹出的“创建新项目”对话框中的“搜索 模板”搜索框内输入“空白解决方案”,查询“空白解决方案”模板,如图3-2所示。选择查询 到的“空白解决方案”模板,单击“下一步”按钮,打开“配置新项目”窗口,修改项目名称为 “ThreLayer,(”) 选择好位置,单击“创建”按钮,完成空解决方案的创建,如图3-3所示。 2.创建数据访问层项目DAL 在图3-3中右侧的“解决方案资源管理器”子窗口中的“解决方案ThreLayer”节点上单 70 基于C# 的管理信息系统开发(第 3 版) 图3- 2 “创建新项目”对话框 图3- 3 创建后的空解决方案 击鼠标右键,选择“添加”→“新建项目”,会再次弹出类似图3-2的“添加新项目”对话框,在 其中搜索“类库”模板,选择“类库(.NETFramework)模(”) 板,单击“下一步”按钮,将项目名 称改为“DAL”,单击“创建”按钮,此时,图3-3的解决方案会变成如图3-4所示状态。 3.编写数据访问层代码 右击DAL项目中的Clas1.s文件,在弹出菜单中选择“删除”命令,将DAL默认创建 c 的Clas1.s文件删除。右击DAL项目,在弹出菜单中选择“添加”→“类”,弹出如图3-5所 c 第 3 章 系统架构 图3- 4 创建DAL 项目之后的解决方案 示的“添加新项”对话框。 图3- 5 “添加新项”对话框 在图3-5中,将名称命名为“LikmanTyp.s”,单击“添加”按钮,在DAL 项目中,会出 nec 现名为“Linec”的类文件。 kmanTyp.s 72 基于C# 的管理信息系统开发(第3 版) 在3.1节提到数据访问层的任务就是实现对数据库的操作,实现对各数据表数据的增 加、删除、修改、查询。作为LinkmanType类,其功能就是要实现对LinkmanType数据表的 增加、删除、修改、查询操作。 DAL项目中的LinkmanType.cs文件代码如下。 namespace DAL //需要手动引用System.Data 和System.Data.SqlClient 命名空间 { //LinkmanType 类默认无访问修饰符,此处必须声明为public,因BLL 在调用DAL 中该类时, //属于跨项目访问,只有声明为public 才能够被BLL 所访问。 public class LinkmanType { ///<summary> ///数据库连接字符串,server 为数据库服务器的名字或IP 地址,database 为数据库的名字, ///uid 为数据库用户名,pwd 为该用户的密码 ///</summary> public string connString = "server=(local);database=mydb;uid=test;pwd= test"; ///<summary> ///插入数据的方法 ///</summary> ///<param name="typeId">要插入的联系人类型编号</param> ///<param name="typeName">要插入的联系人类型名称</param> ///<returns>插入成功返回true,插入失败返回false</returns> public bool insert(string typeId,string typeName) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString =connString ; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText="insert into linkmantype(typeid,typename) values(@typeid,@typename)"; //参数形式的SQL 语句 //以下两句为SQL 参数赋值 cmd.Parameters.Add(new SqlParameter("@typeid", typeId)); cmd.Parameters.Add(new SqlParameter("@typename", typeName)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///修改数据的方法 ///</summary> ///<param name="typeId">要修改的联系人类型新编号</param> ///<param name="typeName">要修改的联系人类型新名称</param> ///<param name="oldTypeId">要修改的联系人类型原编号</param> 第3 章 系统架构 73 ///<returns>修改成功返回true,修改失败返回false</returns> public bool update(string typeId,string typeName,string oldTypeId) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText="update linkmantype set typeid=@typeid, typename=@typename where typeid=@oldTypeId"; cmd.Parameters.Add(new SqlParameter("@typeid", typeId)); cmd.Parameters.Add(new SqlParameter("@typename", typeName)); cmd.Parameters.Add(new SqlParameter("@oldTypeId", oldTypeId)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///删除数据的方法 ///</summary> ///<param name="typeId">要删除的联系人类型编号</param> ///<returns>删除成功返回true,删除失败返回false</returns> public bool delete(string typeId) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText = "delete from linkmantype where typeid=@typeid"; cmd.Parameters.Add(new SqlParameter("typeid", typeId)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///查询数据的方法 ///</summary> ///<param name="strWhere">strWhere 为空字符串时,代表查询表中所有数据 74 基于C# 的管理信息系统开发(第3 版) ///为非空时应传入查询依据,即where 子句的内容,如"typeName='朋友'"等</param> ///<returns>返回null 代表查询失败,返回非null 代表查询成功 ///且结果存于返回的DataTable 中</returns> public DataTable select(string strWhere) { try { string sql = "select * from linkmantype"; if (strWhere != "") { sql += " where " + strWhere; } SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; SqlDataAdapter da = new SqlDataAdapter(sql, conn); DataSet ds = new DataSet(); da.Fill(ds); return ds.Tables[0]; } catch { return null; } } } } 接下来实现Linkmen表的数据访问层代码。采用与创建LinkmanType.cs类文件同样 的方法,在DAL项目中创建Linkmen.cs类文件。在其中编写对Linkmen表的访问代码 如下。 namespace DAL //需要手动引用System.Data 和System.Data.SqlClient 命名空间 { public class Linkmen { public string connString = "server=(local);database=mydb;uid=test; pwd=test"; ///<summary> ///插入数据的方法 ///</summary> ///<param name="lkmName">姓名</param> ///<param name="lkmMPNum">移动电话</param> ///<param name="lkmOPNum">固定电话</param> ///<param name="lkmEmail">电子邮箱</param> ///<param name="lkmCompName">单位名称</param> ///<param name="typeId">联系人类型编号</param> ///<returns>插入成功返回true,插入失败返回false</returns> public bool insert(string lkmName,string lkmMPNum,string lkmOPNum, string lkmEmail,string lkmCompName,string typeId) { try { SqlConnection conn = new SqlConnection(); 第3 章 系统架构 75 conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText = "insert into linkmen(lkmname, lkmMPNum, lkmOPNum, lkmEmail, lkmCompName, typeId) values(@lkmname, @lkmMPNum, @lkmOPNum, @lkmEmail, @lkmCompName, @typeId)"; cmd.Parameters.Add(new SqlParameter("@lkmname", lkmName)); cmd.Parameters.Add(new SqlParameter("@lkmMPNum", lkmMPNum)); cmd.Parameters.Add(new SqlParameter("@lkmOPNum", lkmOPNum)); cmd.Parameters.Add(new SqlParameter("@lkmEmail", lkmEmail)); cmd.Parameters.Add(new SqlParameter("@lkmCompName", lkmCompName)); cmd.Parameters.Add(new SqlParameter("@typeId", typeId)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///修改数据的方法 ///</summary> ///<param name="lkmId">联系人编号</param> ///<param name="lkmName">姓名</param> ///<param name="lkmMPNum">移动电话</param> ///<param name="lkmOPNum">固定电话</param> ///<param name="lkmEmail">电子邮箱</param> ///<param name="lkmCompName">单位名称</param> ///<param name="typeId">联系人类型编号</param> ///<returns>修改成功返回true,修改失败返回false</returns> public bool update(string lkmId,string lkmName, string lkmMPNum, string lkmOPNum,string lkmEmail, string lkmCompName, string typeId) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText = "update linkmen set lkmname=@lkmname, lkmMPNum=@lkmMPNum, lkmOPNum=@lkmOPNum, lkmEmail=@lkmEmail, lkmCompName=@lkmCompName, typeId=@typeId where lkmid=@lkmid"; cmd.Parameters.Add(new SqlParameter("@lkmname", lkmName)); cmd.Parameters.Add(new SqlParameter("@lkmMPNum", lkmMPNum)); cmd.Parameters.Add(new SqlParameter("@lkmOPNum", lkmOPNum)); cmd.Parameters.Add(new SqlParameter("@lkmEmail", lkmEmail)); 76 基于C# 的管理信息系统开发(第3 版) cmd.Parameters.Add(new SqlParameter("@lkmCompName", lkmCompName)); cmd.Parameters.Add(new SqlParameter("@typeId", typeId)); cmd.Parameters.Add(new SqlParameter("@lkmid", lkmId)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///删除数据的方法 ///</summary> ///<param name="lkmId">要删除的联系人的编号</param> ///<returns>删除成功返回true,删除失败返回false</returns> public bool delete(string lkmId) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText = "delete from linkmen where lkmid=@lkmid"; cmd.Parameters.Add(new SqlParameter("@lkmid", lkmId)); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///查询数据的方法 ///</summary> ///<param name="strWhere">strWhere 为空字符串时,代表查询表中所有数据 ///为非空时应传入查询依据,即where 子句的内容,如"lkmName='张三'"等</param> ///<returns>返回null 代表查询失败,返回非null 代表查询成功 ///且结果存于返回的DataTable 中</returns> public DataTable select(string strWhere) { try { string sql = "select linkmen.typeid as typeid, typename, lkmname, lkmid,lkmMPNum, lkmOPNum, lkmEmail, lkmCompName from linkmen left join linkmantype on linkmen.typeid=linkmantype.typeid"; if (strWhere != "") 第3 章 系统架构 77 { sql += " where " + strWhere; } SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; SqlDataAdapter da = new SqlDataAdapter(sql, conn); DataSet ds = new DataSet(); da.Fill(ds); return ds.Tables[0]; } catch { return null; } } } } 上述代码为数据访问层的最基本实现,但还存在如下诸多不足。 (1)增加、删除、修改功能的代码基本类似,存在大量的冗余。事实上,除了所执行的 SQL语句以及所传递的SQL参数不同外,其他代码是一样的。此外,各类的查询功能实现 也极其相似,只是Select语句不同罢了。再者,每个类中都维护着同一个数据库连接字符 串,不但冗余,更会给数据库迁移带来莫大的灾难,每次数据库迁移,都要修改每个类的连接 字符串,何其麻烦! 因此,完全可以对这些冗余的代码进行精简。 (2)以各类的insert函数为例,操作联系人类型表的LinkmanType类中的insert函数 有两个形参:typeId和typeName,分别代表要插入类型的两个字段,而操作联系人表的 Linkmen类中的insert函数则有6个参数:lkmName、lkmMPNum、lkmOPNum、lkmEmail、 lkmCompName和typeId,可见,函数的形参个数与数据表的字段数是成正比的,如果一个 数据表的字段数过多,势必导致相关函数的形参数过于冗长,非常不利于调用和规范化 管理。 3.2.2 数据访问通用类库 图3-6 添加DBUtility类库 后的解决方案 解决以上第一个不足,精简冗余的数据库访问代 码,可考虑再创建一个数据库访问辅助类库,将重复的 代码进行封装。 1.创建数据访问通用类库DBUtility 右击解决方案,单击“添加”→“新建项目”,创建一 个名为“DBUtility”的类库项目。添加项目之后的解决 方案列表如图3-6所示。 2.编写数据访问通用类库中的类代码 在图3-6中,删除DBUtitlity项目中的Class1.cs类 文件,新建一个名为“DbHelperSQL.cs”的类文件, DbHelperSQL.cs类代码如下。 78 基于C# 的管理信息系统开发(第3 版) namespace DBUtility //需要手动引用命名空间System.Data 和System.Data.SqlClient { public class DbHelperSQL { public static string connString = "server=(local);database=mydb; uid=test;pwd=test"; ///<summary> ///数据增、删、改要调用的通用方法 ///</summary> ///<param name="sql">要执行的SQL 语句</param> ///<param name="sqlParams">所有要传给SQL 语句的参数集合</param> ///<returns>返回true 表示执行成功,false 为执行失败</returns> public static bool ExecuteSql(string sql,List<SqlParameter> sqlParams) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; conn.Open(); SqlCommand cmd = new SqlCommand(); cmd.Connection = conn; cmd.CommandText = sql; //遍历传过来的SQL 参数集合,将其逐一加到SqlCommand 对象的参数集合中 for(int i=0;i<sqlParams.Count ;i++) cmd.Parameters.Add(sqlParams[i]); cmd.ExecuteNonQuery(); conn.Close(); return true; } catch { return false; } } ///<summary> ///查询数据的通用方法 ///</summary> ///<param name="sql">select 语句</param> ///<returns>返回null 代表查询失败,返回非null 代表查询成功 ///且结果存于返回的DataTable 中</returns> public static DataTable Query(string sql) { try { SqlConnection conn = new SqlConnection(); conn.ConnectionString = connString; SqlDataAdapter da = new SqlDataAdapter(sql, conn); DataSet ds = new DataSet(); da.Fill(ds); //将sql 语句的查询结果填充到ds 中 return ds.Tables[0]; } catch { 第3 章 系统架构 79 return null; } } } } 如此一来,数据访问层DAL项目中的各函数,只需要调用DBUtility中的函数即可实 现对数据库的访问。 3.为数据访问层项目DAL添加引用 要在DAL中调用DBUtility中的类和函数,需将DBUtility项目引入到DAL项目中: 右击DAL项目节点下的“引用”节点,在弹出式菜单中选择“添加引用”命令,会弹出如图3-7 所示的“引用管理器”对话框。 图3-7 “引用管理器”对话框 在图3-7中,选择“项目”→“解决方案”选项,勾选DBUtility项目,单击“确定”按钮,会 发现DAL项目的引用列表中,多出一个名为“DBUtility”的引用,如图3-8所示。 图3-8 添加引用后的DAL引用列表 80 基于C# 的管理信息系统开发(第3 版) 4.修改3.2.1节中的数据访问层代码 现在,可以在DAL 项目中调用DBUtility 中所有公开(public)的类和函数。以 Linkmen类的insert函数为例,此时,insert函数可通过调用DBUtility的DbHelperSQL类 中的ExecuteSql函数来实现数据的插入,而无须书写重复冗余的代码,修改后的insert函 数如下。 public bool insert(string lkmName,string lkmMPNum,string lkmOPNum, string lkmEmail,string lkmCompName,string typeId) { string sql = "insert into linkmen(lkmname,lkmMPNum,lkmOPNum,lkmEmail, lkmCompName, typeId)values(@lkmname, @lkmMPNum, @lkmOPNum, @lkmEmail, @lkmCompName, @typeId)"; List<SqlParameter> sqlParams = new List<SqlParameter>(); //以下六句代码把要传给SQL 语句的参数存入集合,以便传递给函数 sqlParams.Add(new SqlParameter("@lkmname", lkmName)); sqlParams.Add(new SqlParameter("@lkmMPNum", lkmMPNum)); sqlParams.Add(new SqlParameter("@lkmOPNum", lkmOPNum)); sqlParams.Add(new SqlParameter("@lkmEmail", lkmEmail)); sqlParams.Add(new SqlParameter("@lkmCompName", lkmCompName)); sqlParams.Add(new SqlParameter("@typeId", typeId)); //调用通用类库中的ExecuteSql 方法执行SQL 语句 return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } 由上述代码可见,在insert函数中,已无须再编写SqlConnection、SqlCommand对象的 创建和属性赋值、方法调用等代码。同样,update、delete等函数也不必再写这些重复代码, 代码复用度更高。此外,由于连接字符串已写在了DbHelperSQL类中,不会在其他类中出 现,对于数据库迁移所造成的修改量降到了最低———只需修改DbHelperSQL中的连接字 符串即可。完整的数据访问层代码将在解决第二个不足之后给出。 3.2.3 实体类库 针对第二个不足———函数参数个数因数据表字段过多而变得复杂的问题,可以利用面 向对象的思想,将数据表封装成类。如对于LinkmanType 表,可以定义Model. LinkmanType类,其中包括两个成员变量typeId和typeName,分别对应LinkmanType表 的两个字段。而对于Linkmen表,可以定义Model.Linkmen类,其中包含7个成员变量 lkmId、lkmName、lkmMPNum、lkmOPNum、lkmEmail、lkmCompName和typeId,分别对 应Linkmen表的7个字段。这样在设计对应的insert和update函数时,可以用对应的类类 型来作为形参,例如Linkmen类的insert函数,只需用以下方式定义即可。 public bool insert(Model.Linkmen mLKM); 如此大大缩减了形参数量,简化了函数结构。 可以创建一个专门的类库,用于设计这些针对数据表结构而产生的类,我们称为实体 (Model)类库。 1.创建实体类库Model 右击解决方案,单击“添加”→“新建项目”,创建一个名为“Model”的类库项目。添加项 第3 章 系统架构 81 目之后的解决方案列表如图3-9所示。 图3-9 添加Model之后的解决方案资源管理器 2.编写实体类库中的类代码 在图3-9中,删除Model项目中的Class1.cs类文件,新建两个分别名为“LinkmanType.cs” 和“Linkmen.cs”的类文件,各自代码如下。 LinkmanType.cs类代码如下。 namespace Model { public class LinkmanType { public string typeId; public string typeName; } } Linkmen.cs类代码如下。 namespace Model { public class Linkmen { public string lkmId; public string lkmName; public string lkmMPNum; public string lkmOPNum; public string lkmEmail; public string lkmCompName; public string typeId; } } 3.修改数据访问层代码 首先,与引用DBUtility项目相同的方法,将Model项目引入到DAL项目中。 修改LinkmanType.cs类文件如下。 82 基于C# 的管理信息系统开发(第3 版) namespace DAL //需要引入System.Data 和System.Data.SqlClient 命名空间 { public class LinkmanType { public bool insert(Model.LinkmanType mLKMT) { string sql= "insert into linkmantype(typeid,typename)values (@typeid,@typename)"; List<SqlParameter > sqlParams=new List<SqlParameter>(); sqlParams.Add(new SqlParameter("@typeid", mLKMT.typeId)); sqlParams.Add(new SqlParameter("@typename", mLKMT.typeName)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public bool update(Model.LinkmanType mLKMT,string oldTypeId) { string sql= "update linkmantype set typeid=@typeid, typename=@typename where typeid=@oldTypeId"; List<SqlParameter > sqlParams=new List<SqlParameter>(); sqlParams.Add(new SqlParameter("@typeid", mLKMT.typeId)); sqlParams.Add(new SqlParameter("@typename", mLKMT.typeName)); sqlParams.Add(new SqlParameter("@oldTypeId", oldTypeId)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public bool delete(string typeId) { string sql= "delete from linkmantype where typeid=@typeid"; List<SqlParameter> sqlParams=new List<SqlParameter>(); sqlParams.Add(new SqlParameter("typeid",typeId)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public DataTable select(string strWhere) { string sql = "select * from linkmantype"; if (strWhere != "") { sql += " where " + strWhere; } return DBUtility.DbHelperSQL.Query(sql); } } } 修改Linkmen类文件如下。 namespace DAL { public class Linkmen { public bool insert(Model.Linkmen mLKM) { string sql = "insert into linkmen(lkmname,lkmMPNum,lkmOPNum, lkmEmail, lkmCompName,typeId)values(@lkmname,@lkmMPNum,@lkmOPNum, 第3 章 系统架构 83 @lkmEmail,@lkmCompName,@typeId)"; List<SqlParameter> sqlParams = new List<SqlParameter>(); sqlParams.Add(new SqlParameter("@lkmname", mLKM.lkmName)); sqlParams.Add(new SqlParameter("@lkmMPNum", mLKM.lkmMPNum)); sqlParams.Add(new SqlParameter("@lkmOPNum", mLKM.lkmOPNum)); sqlParams.Add(new SqlParameter("@lkmEmail", mLKM.lkmEmail)); sqlParams.Add(new SqlParameter("@lkmCompName", mLKM.lkmCompName)); sqlParams.Add(new SqlParameter("@typeId", mLKM.typeId)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public bool update(Model.Linkmen mLKM) { string sql = "update linkmen set lkmname=@lkmname, lkmMPNum=@lkmMPNum,lkmOPNum=@lkmOPNum,lkmEmail=@lkmEmail, lkmCompName=@lkmCompName,typeId=@typeId where lkmid=@lkmid"; List<SqlParameter> sqlParams = new List<SqlParameter>(); sqlParams.Add(new SqlParameter("@lkmname", mLKM.lkmName)); sqlParams.Add(new SqlParameter("@lkmMPNum", mLKM.lkmMPNum)); sqlParams.Add(new SqlParameter("@lkmOPNum", mLKM.lkmOPNum)); sqlParams.Add(new SqlParameter("@lkmEmail", mLKM.lkmEmail)); sqlParams.Add(new SqlParameter("@lkmCompName", mLKM.lkmCompName)); sqlParams.Add(new SqlParameter("@typeId", mLKM.typeId)); sqlParams.Add(new SqlParameter("@lkmid", mLKM.lkmId)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public bool delete(string lkmId) { string sql= "delete from linkmen where lkmid=@lkmid"; List<SqlParameter> sqlParams = new List<SqlParameter>(); sqlParams.Add(new SqlParameter("@lkmid", lkmId)); return DBUtility.DbHelperSQL.ExecuteSql(sql, sqlParams); } public DataTable select(string strWhere) { string sql = "select linkmen.typeid as typeid,typename,lkmname, lkmid,lkmMPNum, lkmOPNum,lkmEmail,lkmCompName from linkmen left join linkmantype on linkmen.typeid=linkmantype.typeid"; if (strWhere != "") { sql += " where " + strWhere; } return DBUtility.DbHelperSQL.Query(sql); } } } 上述LinkmanType类和Linkmen类,明显较上一版本要精简得多,函数实现得到精 简,函数参数也得到了精简。此代码为本书简单三层架构终态的数据访问层代码结构。其 中,DAL调用了DBUtility项目的代码,但DBUtility类库不能称为一层,它的存在只是提 供一套通用的访问数据库的方法。而Model类库更不能称为一层,在后面章节中可以看 84 基于C# 的管理信息系统开发(第3 版) 到,Model实际上是贯穿了数据访问层、业务逻辑层和表示层这三层的,因为三层均要调用 图3-10 添加BLL之后的解决方案 资源管理器 Model中的类。 3.2.4 业务逻辑层 1.创建业务逻辑层项目BLL 右击解决方案,单击“添加”→“新建项目”,创建一 个名为“BLL”的类库项目。添加项目之后的解决方案 列表如图3-10所示。 2.编写业务逻辑层代码 先引用DAL 和Model项目,删除BLL 项目中的 Class1.cs文件,然后新建名为“LinkmanType.cs”的类文 件,以实现联系人类型管理的业务逻辑。联系人类型管 理的业务逻辑非常简单,只有基本的对联系人类型的增 加、删除、修改、查询功能。因此,BLL的LinkmanType类 中的代码也只是简单地调用DAL中的LinkmanType类 来实现这些功能。 LinkmanType.cs类文件代码如下。 namespace BLL { public class LinkmanType { DAL.LinkmanType dLKMT = new DAL.LinkmanType(); public bool insert(Model.LinkmanType mLKMT) { return dLKMT.insert(mLKMT); } public bool update(Model.LinkmanType mLKMT, string oldTypeId) { return dLKMT.update(mLKMT, oldTypeId); } public bool delete(string typeId) { return dLKMT.delete(typeId); ; } public DataTable select(string strWhere) { return dLKMT.select(strWhere); } } } 现在来实现联系人管理的业务逻辑层代码,与联系人类型管理的业务逻辑相比,在实现 联系人增加、删除、修改、查询功能之外,还需实现随机抽取一名联系人的功能,这就是一种 业务逻辑,需在业务逻辑层实现。 在BLL项目中新建名为“Linkmen.cs”的类文件,添加代码如下。 第3 章 系统架构 85 namespace BLL { public class Linkmen { DAL.Linkmen dLKM = new DAL.Linkmen(); public bool insert(Model.Linkmen mLKM) { return dLKM.insert(mLKM); } public bool update(Model.Linkmen mLKM) { return dLKM.update(mLKM); } public bool delete(string stuId) { return dLKM.delete(stuId); } public DataTable select(string strWhere) { return dLKM.select(strWhere); } ///<summary> ///随机抽取一名联系人 ///</summary> ///<returns>返回抽取到的联系人对象</returns> public Model.Linkmen randomFriend() { DataTable dt=select(""); if (dt.Rows.Count > 0) { Random random = new Random(); int currNum = random.Next(0, dt.Rows.Count); Model.Linkmen mLKM = new Model.Linkmen(); mLKM.lkmId = dt.Rows[currNum]["lkmid"].ToString(); mLKM.lkmName = dt.Rows[currNum]["lkmname"].ToString(); mLKM.lkmMPNum = dt.Rows[currNum]["lkmmpnum"].ToString(); mLKM.lkmOPNum = dt.Rows[currNum]["lkmopnum"].ToString(); mLKM.lkmEmail= dt.Rows[currNum]["lkmemail"].ToString(); mLKM.lkmCompName = dt.Rows[currNum]["lkmcompname"].ToString(); mLKM.typeId = dt.Rows[currNum]["typeid"].ToString(); return mLKM; } else { return null; } } } } 86 基于C# 的管理信息系统开发(第 3 版) 可见随着业务逻辑的复杂度增加,业务逻辑层代码也会相应增加。 3.2.5 表示层 1. 创建表示层项目UI 1节提到表示层其实就是用户界面层,所以表示 层的项目类型不是DAL 、BLL 这样的类库,而是 Windows窗体应用程序。 右击解决方案,选择“添加”→“新建项目”命令,打 开“添加新项目”对话框,搜索“Windows窗体应用”模 板,选择“Windows窗体应用(.NETFramework)模(”) 板,单击“下一步”按钮,将项目名称改为“UI,(”) 单击“创 建”按钮。此时,解决方案资源管理器中的项目结构如 图3-11 所示。 3. 2. 编写表示层代码 表示层需要引用BLL 和Model项目,无须引用 DAL 。删除UI 项目中的Foc右击 rm1.s窗体文件后, UI 项目,在弹出式菜单中选择“添加”→“窗体 (Windows窗体)命(”) 令,在弹出的“添加新项”窗体中, 将名称改为MainForm.s,单击“添加” c按钮完成窗体的 添加。用同样的方法再添加四个Windows窗体,分别图3-11 添加UI 之后的解决方案 命名为LinkmanTypeManae.cs、LinkmanTypeEdit. 资源管理器 cs、nnges以及 gLikmeEdtc LikmenMaacnni.s。各窗体 的作用如表3-3所示。(.) 表3- 3 各窗体作用 窗体名 MainForm LinkmanTypeManage LinkmanTypeEdit LinkmenManage LinkmenEdit 作用 入口主窗体,程序的MDI 父窗体 联系人类型管理窗体,显示联系人类型列表并且是编辑入口 联系人类型编辑窗体 联系人管理窗体,显示联系人列表并且是编辑入口 联系人编辑窗体 将UI 项目中的Progam.s文件的Main方法中的Appiain.n(nwFrm1()) 语 rclctoRueo 句改为Appliain.n(nwiFrm()), 指明应用程序运行时首先运行MaiFrm ctoRueManono 窗体。接 下来设置MainForm 窗体的若干属性及其值,如表3-4所示。