zoukankan      html  css  js  c++  java
  • 七色花基本权限系统(10)- 数据接口的实现

    为什么要设计数据接口

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

    要设计哪些接口

    凡是数据实现层要暴露给业务逻辑层使用的,都需要设计接口。比如仓储实现、数据库初始化实现。

    实现数据接口

    新建类库项目,名称S.Framework.DataInterface。

    数据实现层中需要暴露的有:

    基本仓储实现类BaseRepository

    数据库初始化实现类MasterDatabaseInitializer

    各实体仓储实现类

    接下来就是为以上3种实现写对应的接口。

    先说一句,数据实现层中,用一级文件夹EntityFramework进行了不同数据驱动的划分,但数据接口层中则不需要这样,因为只是定义规范,不需要关心实现。

    创建目录结构及接口如下图:

    image

    作为BaseRepository的接口,IBaseRepository并不需要包含所有已实现的方法。接口只是定义通用的规范,一定要注意通用性。比如BaseRepository中的Query方法,是返回数据查询器对象的,是EF特有的,那就不用定义在接口规范中。把常规的Find、Add、Update、Delete等通用方法定义在接口中即可,如下:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Linq.Expressions;
      5 using System.Text;
      6 using System.Threading.Tasks;
      7 
      8 namespace S.Framework.DataInterface
      9 {
     10     /// <summary>
     11     /// 仓储基本接口
     12     /// </summary>
     13     /// <typeparam name="TEntity">实体类型</typeparam>
     14     public interface IBaseRepository<TEntity> where TEntity : class
     15     {
     16         /// <summary>
     17         /// 主键查询
     18         /// </summary>
     19         /// <param name="keyValues">键值</param>
     20         /// <returns>实体</returns>
     21         TEntity Find(IEnumerable<object> keyValues);
     22 
     23         /// <summary>
     24         /// 主键查询
     25         /// </summary>
     26         /// <param name="keyValues">键值</param>
     27         /// <returns>实体</returns>
     28         TEntity Find(params object[] keyValues);
     29 
     30         /// <summary>
     31         /// 添加实体
     32         /// </summary>
     33         /// <param name="entity">实体</param>
     34         void Add(TEntity entity);
     35 
     36         /// <summary>
     37         /// 批量添加实体
     38         /// </summary>
     39         /// <param name="entities">实体集合</param>
     40         void AddRange(IEnumerable<TEntity> entities);
     41 
     42         /// <summary>
     43         /// 更改实体
     44         /// </summary>
     45         /// <param name="entity">实体对象</param>
     46         void Update(TEntity entity);
     47 
     48         /// <summary>
     49         /// 批量更改实体
     50         /// </summary>
     51         /// <param name="entities">实体集合</param>
     52         void UpdateRange(IEnumerable<TEntity> entities);
     53 
     54         /// <summary>
     55         /// 主键删除实体
     56         /// </summary>
     57         /// <param name="key">键值</param>
     58         void Delete(object key);
     59 
     60         /// <summary>
     61         /// 删除实体
     62         /// </summary>
     63         /// <param name="entity">实体</param>
     64         void Delete(TEntity entity);
     65 
     66         /// <summary>
     67         /// 批量删除实体
     68         /// </summary>
     69         /// <param name="entities">实体集合</param>
     70         void DeleteRange(IEnumerable<TEntity> entities);
     71     }
     72 }
     73 
    基本仓储接口

    数据库初始化实现,其实有EF独特的地方(比如合并),假设数据驱动换成dapper或者ADO.NET,也是没办法实现部分初始化功能的。因此在接口IDatabaseInitializer中,只要关注“数据库的数据初始化”就行。

      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.DataInterface.Initializes
      8 {
      9     public interface IDatabaseInitializer
     10     {
     11         /// <summary>
     12         /// 设置数据库初始化策略
     13         /// </summary>
     14         /// <param name="migrate">是否合并(自动迁移)。若是,则会检查数据库是否存在,若不存在则创建,若存在则进行自动迁移。若否,则不进行初始化操作(这样能避开EF访问sys.databases检测数据库是否存在,项目稳定后可将参数设置为false。)。</param>
     15         void Initialize(bool migrate);
     16     }
     17 }
     18 
    数据库初始化接口

    实体仓储接口的实现,跟实体仓储一样,也可以通过T4模板来自动生成部分类。

    先让数据接口层引用数据实体层,因为T4中需要反射实体类。

    仓储接口模板:

      1 <#+
      2 // <copyright file="IRepository.tt" company="">
      3 //  Copyright © . All Rights Reserved.
      4 // </copyright>
      5 
      6 public class IRepository : CSharpTemplate
      7 {
      8     private string _modelName;
      9     private string _prefixName;
     10 
     11     public IRepository(string modelName, string prefixName)
     12     {
     13         _modelName = modelName;
     14         _prefixName = prefixName;
     15     }
     16 
     17 	public override string TransformText()
     18 	{
     19 		base.TransformText();
     20 #>
     21 using System;
     22 
     23 using S.Framework.Entity.<#= _prefixName #>;
     24 
     25 namespace S.Framework.DataInterface.IRepositories.<#= _prefixName #>
     26 {
     27 	/// <summary>
     28     /// 仓储接口
     29     /// </summary>
     30     public partial interface I<#= _modelName #>Repository : IBaseRepository<<#= _modelName #>>
     31     {
     32 
     33     }
     34 }
     35 <#+
     36         return this.GenerationEnvironment.ToString();
     37 	}
     38 }
     39 #>
     40 
    实体仓储接口模板

    执行器文件:

      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="IRepository.tt" #>
     11 <#
     12 
     13     string coreName = "S.Framework", projectName = coreName + ".DataInterface", 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     Dictionary<string, List<Type>> prefixModelTypes = new Dictionary<string, List<Type>>();//存储[数据库标识名称]和[实体类型集合]
     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         List<Type> temp;
     45         if(prefixModelTypes.TryGetValue(targetName, out temp))
     46         {
     47             temp.Add(item);
     48         }
     49         else
     50         {
     51             temp = new List<Type>{ item };
     52             prefixModelTypes.Add(targetName, temp);
     53         }
     54 
     55         //目标文件的路径和名称(嵌套Generate文件夹是为了标识T4生成的类文件)
     56         string fileName= targetName + @"GenerateI" + item.Name + "Repository.cs";
     57         //仓储文件
     58         string folderName= @"IRepositories";
     59         IRepository irepository = new IRepository(item.Name, targetName);
     60         irepository.Output.Encoding = Encoding.UTF8;
     61         string path = projectPath + folderName + fileName;
     62         irepository.RenderToFile(path);
     63     }
     64 
     65 #>
     66 
    执行器文件

    运行T4之后,数据接口层文档结构目录如下:

    image

    最后,让数据实现层引用数据接口层,再调整对接口的继承。

    先让BaseRepository继承IBaseRepository接口,如下图:

      1 public abstract class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class, new()

    由于数据库初始化类和实体仓储实现都是T4自动生成的,因此想让生成的文件继承接口,需要调整相应的T4模板。

    调整DatabaseInitializer模板,最终代码如下:

      1 <#+
      2 // <copyright file="DatabaseInitializer.tt" company="">
      3 //  Copyright © . All Rights Reserved.
      4 // </copyright>
      5 
      6 public class DatabaseInitializer : CSharpTemplate
      7 {
      8     private string _prefixName;
      9 
     10     public DatabaseInitializer(string prefixName)
     11     {
     12         this._prefixName = prefixName;
     13     }
     14 
     15 	public override string TransformText()
     16 	{
     17 		base.TransformText();
     18 #>
     19 
     20 using System;
     21 using System.Collections.Generic;
     22 using System.Linq;
     23 using System.Text;
     24 using System.Threading.Tasks;
     25 using System.Data.Entity;
     26 
     27 using S.Framework.DataCore.EntityFramework.EntityContexts;
     28 using S.Framework.DataInterface.Initializes;
     29 
     30 namespace S.Framework.DataAchieve.EntityFramework.Initializes
     31 {
     32 	/// <summary>
     33     /// <#= _prefixName #> 数据库初始化操作类
     34     /// </summary>
     35     public class <#= _prefixName #>DatabaseInitializer : IDatabaseInitializer
     36     {
     37         /// <summary>
     38         /// 设置数据库初始化策略
     39         /// </summary>
     40         /// <param name="migrate">是否合并(自动迁移)。若是,则会检查数据库是否存在,若不存在则创建,若存在则进行自动迁移。若否,则不进行初始化操作(这样能避开EF访问sys.databases检测数据库是否存在,项目稳定后可将参数设置为false)。</param>
     41         public void Initialize(bool migrate)
     42         {
     43             if (!migrate)
     44             {
     45                 System.Data.Entity.Database.SetInitializer<<#= _prefixName #>EntityContext>(null);
     46             }
     47             else
     48             {
     49                 System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<<#= _prefixName #>EntityContext, <#= _prefixName #>MigrateConfiguration>(true));
     50             }
     51         }
     52     }
     53 }
     54 <#+
     55         return this.GenerationEnvironment.ToString();
     56 	}
     57 }
     58 #>
     59 
    数据库初始化模板文件

    调整Repository模板,实体仓储除了需要继承BaseRepository类,还需要继承数据接口层中相应的实体仓储接口,最终代码如下:

      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 using S.Framework.DataInterface.IRepositories.<#= _prefixName #>;
     28 
     29 namespace S.Framework.DataAchieve.EntityFramework.Repositories.<#= _prefixName #>
     30 {
     31 	/// <summary>
     32     /// 实体仓储
     33     /// </summary>
     34     public partial class <#= _modelName #>Repository : BaseRepository<<#= _modelName #>>, I<#= _modelName #>Repository
     35 	{
     36 
     37 	}
     38 }
     39 <#+
     40         return this.GenerationEnvironment.ToString();
     41 	}
     42 }
     43 #>
     44 
    实体仓储模板文件

    另外,由于在WebUI层中的Global.asax里调用了数据库初始化方法(该方法继承于接口),因此也需要让WebUI层引用数据接口层,才能正常运行项目。

    下一章节,将演示仓储与工作单元的设计和实现,是非常核心的内容。

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

  • 相关阅读:
    InnoDB引擎面面观
    [读史思考]为何此大神可以同时进入文庙和武庙?
    [源码解析] 当 Java Stream 遇见 Flink
    Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer
    [源码解析] Flink UDAF 背后做了什么
    [业界方案] ClickHouse业界解决方案学习笔记
    Istio Routing 实践掌握virtualservice/gateway/destinationrule/AB版本发布/金丝雀发布
    树立个人品牌意识:从背景调查谈谈职业口碑的重要性
    Istio 生产环境用户流量接入方案
    故障管理:故障应急和故障复盘
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5591734.html
Copyright © 2011-2022 走看看