zoukankan      html  css  js  c++  java
  • 七色花权限管理系统(7)- 实现数据仓储和利用T4自动生成实体仓储

    基于EntityFramework的数据访问层,我计划细分成数据核心、数据接口和数据实现。

    其中数据核心已经在前几个章节中创建,那么在这篇日志里,将演示数据仓储(接口和实现)的实现及封装架构的整个过程。

    仓储的作用

    仓储的概念请自行搜索了解,我认为它最大的作用就是解耦。没有仓储,就只能直接使用EF数据库上下文对象来操作数据库,而为了“能使用EF数据库上下文对象来操作数据库(各实体库)”,就必须把实体关联给EF数据库上下文。例如MasterEntityContext中的DbSet属性:

      1 /// <summary>
      2 /// 用户
      3 /// </summary>
      4 public DbSet<SysUser> Users { get; set; }
      5 
      6 /// <summary>
      7 /// 角色
      8 /// </summary>
      9 public DbSet<SysRole> Roles { get; set; }

    只有这样,我们才能在初始化EF数据库上下文对象之后,通过对象来访问相应的实体库,比如登录判定中那句代码:

      1 var db = new MasterEntityContext("matrixkey");
      2 var entity = db.Users.Where(w => w.UserName == model.UserName).FirstOrDefault();

    上述代码中的db.Users,其中Users对象,就是EF数据库上下文中定义的属性。

    在WebUI层直接使用数据访问层的核心对象,这无疑是非常不正确的。在后面的日志中,我们将增加业务逻辑层,作为数据访问层和WebUI层的桥梁。

    但是若没有仓储,即使有了业务逻辑层,仍然只能通过直接使用EF数据库上下文对象的方式来操作数据库,这就需要:

    1、在业务层中暴露EF数据库上下文对象

    2、在业务层中引用EntityFramework

    这依旧很糟糕,有了EF数据库上下文对象,就等于有了一切。不能让业务层拥有一切,也不能让业务层引用EntityFramework。

    于是就需要仓储作为“EF数据库上下文和业务层之间的桥梁”。对于业务层而言,仓储才是真正的数据访问层,业务层根本不知道EF数据库上下文的存在,它不关心数据访问层中究竟是谁提供了数据库访问的能力,无论是EF或是dapper或是源生的ADO.NET,它只关心数据访问层是否执行了它要操作的动作。

    总之,业务层是无关数据驱动的,不能把EF暴露给业务层。仓储的实现,解耦了数据核心层和业务层,并且在下一章节中,还将解耦数据实体层和数据核心层。可见仓储最大的作用就是解耦。

    用户仓储的简易实现

    在解决方案下新建类库项目,名称S.Framework.DataAchieve。再创建相应的文件夹进行区分,结构如下图:

    image

    在Master下创建用户仓储类,名称SysUserRepository。上面说过,仓储是桥梁,是为其他需要获取数据的项目层提供代理服务的,那么在仓储类中,需要定义EF数据库上下文对象,以及通过该上下文对象对数据库的各种操作的方法的封装。

    毫无疑问,数据实现层需要引用“数据实体层、数据核心层”。

    这里以“用户登录功能”作为范例,一步一步演示用户仓储中的代码的演变:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 using System.Data.Entity;
      7 using System.Data.Entity.Infrastructure;
      8 
      9 using S.Framework.Entity.Master;
     10 using S.Framework.DataCore.EntityFramework.EntityContexts;
     11 
     12 namespace S.Framework.DataAchieve.EntityFramework.Repositories.Master
     13 {
     14     /// <summary>
     15     /// 用户仓储
     16     /// </summary>
     17     public class SysUserRepository
     18     {
     19 
     20     }
     21 }
     22 
    用户仓储类-0

    定义数据库上下文对象:

      1 /// <summary>
      2 /// 数据库上下文
      3 /// </summary>
      4 private MasterEntityContext Db
      5 {
      6     get;
      7     set;
      8 }
    数据库上下文对象

    数据库上下文对象作为最核心的内容,只允许在当前类中被设置和获取,因此该属性的访问修饰符设置为private。

    定义用户仓储的构造方法,并在方法中初始化数据库上下文对象:

      1 public SysUserRepository()
      2 {
      3     this.Db = new MasterEntityContext("matrixkey");
      4 }

    “用户登录功能”中需要根据用户名获取用户实体,可以在仓储中定义该方法:

      1 /// <summary>
      2 /// 根据用户名获取用户实体
      3 /// </summary>
      4 /// <param name="userName">用户名</param>
      5 /// <returns>用户实体</returns>
      6 public SysUser GetByUserName(string userName)
      7 {
      8     return this.Db.Users.Where(w => w.UserName == userName).FirstOrDefault();
      9 }

    理论上来说,用户仓储已经完成。

    用户仓储功能检验结果

    先在WebUI项目中增加对数据实现层(S.Framework.DataAchieve)的引用(对数据核心层的引用继续留着,因为数据库初始化策略的设置要用到核心层,等会会把数据库初始化设置从核心层移出去,那时再移除WebUI层对数据核心层的引用)。

    把“用户登录功能”的代码调整为对用户仓储的调用,将

      1 var db = new MasterEntityContext("matrixkey");
      2 var entity = db.Users.Where(w => w.UserName == model.UserName).FirstOrDefault();

    修改为:

      1 var rep = new SysUserRepository();
      2 var entity = rep.GetByUserName(model.UserName);

    编译运行,用admin和123456进行登录,成功跳转至首页。

    解耦数据核心层与WebUI层

    上面说到,由于数据库初始化策略及其设置,都定义在数据核心层中,所以WebUI层需要依赖数据核心层。不能忍,必须解耦,方法是将数据库初始化策略及其设置类,移动到数据实现层中,如下图:

    image

    此时可以把数据核心层中EntityFramework文件夹下的Migrations、Initializes子文件夹删除。然后调整WebUI层中Global的命名空间即可。

    编译,可能会有“找不到命名空间”的报错,原因是旧命名空间不存在了,删除这条using即可。

    编译生成无误,然后移除WebUI层对数据核心层的引用吧。

    由于数据库初始化类调整过,因此需要删除数据库让EF重新创建一次。删除数据库,重新去登录页面尝试登录。

    仓储的完善

    对实体的常用操作,可以简单地归纳为“特定查询、添加、更新、删除”,这几类操作是每一个实体仓储都需要包含的,因此可以通过封装来精简代码量。

    建立一个仓储基本类,名称BaseRepository,将通用的部分在基本类中实现,如下:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 using System.Linq.Expressions;
      7 
      8 using S.Framework.Entity;
      9 using S.Framework.DataCore.EntityFramework.EntityContexts;
     10 
     11 namespace S.Framework.DataAchieve.EntityFramework
     12 {
     13     public abstract class BaseRepository<TEntity> where TEntity : class, new()
     14     {
     15         private System.Data.Entity.DbContext Db { get; set; }
     16 
     17         private System.Data.Entity.DbSet<TEntity> DbSet { get { return this.Db.Set<TEntity>(); } }
     18 
     19         public BaseRepository()
     20         {
     21             this.Db = new MasterEntityContext("matrixkey");
     22         }
     23 
     24         /// <summary>
     25         /// 主键查询
     26         /// </summary>
     27         /// <param name="keyValues">键值</param>
     28         /// <returns>实体</returns>
     29         public virtual TEntity Find(IEnumerable<object> keyValues)
     30         {
     31             if (keyValues == null || keyValues.Count() == 0)
     32             {
     33                 throw new ArgumentException("参数有误。");
     34             }
     35             keyValues = keyValues.Where(keyValue => keyValue != null);
     36             return this.DbSet.Find(keyValues);
     37         }
     38 
     39         /// <summary>
     40         /// 主键查询
     41         /// </summary>
     42         /// <param name="keyValues">键值</param>
     43         /// <returns>实体</returns>
     44         public virtual TEntity Find(params object[] keyValues)
     45         {
     46             if (keyValues == null || keyValues.Count() == 0)
     47             {
     48                 throw new ArgumentException("参数有误。");
     49             }
     50 
     51             return this.DbSet.Find(keyValues);
     52         }
     53 
     54         /// <summary>
     55         /// 获取 <see cref="TEntity"/> 的Linq查询器
     56         /// </summary>
     57         /// <returns></returns>
     58         protected IQueryable<TEntity> Query()
     59         {
     60             return this.DbSet.AsQueryable();
     61         }
     62 
     63         /// <summary>
     64         /// 获取 <see cref="TEntity"/> 的Linq查询器
     65         /// </summary>
     66         /// <param name="predicate">查询条件</param>
     67         /// <returns>数据查询器</returns>
     68         protected IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> predicate)
     69         {
     70             return this.DbSet.Where(predicate);
     71         }
     72 
     73         /// <summary>
     74         /// 添加实体
     75         /// </summary>
     76         /// <param name="entity">实体</param>
     77         public void Add(TEntity entity)
     78         {
     79             if (entity == null)
     80             { return; }
     81 
     82             this.DbSet.Add(entity);
     83         }
     84 
     85         /// <summary>
     86         /// 批量添加实体
     87         /// </summary>
     88         /// <param name="entities">实体集合</param>
     89         public void AddRange(IEnumerable<TEntity> entities)
     90         {
     91             if (entities == null || entities.Count() == 0)
     92             { return; }
     93 
     94             System.Data.Entity.DbSet<TEntity> set = this.DbSet;
     95             bool autoDetectChangesEnabled = this.Db.Configuration.AutoDetectChangesEnabled;
     96             this.Db.Configuration.AutoDetectChangesEnabled = false;
     97 
     98             foreach (TEntity entity in entities)
     99             {
    100                 if (entity == null)
    101                 { continue; }
    102 
    103                 set.Add(entity);
    104             }
    105 
    106             this.Db.Configuration.AutoDetectChangesEnabled = autoDetectChangesEnabled;
    107         }
    108 
    109         /// <summary>
    110         /// 更改实体
    111         /// </summary>
    112         /// <param name="entity">实体对象</param>
    113         public void Update(TEntity entity)
    114         {
    115             if (entity == null)
    116             { return; }
    117 
    118             System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> entry = this.Db.Entry(entity);
    119             if (entry.State == System.Data.Entity.EntityState.Detached)
    120             {
    121                 this.DbSet.Attach(entity);
    122             }
    123             entry.State = System.Data.Entity.EntityState.Modified;
    124         }
    125 
    126         /// <summary>
    127         /// 批量更改实体
    128         /// </summary>
    129         /// <param name="entities">实体集合</param>
    130         public void UpdateRange(IEnumerable<TEntity> entities)
    131         {
    132             if (entities == null || entities.Count() == 0)
    133             { return; }
    134 
    135             var set = this.DbSet;
    136             bool autoDetectChangesEnabled = this.Db.Configuration.AutoDetectChangesEnabled;
    137             this.Db.Configuration.AutoDetectChangesEnabled = false;
    138 
    139             foreach (TEntity entity in entities)
    140             {
    141                 if (entity == null)
    142                 { continue; }
    143 
    144                 System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> entry = this.Db.Entry(entity);
    145                 if (entry.State == System.Data.Entity.EntityState.Detached)
    146                 {
    147                     set.Attach(entity);
    148                 }
    149                 entry.State = System.Data.Entity.EntityState.Modified;
    150             }
    151 
    152             this.Db.Configuration.AutoDetectChangesEnabled = autoDetectChangesEnabled;
    153         }
    154 
    155         /// <summary>
    156         /// 主键删除实体
    157         /// </summary>
    158         /// <param name="key">键值</param>
    159         public void Delete(object key)
    160         {
    161             if (key == null || string.IsNullOrWhiteSpace(key.ToString()))
    162             { return; }
    163 
    164             TEntity entity = this.Find(key);
    165 
    166             this.Delete(entity);
    167         }
    168 
    169         /// <summary>
    170         /// 删除实体
    171         /// </summary>
    172         /// <param name="entity">实体</param>
    173         public void Delete(TEntity entity)
    174         {
    175             if (entity == null)
    176             { return; }
    177 
    178             System.Data.Entity.DbSet<TEntity> set = this.DbSet;
    179             System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> entry = this.Db.Entry(entity);
    180             if (entry.State == System.Data.Entity.EntityState.Detached)
    181             {
    182                 set.Attach(entity); set.Remove(entity);
    183             }
    184             else
    185             { entry.State = System.Data.Entity.EntityState.Deleted; }
    186         }
    187 
    188         /// <summary>
    189         /// 批量删除实体
    190         /// </summary>
    191         /// <param name="entities">实体集合</param>
    192         public void DeleteRange(IEnumerable<TEntity> entities)
    193         {
    194             if (entities == null || entities.Count() == 0)
    195             { return; }
    196             var set = this.DbSet;
    197 
    198             bool autoDetectChangesEnabled = this.Db.Configuration.AutoDetectChangesEnabled;
    199             this.Db.Configuration.AutoDetectChangesEnabled = false;
    200 
    201             foreach (TEntity entity in entities)
    202             {
    203                 if (entity == null)
    204                 { continue; }
    205 
    206                 System.Data.Entity.Infrastructure.DbEntityEntry<TEntity> entry = this.Db.Entry(entity);
    207                 if (entry.State == System.Data.Entity.EntityState.Detached)
    208                 {
    209                     set.Attach(entity);
    210                 }
    211                 else
    212                 {
    213                     entry.State = System.Data.Entity.EntityState.Deleted;
    214                 }
    215             }
    216 
    217             this.Db.Configuration.AutoDetectChangesEnabled = autoDetectChangesEnabled;
    218         }
    219     }
    220 }
    221 
    仓储基本类

    注意仓储基本类的修饰符以及类中各属性、方法等成员的访问修饰符,要充分理解abstract、private、protected、public的使用原因。不了解泛型的读者请自行查阅相关资料。

    现在让用户仓储类继承仓储基本类,并删除“在父类中已经实现的成员几相关代码”,用户仓储类将变得非常简单:

      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.DataAchieve.EntityFramework.Repositories.Master
     10 {
     11     /// <summary>
     12     /// 用户仓储
     13     /// </summary>
     14     public class SysUserRepository : BaseRepository<SysUser>
     15     {
     16         /// <summary>
     17         /// 根据用户名获取用户实体
     18         /// </summary>
     19         /// <param name="userName">用户名</param>
     20         /// <returns>用户实体</returns>
     21         public SysUser GetByUserName(string userName)
     22         {
     23             return this.Query(w => w.UserName == userName).FirstOrDefault();
     24         }
     25     }
     26 }
     27 
    新的仓储基本类

    编译运行,再次进行登录,成功跳转至首页。

    现在为角色实体创建仓储类试试,简单吧。

    但同时也引出新问题:手动为每个实体创建实体仓储是个重复又机械的体力活,是否可以像“实体配置类”那样也用T4模板来自动生成?

    当然可以。过程和“利用T4自动生成实体配置类”一样,模板不同而已,这里一笔带过,直接贴出“仓储模板”和“执行器”的相关代码:

      1 <#+
      2 // <copyright file="Repository.tt" company="">
      3 //  Copyright © . All Rights Reserved.
      4 // </copyright>
      5 
      6 public class Repository : CSharpTemplate
      7 {
      8     private string _modelName;
      9     private string _prefixName;
     10 
     11     public Repository(string modelName, string prefixName)
     12     {
     13         this._modelName = modelName;
     14         this._prefixName = prefixName;
     15     }
     16 	public override string TransformText()
     17 	{
     18 		base.TransformText();
     19 #>
     20 using System;
     21 using System.Collections.Generic;
     22 using System.Linq;
     23 using System.Text;
     24 using System.Threading.Tasks;
     25 
     26 using S.Framework.Entity.<#= _prefixName #>;
     27 
     28 namespace S.Framework.DataAchieve.EntityFramework.Repositories.<#= _prefixName #>
     29 {
     30 	/// <summary>
     31     /// 实体仓储
     32     /// </summary>
     33     public partial class <#= _modelName #>Repository : BaseRepository<<#= _modelName #>>
     34 	{
     35 
     36 	}
     37 }
     38 <#+
     39         return this.GenerationEnvironment.ToString();
     40 	}
     41 }
     42 #>
     43 
    实体仓储模板
      1 <#@ template language="C#" debug="True" #>
      2 <#@ assembly name="System.Core" #>
      3 <#@ output extension="cs" #>
      4 <#@ import namespace="System.IO" #>
      5 <#@ import namespace="System.Text" #>
      6 <#@ import namespace="System.Reflection" #>
      7 <#@ import namespace="System.Linq" #>
      8 <#@ import namespace="System.Collections.Generic" #>
      9 <#@ include file="T4Toolbox.tt" #>
     10 <#@ include file="Repository.tt" #>
     11 <#
     12 
     13     string coreName = "S.Framework", projectName = coreName + ".DataAchieve", entityProjectName = coreName + ".Entity";
     14     string entityBaseModelName = entityProjectName + ".EntityBaseModel";
     15     string entityBaseModelNameForReflection = entityProjectName + ".EntityModelBaseForReflection";
     16     //当前完整路径
     17     string currentPath = Path.GetDirectoryName(Host.TemplateFile);
     18     //T4文件夹的父级文件夹路径
     19     string projectPath = currentPath.Substring(0, currentPath.IndexOf(@"T4"));
     20     //解决方案路径
     21     string solutionFolderPath = currentPath.Substring(0, currentPath.IndexOf(@"" + projectName));
     22 
     23     //加载数据实体.dll
     24     string entityFilePath = string.Concat(solutionFolderPath, ("\"+ entityProjectName +"\bin\Debug\" + entityProjectName + ".dll"));
     25     byte[] fileData = File.ReadAllBytes(entityFilePath);
     26     Assembly assembly = Assembly.Load(fileData);
     27     //反射出实体类,不知道为啥此处不能成功判定“是否继承EntityModelBaseForReflection类”
     28     //因此只能通过名称比较的方式来判定
     29     IEnumerable<Type> modelTypes = assembly.GetTypes().Where(m => m.IsClass && !m.IsAbstract && (m.BaseType.FullName.Equals(entityBaseModelName) || m.BaseType.FullName.Equals(entityBaseModelNameForReflection)));
     30 
     31     //循环实体类
     32     List<string> prefixNames = new List<string>();
     33     foreach (Type item in modelTypes)
     34     {
     35         //找 实体文件夹 名称
     36         string tempNamespace= item.Namespace, nameSpaceWithoutProjectName = tempNamespace.Substring(entityProjectName.Length);
     37         if(nameSpaceWithoutProjectName.IndexOf(".") != 0 || nameSpaceWithoutProjectName.LastIndexOf(".") > 0)
     38         { continue; }
     39 
     40         //是否直接继承实体基本类
     41         bool purity = item.BaseType.FullName.Equals(entityBaseModelNameForReflection);
     42         //实体所在的数据库标识名称
     43         string targetName = nameSpaceWithoutProjectName.Substring(1);
     44         if(!prefixNames.Any(a => a == targetName)){ prefixNames.Add(targetName); }
     45         //目标文件的路径和名称(嵌套Generate文件夹是为了标识T4生成的类文件)
     46         string fileName= targetName + @"Generate" + item.Name + "Repository.cs";
     47 
     48         //仓储文件
     49         string folderName= @"Repositories";
     50         Repository repository = new Repository(item.Name, targetName);
     51         repository.Output.Encoding = Encoding.UTF8;
     52         string path = projectPath + folderName + fileName;
     53         repository.RenderToFile(path);
     54     }
     55 #>
     56 
    模板执行器文件

    通过T4模板生成的实体仓储文件,结构如下图:

    image

    下一篇日志,将演示数据实体层和数据核心层的解耦。

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

  • 相关阅读:
    🍖JS函数
    🍖JS流程控制
    🍖JS运算符介绍
    🍖JS数值类型与字符串类型的内置方法
    多态
    property装饰器
    封装
    组合
    继承&派生 属性查找
    继承与派生
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5569228.html
Copyright © 2011-2022 走看看