七色花基本权限系统(13)- 业务层的设计和实现

解耦WebUI层与EntityFramework

在还未实现实体仓储时,登录功能是在控制器中直接初始化EF数据库上下文来实现的,这样也导致WebUI层必须引用EntityFramework。在完成数据层的设计和实现之后,控制器中不再直接使用EF数据库上下文对象,而是通过工作单元去调用实体仓储,其实到了这一步就可以让WebUI层不再依赖EntityFramework。从WebUI层中通过nuget管理的方式移除EF,但要注意的是,EF包含2个dll,其中的EntityFramework.SqlServer.dll需要手动拷贝到WebUI的bin目录下,作为识别数据库管道。

解耦WebUI层与数据层

业务层的必要性就不谈了。

由于数据层分库、分实体仓储,所以业务层也同样需要分库分业务类。

先搭建业务层,并手动写业务类,让WebUI层和数据层解耦。

其中,BaseBll类很简单,目前只是一个空的业务基类:

  1 /// <summary>
  2 /// 业务基类
  3 /// </summary>
  4 /// <typeparam name="TEntity">实体类型</typeparam>
  5 public abstract class BaseBll<TEntity> where TEntity : class
  6 {
  7 }

文件夹Other下是数据库初始化类,因为WebUI中的Global.asax中原先调用的是数据实现层的方法,因此需要在业务层中包装。该初始化类代码如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6
  7 using S.Framework.DataAchieve.EntityFramework.Initializes;
  8
  9 namespace S.Framework.BLL.Other
 10 {
 11     /// <summary>
 12     /// 数据库初始化操作类
 13     /// </summary>
 14     public static class DatabaseInitializer
 15     {
 16         /// <summary>
 17         /// 数据库初始化
 18         /// </summary>
 19         public static void Initialize()
 20         {
 21             new MasterDatabaseInitializer().Initialize(S.Framework.DatabaseConfig.IsMigrateDatabase);
 22         }
 23     }
 24 }
 25 

数据库初始化包装类

然后调整Global.asax的数据库初始化代码,调用业务层的方法即可:

这里的业务类其实就做2件事情:

1)封装业务(与数据库访问无关)

2)调用数据层

在WebUI层中,原先有2处位置调用了数据层。第一处是AccountController中的登录功能,另一处是TestController中的EF数据插入测试功能。把这2处的代码,移动到业务层中就行。插入测试代码之前没贴出来,这次一并贴一下。于是SysUserBll的代码如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6
  7 using S.Framework.Entity.Master;
  8
  9 namespace S.Framework.BLL.Powerful
 10 {
 11     /// <summary>
 12     /// SysUser 业务实现
 13     /// </summary>
 14     public class SysUserBll : BaseBll<SysUser>
 15     {
 16         /// <summary>
 17         /// 根据用户名获取用户实体
 18         /// </summary>
 19         /// <param name="userName">用户名</param>
 20         /// <returns>用户实体</returns>
 21         public SysUser GetByUserName(string userName)
 22         {
 23             using (var unit = new S.Framework.DataAchieve.EntityFramework.UnitOfWork(S.Framework.DatabaseConfig.ConnectionStringNames))
 24             {
 25                 return unit.Master.SysUser.GetByUserName(userName);
 26             }
 27         }
 28
 29         /// <summary>
 30         /// 测试以EF方式插入指定数量的用户数据
 31         /// </summary>
 32         /// <param name="count">要插入数据库的用户数据量</param>
 33         /// <returns>操作结果</returns>
 34         public object TestForEFInsert(int count = 1000)
 35         {
 36             long cost = 0;
 37             List<string> msg = new List<string>();
 38             System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
 39             sw.Start();
 40             using (var unit = new S.Framework.DataAchieve.EntityFramework.UnitOfWork(S.Framework.DatabaseConfig.ConnectionStringNames))
 41             {
 42                 sw.Stop();
 43                 cost += sw.ElapsedMilliseconds;
 44                 msg.Add("初始化工作单元完成,耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒;");
 45
 46                 sw.Restart();
 47                 var rep = unit.Master.SysUser;
 48                 sw.Stop();
 49                 cost += sw.ElapsedMilliseconds;
 50                 msg.Add("初始化用户仓储完成,耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒;");
 51
 52                 sw.Restart();
 53                 var users = this.CreateUsers(count);
 54                 sw.Stop();
 55                 cost += sw.ElapsedMilliseconds;
 56                 msg.Add("初始化" + count + "条用户实体对象完成,耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒;");
 57
 58                 sw.Restart();
 59                 rep.AddRange(users);
 60                 sw.Stop();
 61                 cost += sw.ElapsedMilliseconds;
 62                 msg.Add("将" + count + "条用户实体对象附加至db,耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒;");
 63
 64                 sw.Restart();
 65                 int result = unit.Commit();
 66                 sw.Stop();
 67                 cost += sw.ElapsedMilliseconds;
 68                 msg.Add("执行提交,影响行数[" + result + "],耗时:" + sw.ElapsedMilliseconds.ToString() + "毫秒;");
 69             }
 70
 71             return new { success = true, cost = cost, msg = string.Join("<br />", msg) };
 72         }
 73
 74         /// <summary>
 75         /// 组装指定数量的用户实体
 76         /// </summary>
 77         /// <param name="count">需要组装的用户实体数量</param>
 78         /// <returns>用户实体集合</returns>
 79         private IEnumerable<S.Framework.Entity.Master.SysUser> CreateUsers(int count)
 80         {
 81             List<S.Framework.Entity.Master.SysUser> entities = new List<Entity.Master.SysUser>();
 82             DateTime dt = DateTime.Now;
 83             for (int i = 0; i < count; i++)
 84             {
 85                 entities.Add(new S.Framework.Entity.Master.SysUser { ID = Guid.NewGuid().ToString(), UserName = "test", Password = "123456", CreateUser = "admin", CreateDate = dt });
 86             }
 87
 88             return entities;
 89         }
 90     }
 91 }
 92 

用户业务类

然后调整一下AccountController和TestController中的代码,通过调用业务类方法来完成原先的功能。

这里说一点,业务类中会存在部分方法,仅仅是对数据层方法的调用,不含任何业务,这种情况会让人觉得“还要去中间包装一层反而很麻烦(很多时候需要额外创建数据传输模型)”。正规项目更多考虑的是可维护性、健壮性、可扩展性,MVC时代模型为王,千万别省。

调整之后,记得移除“WebUI层中对数据层的引用”,增加“WebUI层对业务层的引用”,而业务层当然需要引用数据层(引用数据接口和数据实现,不用引用数据核心)。

这样,WebUI层和数据层就没有依赖关系了。

业务层的设计

跟数据层的设计类似,要能这样调用业务方法:业务层工厂.数据库标识(其实是该数据库下的业务类的工厂对象).各实体业务类.业务方法

业务层的实现

业务类SysUserBll已经有了,把SysRoleBll也创建出来。然后创建数据库业务类工厂,用于快速初始化业务类,具体文档结构如下:

该工厂的代码比较简单:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6
  7 namespace S.Framework.BLL.PowerfulFactories
  8 {
  9     /// <summary>
 10     /// Master 业务实现类工厂对象
 11     /// </summary>
 12     public class MasterPowerfulFactory
 13     {
 14         /// <summary>
 15         /// SysUser 业务实现类对象
 16         /// </summary>
 17         public Powerful.SysUserBll SysUser
 18         {
 19             get { return new Powerful.SysUserBll(); }
 20         }
 21
 22         /// <summary>
 23         /// SysRole 业务实现类对象
 24         /// </summary>
 25         public Powerful.SysRoleBll SysRole
 26         {
 27             get { return new Powerful.SysRoleBll(); }
 28         }
 29     }
 30 }
 31 

数据库业务类工厂

最后创建业务层工厂,用于快速初始化“数据库业务类工厂”:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6
  7 namespace S.Framework.BLL
  8 {
  9     /// <summary>
 10     /// 业务工厂
 11     /// </summary>
 12     public class BllFactory
 13     {
 14         /// <summary>
 15         /// Master 业务实现类工厂对象
 16         /// </summary>
 17         public static PowerfulFactories.MasterPowerfulFactory Master
 18         {
 19             get { return new PowerfulFactories.MasterPowerfulFactory(); }
 20         }
 21
 22     }
 23 }
 24 

业务层工厂

此时,整个业务层结构如下图:

调整WebUI层控制器调用业务类的方式,如下:

  1 //这里不用new业务类了,通过业务层工厂直接使用业务类即可
  2 S.Framework.BLL.Powerful.SysUserBll UB = S.Framework.BLL.BllFactory.Master.SysUser;
  3 var entity = UB.GetByUserName(model.UserName);
  4
  5 //TestController中对业务类的调用也是同样的改法

编译运行,登录测试。

利用T4模板自动生成业务类及工厂

和数据层一样,这些业务类和工厂,也可以通过T4模板来自动生成。此处不再详细赘述,处理逻辑在数据层实现的章节里详细提过。

模板代码也不贴了,下本章节源代码看吧。此时目录结构如下图:

业务层的完善和补充

在实体业务类中,初始化工作单元对象都是在方法中单独new的,这可以统一起来。

在业务层根目录下创建类,名称IUnitOfWorkFactory,其代码如下:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Threading.Tasks;
  6
  7 using S.Framework.DataInterface;
  8
  9 namespace S.Framework.BLL
 10 {
 11     internal static class IUnitOfWorkFactory
 12     {
 13         /// <summary>
 14         /// 获取工作单元实例
 15         /// </summary>
 16         public static IUnitOfWork UnitOfWork
 17         {
 18             get
 19             {
 20                 return new S.Framework.DataAchieve.EntityFramework.UnitOfWork(S.Framework.DatabaseConfig.ConnectionStringNames);
 21             }
 22         }
 23     }
 24 }
 25 

工作单元工厂

然后将原先的

  1 using (var unit = new S.Framework.DataAchieve.EntityFramework.UnitOfWork(S.Framework.DatabaseConfig.ConnectionStringNames))

修改成

  1 using (var unit = IUnitOfWorkFactory.UnitOfWork)

但会发现报错,如下图:

原因是,此时的unit是工作对象接口类型(IUnitOfWork),而不是工作单元类型(UnitOfWork),而在该接口中,当初在设计工作单元接口时只定义了“开启事务、提交、回滚”3个方法,没有定义“仓储工厂”。此时自然就无法识别。

解决的方法也很简单,在接口中定义“仓储工厂”即可,其实现已经在工作单元中完成了。这个定义本应该在前面的工作单元设计与实现章节中完成,不小心漏了。

在数据接口层(S.Framework.DataInterface),增加T4模板,用于生成工作单元接口,其代码如下:

  1 <#+
  2 // <copyright file="IUnitOfWork.tt" company="">
  3 //  Copyright ? . All Rights Reserved.
  4 // </copyright>
  5
  6 public class IUnitOfWork : CSharpTemplate
  7 {
  8     private List<string> _prefixNameList;
  9
 10     public IUnitOfWork(List<string> prefixNameList)
 11     {
 12         _prefixNameList = prefixNameList;
 13     }
 14 	public override string TransformText()
 15 	{
 16 		base.TransformText();
 17 #>
 18 using System;
 19 using System.Collections.Generic;
 20 using System.Linq;
 21 using System.Text;
 22
 23 using S.Framework.DataInterface.IRepositoryFactories;
 24
 25
 26 namespace S.Framework.DataInterface
 27 {
 28 	/// <summary>
 29     /// 工作单元接口
 30     /// </summary>
 31     public partial interface IUnitOfWork
 32     {
 33         #region 生成仓储接口工厂实例
 34
 35 <#+
 36             foreach(string item in _prefixNameList)
 37             {
 38 #>
 39         /// <summary>
 40         /// <#= item #> 仓储接口工厂
 41         /// </summary>
 42         I<#= item #>IRepositoryFactory <#= item #> { get; }
 43 <#+
 44             }
 45 #>
 46
 47         #endregion
 48     }
 49 }
 50 <#+
 51         return this.GenerationEnvironment.ToString();
 52 	}
 53 }
 54 #>
 55 

工作单元接口模板文件(IUnitOfWork)

然后调整执行器文件,在头部include工作单元接口模板之后,在文件末尾增加一段:

  1 //工作单元接口文件
  2 string fileName2 = "IUnitOfWork.Generate.cs";
  3 IUnitOfWork iUnitOfWork = new IUnitOfWork(prefixModelTypes.Keys.ToList());
  4 iUnitOfWork.Output.Encoding = Encoding.UTF8;
  5 string path2 = string.Format(@"{0}\", projectPath) + fileName2;
  6 iUnitOfWork.RenderToFile(path2);

运行一下,工作单元接口就补全了。这样前面的报错就解决了。

到此,业务层基本就完成了。后面实现各模块功能的时候,其实还需要进一步完善,比如封装业务处理结果类型。这就留到后面吧。

现在,数据层 => 业务层 => 界面层 已经成形了。

下一章节,将演示ORM辅助利器Dapper,并将其与EF结合,共同支撑数据层。

时间: 2024-10-13 09:17:21

七色花基本权限系统(13)- 业务层的设计和实现的相关文章

七色花基本权限系统(3)- 利用EasyUI进行首页布局

EasyUI EasyUI是基于JQuery库的偏js轻型前端UI框架,不了解的读者可以参考官网地址. 在项目中增加JQuery和EasyUI,并在布局页中引用.为了结构清晰,方便日后维护和升级,可以在Scripts下创建jquery文件夹和jquery-easyui文件夹.这里选择1.11.0的JQuery和1.4.3的EasyUI. 特别说明一下,我已经修复了该版本的几个(只能通过修改源码来修正的)bug,并在updateLog.txt文件中做了修复记录. 图标库 再引入2套通用的图标库,以

七色花基本权限系统(4) - 初步使用EntityFramework实现用户登录

这篇日志将演示: 1.利用EF的Code First模式,自动创建数据库 2.实现简单的用户登录(不考虑安全仅仅是密码验证) 为什么选择EntityFramework 第一,开发常规中小型系统,能够提高开发效率. 针对小型系统,ORM提高开发效率那是立竿见影.而且linq+lambda的用户体验非常棒,让代码可读性大大增强.即使把linq写得很烂,小系统也无所谓. 针对中型系统,在对ORM有一定了解并且对linq to entity也掌握一定技巧的基础上,使用ORM还是可以提高开发效率. 第二,

七色花基本权限系统(10)- 数据接口的实现

为什么要设计数据接口 首先来看一下3层的主要逻辑:数据层 => 业务层 => 应用层.作为通用的项目模板,其中最可能按需而大变的就是数据层,因为不同的项目,使用的数据库.数据驱动技术,是很有可能不同的.项目A,MsSql+EF(就像我正在演示的),项目B,也用这套模板,但变成了MySql+ADO.NET,那么就要尽可能地维持项目的整洁,减少需要修改的代码的量和范围.最佳的做法自然就是"数据层暴露出接口,业务层不关心数据实现". 要设计哪些接口 凡是数据实现层要暴露给业务逻辑

七色花基本权限系统(14)- 实现EntityFramework和Dapper的混搭

Dapper是什么 Dapper是一款轻量级的微ORM,其核心是实现了"将查询结果映射到指定数据模型",因此可以抛开DataSet.DataTable等数据集对象,以强类型的方式使用查询数据结果.Dapper是开源的,它的GitHub地址在这里:https://github.com/StackExchange/dapper-dot-net,本章节中选择1.4.0版本的Dapper下的.NET45下的核心类,点击下载该核心类:SqlMapper. 为什么要用Dapper来配合Entity

七色花基本权限系统(8)- 实现实体层和核心层解耦

经过前面的工作,系统正变得越来越清晰. 现在有一个问题需要解决.当需要额外增加一个数据表时,我们需要做的操作是: 在实体层创建实体并编译实体层 在核心层运行T4 配置实体 将实体对象关联给EF数据库上下文(定义DbSet) 将实体配置注册给EF配置对象 这过于繁琐,最后2个步骤,强行地把实体关联在EF数据库上下文里,导致了两者的耦合.这篇日志将演示如何将最后2个步骤省略,解放EF数据库上下文,不用手动关联实体对象,不用手动注册实体配置. 回顾和解释 最后两步还记得吗? 通过定义DbSet对象将实

七色花基本权限系统(6)- 让EntityFramework Code First自动合并/迁移/数据初始化

在前一章节里,我们已经能够对映射字段进行配置了.但在演示中,我们通过删除原数据库让EF重新创建的方式,才把新的字段信息更新(其实是破而后立)到了数据库.这显然无法让人接受.在这篇日志里,将演示"在实体类发生改变时如何自动更新数据库中的表结构"和"在EF创建数据库的时候如何初始化一批数据". 合并/迁移 合并是指"新的实体模型映射到数据库中,更新其结构",例如: 新增了实体类,那在数据库中就是新增数据表. 删除了实体类,那在数据库中就是删除数据表.

七色花基本权限系统(5)- 实体配置的使用和利用T4自动生成实体配置

在前面的章节里,用户表的结构非常简单,没有控制如何映射到数据库.通常,需要对字段的长度.是否可为空甚至特定数据类型进行设置,因为EntityFramework的默认映射规则相对而言比较简单和通用.在这篇日志里,将演示如何对数据实体进行映射配置,并利用T4模板自动创建映射配置类文件. 配置方式 EntityFramework的实体映射配置有2种. 第一种是直接在实体类中以特性的方式进行控制,这些特性有部分是EF实现的,也有部分是非EF实现的.也就是说,在数据实体层不引用EF的情况下,只能使用不全的

七色花基本权限系统(2)- MVC项目搭建及初步调整

新建ASP.NET MVC项目,解决方案名称Seven,MVC项目名称S.Framework.WebClient.如下图: 创建MVC项时会让你选择身份验证方式,选择无吧,咱要空白干净的MVC项目.是否勾选单元测试随自己喜欢就好. 现在把MVC项目中我们不需要用到的部分移除掉,比如自带的bootstrap.jquery.jquery Validation等,可通过nuget工具来移除.如下图: (如果你没装nuget,请google) 请依次移除bootstrap.Microsoft jQuer

权限系统概要

前言: 权限往往是一个极其复杂的问题,但也可简单表述为这样的逻辑表达式:判断"Who对What(Which)进行How的操作"的逻辑表达式是否为真.针对不同的应用,需要根据项目的实际情况和具体架构,在维护性.灵活性.完整性等N多个方案之间比较权衡,选择符合的方案. 目标: 直观,因为系统最终会由最终用户来维护,权限分配的直观和容易理解,显得比较重要,系统不辞劳苦的实现了组的继承,除了功能的必须,更主要的就是因为它足够直观. 简单,包括概念数量上的简单和意义上的简单还有功能上的简单.想用