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

解耦WebUI层与EntityFramework

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

解耦WebUI层与数据层

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

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

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

image

其中,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的数据库初始化代码,调用业务层的方法即可:image

这里的业务类其实就做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也创建出来。然后创建数据库业务类工厂,用于快速初始化业务类,具体文档结构如下:

image

该工厂的代码比较简单:

  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 
业务层工厂

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

image

调整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模板来自动生成。此处不再详细赘述,处理逻辑在数据层实现的章节里详细提过。

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

image

业务层的完善和补充

在实体业务类中,初始化工作单元对象都是在方法中单独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)

但会发现报错,如下图:

image

原因是,此时的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结合,共同支撑数据层。

截止本章节,项目源码下载:点击下载(存在百度云盘中)

原文地址:https://www.cnblogs.com/matrixkey/p/5633010.html