七色花基本权限系统(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 数据库的初始化类。

该类代码如下:

  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,转到它的定义,可以看到一个方法:

(如果大家看到的是英文版的,建议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数据已创建。

下一章节,将演示数据实体和数据核心的解耦。

时间: 2024-08-06 19:04:28

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

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

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

七色花基本权限系统(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套通用的图标库,以

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

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

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

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

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

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

七色花基本权限系统(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

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

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

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

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

EntityFramework Code First 模式下使用数据迁移

启用数据迁移 在程序包管理控制台选择安装了EntityFramework的项目,键入如下指令以启EF用数迁移. Enable-Migrations 命令成功运行后,所选项目下会添加名为Migrations的文件夹及Configuration.cs文件,如下图. 创建迁移版本 程序包管理控制台,键入如下命令创建一个迁移版本(当前Entity与数据库的差异). Add-Migration xxx xxx为迁移文件名,例如此次添加了产品表,为方便记忆可取为 Add-Migration createPr