• 七色花基本权限系统(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结合,共同支撑数据层。

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

  • 相关阅读:
    . net core的发布
    Using Redis Cache for session data storage in ASP.NET Core
    WCF之双工服务
    值得参考的.net core 的博客
    一、获取EF
    limit 基本实现方式
    Session机制详解及分布式中Session共享解决方案
    ASP.NET Core 中的基于角色的授权ASP.NET Core 中的基于角色的授权
    WCF之双工服务
    苹果公司的粉丝转抄
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5633010.html
Copyright © 2020-2023  润新知