• 七色花基本权限系统(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和配置实体字段就可以。

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

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

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

  • 相关阅读:
    给JavaScript新手的24条实用建议
    javascript之HTML(select option)详解
    PHP的正则处理函数总结分析
    多级关联菜单:
    理解json两种结构:数组和对象
    dede标签学习笔记(一)
    Jewel_M PHP定时执行任务的实现
    网站刷新器
    PHP_SELF、 SCRIPT_NAME、 REQUEST_URI区别
    RemoveXSS()
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5570788.html
Copyright © 2020-2023  润新知