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


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

    合并/迁移

    合并是指“新的实体模型映射到数据库中,更新其结构”,例如:

    新增了实体类,那在数据库中就是新增数据表。

    删除了实体类,那在数据库中就是删除数据表。

    在一个已有的实体类中增加属性,那在数据库中就是对相应的数据表增加字段。

    在一个已有的实体类中删除属性,那在数据库中就是对相应的数据表删除字段。

    修改一个已有的实体的属性的名称或类型或配置,

    迁移是指“在更新数据库结构时,把老结构中的数据迁移到新结构中”。

    数据初始化

    通常是指在EF创建数据库的时候,自动新增一批数据,这样方便开发阶段数据重置和测试。这里说通常两个字是因为EF也可以在“合并”的时候重置数据,但一般不会这么做。

    手动方式

    合并的手动方式有2种。

    第一种是删除原数据库让EF重新创建数据库。在项目开展之后很少会这么干。

    第二种是通过nuget命令方式让新旧模型合并。操作过于繁琐,开发阶段的实体更新频率较高,费时费力。

    自动合并/迁移

    既然可以通过nuget命令的方式实现合并,自然也可以通过代码的方式自动完成。EF提供了用于合并的配置入口,少量代码就能完成配置。

    在S.Framework.DataCore/EntityFramework中创建文件夹,名称Migrations,在该文件夹中创建合并配置类。

      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.Migrations;
      7 
      8 using S.Framework.DataCore.EntityFramework.EntityContexts;
      9 
     10 namespace S.Framework.DataCore.EntityFramework.Migrations
     11 {
     12     /// <summary>
     13     /// Master 数据库迁移策略
     14     /// </summary>
     15     internal sealed class MasterMigrateConfiguration : DbMigrationsConfiguration<MasterEntityContext>
     16     {
     17         /// <summary>
     18         /// 构造方法
     19         /// </summary>
     20         public MasterMigrateConfiguration()
     21         {
     22             //表示开启自动合并
     23             this.AutomaticMigrationsEnabled = true;
     24             //表示将导致数据丢失的合并也允许进行
     25             this.AutomaticMigrationDataLossAllowed = true;
     26         }
     27     }
     28 }
     29 
    数据库合并策略配置类

    其中的AutomaticMigrationDataLossAllowed属性表达的意思是,当数据从合并之前的数据类型改变为合并后的数据类型时,如果会发生数据丢失,是否强制合并。

    举例来说就是nvarchar(1000)的数据转换成int的数据,就很有可能造成数据丢失。

    顺便提2个注意事项。

    第一,修改字段的数据类型时,建议一个字段一个字段地修改,同时注意数据的类型转换是否可行。例如把String类型的属性修改为int类型的属性,要注意该字段的数据能否转换成int。

    第二,修改字段的名称时,千万别通过自动迁移的方式直接去更新数据库结构。因为这种操作,EF的处理是:先删除原字段 => 添加新字段。数据会丢失的!数据会丢失的!数据会丢失的!重说3。较能接受的解决方式是:先创建新字段,自动合并,自己写sql把旧字段的数据更新给新字段,然后再删旧字段。

    第三,开启了自动迁移时,千万不要直接手动去修改数据库结构。EF的合并历史表是个很敏感的,此处不展开,有机会可以新开日志讲讲。

    配置好了策略,还需要让EF数据库上下文知晓。EF提供了SetInitializer方法,用来对数据库对象设置初始化策略。

    按下图创建目录结构,并添加名为“MasterDatabaseInitializer”的类,用来表示 Master 数据库的初始化类。

    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;
      7 
      8 using S.Framework.DataCore.EntityFramework.EntityContexts;
      9 using S.Framework.DataCore.EntityFramework.Migrations;
     10 
     11 namespace S.Framework.DataCore.EntityFramework.Initializes
     12 {
     13     /// <summary>
     14     /// Master 数据库初始化操作类
     15     /// </summary>
     16     public class MasterDatabaseInitializer
     17     {
     18         /// <summary>
     19         /// 设置数据库初始化策略
     20         /// </summary>
     21         /// <param name="migrate">是否合并(自动迁移)。若是,则会检查数据库是否存在,若不存在则创建,若存在则进行自动迁移。若否,则不进行初始化操作(这样能避开EF访问sys.databases检测数据库是否存在,项目稳定后可将参数设置为false)。</param>
     22         public void Initialize(bool migrate)
     23         {
     24             if (!migrate)
     25             {
     26                 System.Data.Entity.Database.SetInitializer<MasterEntityContext>(null);
     27             }
     28             else
     29             {
     30                 System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<MasterEntityContext, MigrateConfiguration>(true));
     31             }
     32         }
     33     }
     34 }
     35 
    Master数据库初始化类

    接下来还有最后一步,在WebUI层的Global.asax中调用上述数据库初始化类中的Initialize方法。

    由于我们将Global中的内容封装在了S.Framework.Web层的SHttpApplication类中,而又不想让S.Framework.Web层引用S.Framework.DataCore层(如果要在SHttpApplication中调用数据库初始化类就需要该引用),因此我们对SHttpApplication稍作调整,增加如下代码:

      1 protected virtual void ApplicationAppend() { }

    并在Application_Start中的末尾,调用该方法:

      1 this.ApplicationAppend();

    然后去Global中重写上述方法:

      1 protected override void ApplicationAppend()
      2 {
      3     new MasterDatabaseInitializer().Initialize(true);
      4 }

    别忘了using命名空间。

    自动合并迁移验证结果

    对Home/Index中那段“往数据库里新增admin用户”的代码稍作调整吧,增加一个判定:

      1 public ActionResult Index()
      2 {
      3     var db = new MasterEntityContext("matrixkey");
      4 
      5     //判断admin是否存在,若存在,不新增
      6     if (!db.Users.Any(a => a.UserName == "admin"))
      7     {
      8         var entity = new SysUser { ID = Guid.NewGuid().ToString(), UserName = "admin", Password = "123456", CreateDate = DateTime.Now, CreateUser = "admin" };
      9         //将用户对象附加给数据库上下文(这仅仅是内存级别的操作)
     10         db.Users.Add(entity);
     11         //数据库上下文保存更改(提交变更到数据库执行)
     12         db.SaveChanges();
     13     }
     14 
     15     return View();
     16 }

    测试步骤:编译生成 => 删除数据库 => 运行首页 => 检查数据库结构 => 在SysRole中新增一个属性如Test1 => 再次编译生成 => 运行首页 => 检查数据库结构 => 在SysRole中新增一个属性如Test2 => 再次编译生成 => 运行首页 => 检查数据库结构 => 删除SysRole中一个属性如Test1  => 再次编译生成 => 运行首页 => 检查数据库结构。

    当然你也可以新增一个实体来进行测试,步骤与上面类似,编译后运行就行。如果要配置新实体的字段,别忘了运行T4(不运行T4也不会错,但都是默认配置)。

    数据初始化

    完成了自动合并迁移之后,数据初始化就非常简单了,因为数据初始化的入口就在合并策略类里。

    定位到 Master 数据库迁移策略(MasterMigrateConfiguration)中,该策略类继承于DbMigrationsConfiguration,转到它的定义,可以看到一个方法:

    image

    (如果大家看到的是英文版的,建议nuget中安装与EF相同版本的EntityFramework.zh-Hans,然后重新打开VS项目,就可以有汉化效果了。)

    在迁移策略类中重写该方法就可以实现数据初始化功能。剪切Home/Index中那段数据库操作的代码,粘帖到Seed方法里,调整一下数据库上下文对象的名称,如下:

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

    此时Home/Index里只有一句“return View();”。

    数据初始化验证结果

    为了检测效果,先将用户表中的数据清空。

    编译,运行。

    此时发现:首页打开了,但检查数据库后发现用户表中并没有新增admin数据。

    这是因为:想让EF帮你做事,必须先触发EF数据库上下文的调用。

    任何模型的更改、迁移策略/初始化策略的更改,在再次触发EF数据库上下文调用时,都会执行Seed方法。

    由于首页的现实的过程中,并没有对数据库上下文的调用,因此没法通过“打开首页”来让EF执行Seed方法。

    还记得前面已经实现的“用户登录”功能吗,这就是个现成的对EF数据库上下文的调用。

    打开/Account/Login,用户名和密码随意输入,都会触发。返回操作结果之后,检查数据库,可以发现admin数据已创建。

    下一章节,将实现数据仓储和利用T4自动生成实体仓储。

    本章节源码将合并在下一章节源码中,请在下一章节中下载。

    PS,其实是我中间漏了一个版本。。

  • 相关阅读:
    CF611C New Year and Domino
    CF706C Hard problem (状态机dp)
    CF467C George and Job (dp)
    Vue的响应式系统
    如何更好的使用js?
    关于JS变量和作用域详解
    运算符的应用及流程控制if,switch语句
    js闭包
    js的基础
    js的使用及语法
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5564607.html
Copyright © 2020-2023  润新知