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

经过前面的工作,系统正变得越来越清晰。

现在有一个问题需要解决。当需要额外增加一个数据表时,我们需要做的操作是:

在实体层创建实体并编译实体层

在核心层运行T4

配置实体

将实体对象关联给EF数据库上下文(定义DbSet)

将实体配置注册给EF配置对象

这过于繁琐,最后2个步骤,强行地把实体关联在EF数据库上下文里,导致了两者的耦合。这篇日志将演示如何将最后2个步骤省略,解放EF数据库上下文,不用手动关联实体对象,不用手动注册实体配置。

 

回顾和解释

最后两步还记得吗?

通过定义DbSet对象将实体关联给EF:

  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; }

那么定义DbSet究竟是用来干什么的?

它有2个作用:

在没有注册实体配置的情况下,告诉EF,要把指定实体映射到数据库(如果注册了实体配置,就起不到这个作用了)

能够显式地通过db对象访问实体库(db.Uses)

在第5章节中,我们加入了实体配置,因此第1个中作用就无效了。那来看看第2个作用能否取缔掉,如果能够取缔掉,那就有了“解耦”的可能。

回顾用户登录功能的最初代码:

  1 db.Users.Where(w => w.UserName == model.UserName).FirstOrDefault()

当时还没有建立仓储,于是直接用EF数据库上下文对象访问用户库(db.Users)。

后来建立了仓储,代码修改为:

  1 this.Query(w => w.UserName == userName).FirstOrDefault()
  1 /// <summary>
  2 /// 获取 <see cref="TEntity"/> 的Linq查询器
  3 /// </summary>
  4 /// <param name="predicate">查询条件</param>
  5 /// <returns>数据查询器</returns>
  6 protected IQueryable<TEntity> Query(Expression<Func<TEntity, bool>> predicate)
  7 {
  8     return this.DbSet.Where(predicate);
  9 }
  1 private System.Data.Entity.DbSet<TEntity> DbSet { get { return this.Db.Set<TEntity>(); } }

可以看到,修改后的代码,并未调用db.Users,而是调用db.Set<SysUser>()。

回顾数据初始化策略中关于自动生成数据的代码:

  1 /// <summary>
  2 /// 数据初始化
  3 /// </summary>
  4 /// <param name="context">数据库上下文</param>
  5 protected override void Seed(MasterEntityContext context)
  6 {
  7     if (!context.Users.Any(a => a.UserName == "admin"))
  8     {
  9         var entity = new SysUser { ID = Guid.NewGuid().ToString(), UserName = "admin", Password = "123456", CreateDate = DateTime.Now, CreateUser = "admin" };
 10         //将用户对象附加给数据库上下文(这仅仅是内存级别的操作)
 11         context.Users.Add(entity);
 12         //数据库上下文保存更改(提交变更到数据库执行)
 13         context.SaveChanges();
 14     }
 15 }

这里的db.Users同样也可以替换掉,修改代码后如下:

  1 /// <summary>
  2 /// 数据初始化
  3 /// </summary>
  4 /// <param name="context">数据库上下文</param>
  5 protected override void Seed(MasterEntityContext context)
  6 {
  7     //判断admin是否存在,若存在,不新增
  8     var dbSet = context.Set<SysUser>();
  9     if (!dbSet.Any(a => a.UserName == "admin"))
 10     {
 11         var entity = new SysUser { ID = Guid.NewGuid().ToString(), UserName = "admin", Password = "123456", CreateDate = DateTime.Now, CreateUser = "admin" };
 12         //将用户对象附加给数据库上下文(这仅仅是内存级别的操作)
 13         dbSet.Add(entity);
 14         //数据库上下文保存更改(提交变更到数据库执行)
 15         context.SaveChanges();
 16     }
 17 }

至此,db.实体库这种调用方式以及全部移除,可以把数据库上下文中的DbSet属性定义的代码移除。

 

现在来处理注册实体配置的代码:

  1 /// <summary>
  2 /// 模型配置重写
  3 /// </summary>
  4 /// <param name="modelBuilder">数据实体生成器</param>
  5 protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
  6 {
  7     // 禁用一对多级联删除
  8     modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
  9     // 禁用多对多级联删除
 10     modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
 11     // 禁用表名自动复数规则
 12     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
 13 
 14     new SysUserConfiguration().RegistTo(modelBuilder.Configurations);
 15     new SysRoleConfiguration().RegistTo(modelBuilder.Configurations);
 16 }

其中最后2句代码,就是分别对用户配置类、角色配置类的注册。

首先可以确定的是,这里的代码不能省。那既然实体配置类会通过T4直接创建,能否让代码自动去注册实体配置类呢?

答案是可以的。做法也也不止一种。

第一种做法是利用T4自动创建EF数据库上下文,把“实体名称集合”传入模板,自动加上“对每个实体配置类进行注册”的代码。

第二种做法是给实体配置类增加一个“标记”,通过这个标记反射出所有的实体配置类,然后存储在静态变量中,让EF对该静态变量进行循环注册。

很明显,第二种做法优于第一种做法,只需反射一次,还兼顾了代码的简洁优雅。

定义实体配置器接口,名称IEntityConfiguration,并把“各实体配置类中都必须包含的方法”预定义在接口中:

  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.ModelConfiguration.Configuration;
  7 
  8 namespace S.Framework.DataCore.EntityFramework
  9 {
 10     /// <summary>
 11     /// 实体配置器接口
 12     /// </summary>
 13     public interface IEntityConfiguration
 14     {
 15         /// <summary>
 16         /// 将实体配置对象注册到实体生成器配置集合
 17         /// </summary>
 18         /// <param name="configurations">实体生成器配置集合</param>
 19         void RegistTo(ConfigurationRegistrar configurations);
 20     }
 21 }
 22 
实体配置器接口

实现该接口:

  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.ModelConfiguration;
  7 using System.Data.Entity.ModelConfiguration.Configuration;
  8 
  9 namespace S.Framework.DataCore.EntityFramework
 10 {
 11     /// <summary>
 12     /// 实体配置基类
 13     /// </summary>
 14     /// <typeparam name="TEntity">实体类型</typeparam>
 15     public abstract class EntityConfiguration<TEntity> : EntityTypeConfiguration<TEntity>, IEntityConfiguration where TEntity : class
 16     {
 17         public EntityConfiguration()
 18         {
 19 
 20         }
 21 
 22         public void RegistTo(ConfigurationRegistrar configurations)
 23         {
 24             configurations.Add(this);
 25         }
 26     }
 27 }
 28 
实体配置器接口的实现

接口和实现的文件目录结构如下图:

image

 

定义好了接口,也进行了实现。现在去修改T4配置模板,让生成的配置类继承上述的实现类即可。注册方法RegistTo可以去掉了,对EntityTypeConfiguration的继承也可以去掉了。

OK,现在实体配置类都有标记了——都继承于IEntityConfiguration接口,还需要通过一个方法去反射出“继承指定接口的类”,并对这些类初始化后存储在静态变量中,我们可以叫它为“实体配置工厂类”,如下图:

image

该工厂类通过单例的方式来反射并存储结果。

  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.ModelConfiguration.Configuration;
  7 
  8 using S.Utilities;
  9 
 10 namespace S.Framework.DataCore.EntityFramework.ConfigurationFactories
 11 {
 12     /// <summary>
 13     /// 实体配置工厂类
 14     /// </summary>
 15     public static class MasterConfigurationFactory
 16     {
 17         /// <summary>
 18         /// 同步对象
 19         /// </summary>
 20         private static readonly object sync = new object();
 21 
 22         /// <summary>
 23         /// 唯一实例
 24         /// </summary>
 25         private static IEnumerable<IEntityConfiguration> singleton;
 26 
 27         /// <summary>
 28         /// 实体配置
 29         /// </summary>
 30         private static IEnumerable<IEntityConfiguration> Configurations
 31         {
 32             get
 33             {
 34                 if (singleton == null)
 35                 {
 36                     lock (sync)
 37                     {
 38                         if (singleton == null)
 39                         {
 40                             var types = typeof(IEntityConfiguration).GetSubClass().Where(w => !w.IsAbstract && w.Namespace.EndsWith("Master"));
 41 
 42                             singleton = types.Select(m => Activator.CreateInstance(m) as IEntityConfiguration);
 43                         }
 44                     }
 45                 }
 46 
 47                 return singleton;
 48             }
 49         }
 50 
 51         /// <summary>
 52         /// 初始化实体模型生成器
 53         /// </summary>
 54         /// <param name="configurations">实体模型生成器</param>
 55         public static void ConfigurationsInit(ConfigurationRegistrar configurations)
 56         {
 57             foreach (var configuration in Configurations)
 58             {
 59                 configuration.RegistTo(configurations);
 60             }
 61         }
 62     }
 63 }
 64 
实体配置器工厂

注:这里开始将会创建工具类库,并逐步扩充,但不会具体展开解释实现。

通过该配置器工厂中定义的ConfigurationsInit方法,就可以取缔EF数据库上下文中的注册代码了。

修改OnModelCreating如下:

  1 /// <summary>
  2 /// 模型配置重写
  3 /// </summary>
  4 /// <param name="modelBuilder">数据实体生成器</param>
  5 protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
  6 {
  7     // 禁用一对多级联删除
  8     modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
  9     // 禁用多对多级联删除
 10     modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
 11     // 禁用表名自动复数规则
 12     modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
 13 
 14     MasterConfigurationFactory.ConfigurationsInit(modelBuilder.Configurations);
 15 }
通过实体配置器工厂自动注册

别忘了using命名空间。

 

此时,在数据核心层(S.Framework.DataCore)中再也没有任何实体的直接使用,并且本篇日志开头说的操作步骤中省去了最后2个步骤。

现在当你新增一个实体,只需要运行T4和配置实体字段就可以。

重新编译,再次登录一下,可以跳转至首页就说明没问题。

 

下一章节,将中途休息,整理和完善前面的内容。

 

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

posted @ 2016-06-08 17:33  落阳  阅读(497)  评论(2编辑  收藏  举报