七色花权限管理系统(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

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

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

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