七色花基本权限系统(10)- 数据接口的实现

为什么要设计数据接口

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

要设计哪些接口

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

实现数据接口

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

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

基本仓储实现类BaseRepository

数据库初始化实现类MasterDatabaseInitializer

各实体仓储实现类

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

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

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

作为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 + @"\Generate\I" + 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之后,数据接口层文档结构目录如下:

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

先让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层引用数据接口层,才能正常运行项目。

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

时间: 2024-10-18 22:27:37

七色花基本权限系统(10)- 数据接口的实现的相关文章

七色花基本权限系统(3)- 利用EasyUI进行首页布局

EasyUI EasyUI是基于JQuery库的偏js轻型前端UI框架,不了解的读者可以参考官网地址. 在项目中增加JQuery和EasyUI,并在布局页中引用.为了结构清晰,方便日后维护和升级,可以在Scripts下创建jquery文件夹和jquery-easyui文件夹.这里选择1.11.0的JQuery和1.4.3的EasyUI. 特别说明一下,我已经修复了该版本的几个(只能通过修改源码来修正的)bug,并在updateLog.txt文件中做了修复记录. 图标库 再引入2套通用的图标库,以

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

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

七色花基本权限系统(6)- 让EntityFramework Code First自动合并/迁移/数据初始化

在前一章节里,我们已经能够对映射字段进行配置了.但在演示中,我们通过删除原数据库让EF重新创建的方式,才把新的字段信息更新(其实是破而后立)到了数据库.这显然无法让人接受.在这篇日志里,将演示"在实体类发生改变时如何自动更新数据库中的表结构"和"在EF创建数据库的时候如何初始化一批数据". 合并/迁移 合并是指"新的实体模型映射到数据库中,更新其结构",例如: 新增了实体类,那在数据库中就是新增数据表. 删除了实体类,那在数据库中就是删除数据表.

七色花基本权限系统(8)- 实现实体层和核心层解耦

经过前面的工作,系统正变得越来越清晰. 现在有一个问题需要解决.当需要额外增加一个数据表时,我们需要做的操作是: 在实体层创建实体并编译实体层 在核心层运行T4 配置实体 将实体对象关联给EF数据库上下文(定义DbSet) 将实体配置注册给EF配置对象 这过于繁琐,最后2个步骤,强行地把实体关联在EF数据库上下文里,导致了两者的耦合.这篇日志将演示如何将最后2个步骤省略,解放EF数据库上下文,不用手动关联实体对象,不用手动注册实体配置. 回顾和解释 最后两步还记得吗? 通过定义DbSet对象将实

七色花基本权限系统(14)- 实现EntityFramework和Dapper的混搭

Dapper是什么 Dapper是一款轻量级的微ORM,其核心是实现了"将查询结果映射到指定数据模型",因此可以抛开DataSet.DataTable等数据集对象,以强类型的方式使用查询数据结果.Dapper是开源的,它的GitHub地址在这里:https://github.com/StackExchange/dapper-dot-net,本章节中选择1.4.0版本的Dapper下的.NET45下的核心类,点击下载该核心类:SqlMapper. 为什么要用Dapper来配合Entity

七色花基本权限系统(4) - 初步使用EntityFramework实现用户登录

这篇日志将演示: 1.利用EF的Code First模式,自动创建数据库 2.实现简单的用户登录(不考虑安全仅仅是密码验证) 为什么选择EntityFramework 第一,开发常规中小型系统,能够提高开发效率. 针对小型系统,ORM提高开发效率那是立竿见影.而且linq+lambda的用户体验非常棒,让代码可读性大大增强.即使把linq写得很烂,小系统也无所谓. 针对中型系统,在对ORM有一定了解并且对linq to entity也掌握一定技巧的基础上,使用ORM还是可以提高开发效率. 第二,

七色花基本权限系统(5)- 实体配置的使用和利用T4自动生成实体配置

在前面的章节里,用户表的结构非常简单,没有控制如何映射到数据库.通常,需要对字段的长度.是否可为空甚至特定数据类型进行设置,因为EntityFramework的默认映射规则相对而言比较简单和通用.在这篇日志里,将演示如何对数据实体进行映射配置,并利用T4模板自动创建映射配置类文件. 配置方式 EntityFramework的实体映射配置有2种. 第一种是直接在实体类中以特性的方式进行控制,这些特性有部分是EF实现的,也有部分是非EF实现的.也就是说,在数据实体层不引用EF的情况下,只能使用不全的

七色花基本权限系统(2)- MVC项目搭建及初步调整

新建ASP.NET MVC项目,解决方案名称Seven,MVC项目名称S.Framework.WebClient.如下图: 创建MVC项时会让你选择身份验证方式,选择无吧,咱要空白干净的MVC项目.是否勾选单元测试随自己喜欢就好. 现在把MVC项目中我们不需要用到的部分移除掉,比如自带的bootstrap.jquery.jquery Validation等,可通过nuget工具来移除.如下图: (如果你没装nuget,请google) 请依次移除bootstrap.Microsoft jQuer

【Android】利用安卓的数据接口、多媒体处理编写内存卡Mp3播放器app

通过调用安卓的MediaPlayer可以直接完成Mp3等主流音频的播放,同时利用ContentResolver与Cursor可以直接读取安卓内在数据库的信息,直接获取当前sdcard中所有音频的列表,无须像<[Android]内存卡图片读取器,图库app>(点击打开链接)一样利用原始的Java代码去遍历整个sdcard卡,直接调用安卓固有的类既便捷又快速.最后,读取出来的Mp3可以通过适配器直接加载到ListView列表,做出如下所示的内存卡Mp3播放器app效果.本app在自己的真实的16G