Code First 数据库迁移
当 Entity Framework Code First 的数据模型发生改变时,默认会引发一个System.InvalidOperationException 的异常。解决方法是使用DropCreateDatabaseAlways 或DropCreateDatabaseIfModelChanges,让Entity Framework 自动将数据库删除,然后重新创建。不过,这种方式过于残暴,应该使用更人性化的方式,让Entity Framework 帮助我们自动调整数据库架构。并且仍然保留现有数据库中的数据。而这种开发技术就是 Code First 数据库迁移(DB Migration)。
首先,我们先用 Code First 方式建立一个简单的ASP.NET MVC4 应用程序
在Models 文件夹下建立两个实体类Member、Guestbook。
Member 实体类定义如下:
- namespace CodeFirstDemo.Models
- {
- public partial class Member
- {
- public Member()
- {
- this.Guestbooks = new List<Guestbook>();
- }
- public int Id { get; set; }
- public string Name { get; set; }
- public string Email { get; set; }
- public virtual ICollection<Guestbook> Guestbooks { get; set; }
- }
- }
Guestbook 实体类定义如下:
- namespace CodeFirstDemo.Models
- {
- public partial class Guestbook
- {
- public int Id { get; set; }
- public string Message { get; set; }
- public System.DateTime CreatedOn { get; set; }
- public int MemberId { get; set; }
- public virtual Member Member { get; set; }
- }
- }
在Models 文件夹下建立Mapping 文件夹,并建立对应实体类的关系映射类MemberMap 、GuestbookMap
MemberMap 类定义如下:
- namespace CodeFirstDemo.Models.Mapping
- {
- public class MemberMap : EntityTypeConfiguration<Member>
- {
- public MemberMap()
- {
- // Primary Key
- this.HasKey(t => t.Id);
- // Properties
- this.Property(t => t.Id)
- .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
- this.Property(t => t.Name)
- .IsRequired()
- .HasMaxLength(10);
- this.Property(t => t.Email)
- .IsRequired()
- .HasMaxLength(200);
- // Table & Column Mappings
- this.ToTable("Member");
- this.Property(t => t.Id).HasColumnName("Id");
- this.Property(t => t.Name).HasColumnName("Name");
- this.Property(t => t.Email).HasColumnName("Email");
- }
- }
- }
GuestbookMap 类定义如下:
- namespace CodeFirstDemo.Models.Mapping
- {
- public class GuestbookMap : EntityTypeConfiguration<Guestbook>
- {
- public GuestbookMap()
- {
- // Primary Key
- this.HasKey(t => t.Id);
- // Properties
- this.Property(t => t.Message)
- .IsRequired()
- .HasMaxLength(200);
- // Table & Column Mappings
- this.ToTable("Guestbook");
- this.Property(t => t.Id).HasColumnName("Id");
- this.Property(t => t.Message).HasColumnName("Message");
- this.Property(t => t.CreatedOn).HasColumnName("CreatedOn");
- this.Property(t => t.MemberId).HasColumnName("MemberId");
- // Relationships
- this.HasRequired(t => t.Member)
- .WithMany(t => t.Guestbooks)
- .HasForeignKey(d => d.MemberId);
- }
- }
- }
在Models 建立数据库上下文类CodeFirstDemoContext
CodeFirstDemoContext 类定义如下:
- namespace CodeFirstDemo.Models
- {
- public partial class CodeFirstDemoContext : DbContext
- {
- static CodeFirstDemoContext()
- {
- //Database.SetInitializer<CodeFirstDemoContext>(new DropCreateDatabaseIfModelChanges<CodeFirstDemoContext>());
- }
- public CodeFirstDemoContext()
- : base("Name=CodeFirstDemoContext")
- {
- }
- public DbSet<Guestbook> Guestbooks { get; set; }
- public DbSet<Member> Members { get; set; }
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- modelBuilder.Configurations.Add(new GuestbookMap());
- modelBuilder.Configurations.Add(new MemberMap());
- }
- }
- }
Models 文件夹结构下
以上就是一个简单的 Code First 结构了
接下来在Web.config 添加数据库连接字符串
- <connectionStrings>
- <add name="CodeFirstDemoContext" connectionString="Data Source=vin-pc;Initial Catalog=CodeFirstDemo;Persist Security Info=True;User ID=sa;Password=123456;MultipleActiveResultSets=True"
- providerName="System.Data.SqlClient" />
- </connectionStrings>
然后添加一个控制器HomeController
- namespace CodeFirstDemo.Controllers
- {
- public class HomeController : Controller
- {
- //
- // GET: /Home/
- public ActionResult Index()
- {
- CodeFirstDemoContext db = new CodeFirstDemoContext();
- Member member = new Member { Name = "tt", Email = "qwe@qq.com" };
- db.Members.Add(member);
- db.SaveChanges();
- return View();
- }
- }
- }
EF Code First 如何记录版本
当应用程序通过EF Code First 创建数据库后,在此数据库中讲会自动创建一个名为 dbo. __MigrationHistory 的系统数据表,如下图所示:
打开dbo. __MigrationHistory 会发现三个字段:MigrationId 字段用来记录这次由 EFCode First
所创建的一个表示名称,也可以称为一个版本代码;Model 字段表示这次创建时的模型数据,这是由 Entity Framework
将所有数据模型串行化后的版本,所以看不出是什么;ProductVersion 字段表示当前使用的Entity Framework
版本,如下图所示:
如果尚未启用数据库迁移功能,每次在应用程序运行时,都会对比程序中当前的数据模型,与数据库中dbo. __MigrationHistory 表的Model 字段中的值是否一致,如果不一致,默认就会发生异常。
如果启用数据库迁移功能之后,这个表就会开始记录每次数据模型变动的记录与版本。
启用数据库迁移
若要在项目中启用数据库迁移功能,必须先开启程序包管理器控制台(Package Manager Console)窗口,然后输入 Enable-Migrations指令,如下图:
运行 Enable-Migrations 指令的过程中, Visual Studio 会在项目里创建一个Migrations
目录,该目录下还创建有两个文件,201309120825043_InitialCreate.cs 、Configuration.cs,如下图:
1. 201309120825043_InitialCreate.cs
在启用数据库迁移之前,由于已经通过 Code First 在数据库中创建好了相关的数据库结构,也创建了一个初始的dbo.
__MigrationHistory 数据表,表中也有一条数据,这条数据的MigrationId值正好会等于文档名。VS会将dbo.
__MigrationHistory 表的Model 值读出,并创建这个类的属性,其属性就是包含那次数据模型的完整描述。
- namespace CodeFirstDemo.Migrations
- {
- using System;
- using System.Data.Entity.Migrations;
- public partial class InitialCreate : DbMigration
- {
- public override void Up()
- {
- CreateTable(
- "dbo.Guestbook",
- c => new
- {
- Id = c.Int(nullable: false, identity: true),
- Message = c.String(nullable: false, maxLength: 200),
- CreatedOn = c.DateTime(nullable: false),
- MemberId = c.Int(nullable: false),
- })
- .PrimaryKey(t => t.Id)
- .ForeignKey("dbo.Member", t => t.MemberId, cascadeDelete: true)
- .Index(t => t.MemberId);
- CreateTable(
- "dbo.Member",
- c => new
- {
- Id = c.Int(nullable: false, identity: true),
- Name = c.String(nullable: false, maxLength: 5),
- Email = c.String(nullable: false, maxLength: 200),
- })
- .PrimaryKey(t => t.Id);
- }
- public override void Down()
- {
- DropIndex("dbo.Guestbook", new[] { "MemberId" });
- DropForeignKey("dbo.Guestbook", "MemberId", "dbo.Member");
- DropTable("dbo.Member");
- DropTable("dbo.Guestbook");
- }
- }
- }
2. Configuration.cs
这个类定义了运行数据库迁移时该有的行为。默认情况下,数据库并不会发生迁移动作,除非将 Configuration() 内的 AutomaticMigrationsEnabled 改为 true,才会让 CodeFirst 自动迁移数据库。
- namespace CodeFirstDemo.Migrations
- {
- using System;
- using System.Data.Entity;
- using System.Data.Entity.Migrations;
- using System.Linq;
- internal sealed class Configuration : DbMigrationsConfiguration<CodeFirstDemo.Models.CodeFirstDemoContext>
- {
- public Configuration()
- {
- AutomaticMigrationsEnabled = false;
- }
- protected override void Seed(CodeFirstDemo.Models.CodeFirstDemoContext context)
- {
- // This method will be called after migrating to the latest version.
- // You can use the DbSet<T>.AddOrUpdate() helper extension method
- // to avoid creating duplicate seed data. E.g.
- //
- // context.People.AddOrUpdate(
- // p => p.FullName,
- // new Person { FullName = "Andrew Peters" },
- // new Person { FullName = "Brice Lambson" },
- // new Person { FullName = "Rowan Miller" }
- // );
- //
- }
- }
- }
运行数据库迁移
下面来更改 Member 的数据模型,添加两个字段,分别是 UserName 和 Password 属性。
- namespace CodeFirstDemo.Models
- {
- public partial class Member
- {
- public Member()
- {
- this.Guestbooks = new List<Guestbook>();
- }
- public int Id { get; set; }
- public string Name { get; set; }
- public string Email { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- public virtual ICollection<Guestbook> Guestbooks { get; set; }
- }
- }
通过Package Manager Console 输入 Add-Migration 指令,来新增一条数据库迁移版本,输入时必须带上一个“版本名称”参数。例如,想要取名为AddUsernamePassword,则可以输入以下指令:
运行完成后,会在 Migrations 文件夹新增一个文件,如下图:
这次运行 Add-Migration 指令,所代表的意思就是新增一次运行数据库迁移命令,VS2012会自动对比当前数据库中的 Model 定义与当前更改过的数据模型,并将差异的字段变化写入这个自动新增的类内,程序代码如下:
- namespace CodeFirstDemo.Migrations
- {
- using System;
- using System.Data.Entity.Migrations;
- public partial class AddUsernamePassword : DbMigration
- {
- public override void Up()
- {
- AddColumn("dbo.Member", "UserName", c => c.String());
- AddColumn("dbo.Member", "Password", c => c.String());
- }
- public override void Down()
- {
- DropColumn("dbo.Member", "Password");
- DropColumn("dbo.Member", "UserName");
- }
- }
- }
NOTES
每一次新增数据库迁移版本,其类内都会包含一个Up() 方法与 Down() 方法,所代表的意思分别是“升级数据库”与“降级数据库”的动作,所以,数据库迁移不仅仅只是将数据库升级,还可以恢复到旧版本。
当前还没有对数据库做任何迁移动作,所以数据库中的数据结构并没有任何改变,现在,手动在 Member 数据表中输入几条数据,以确认待会儿数据库迁移(升级)之后数据是否消失,如图:
接着,对数据库进行迁移动作,在程序包管理控制台(Package Manager Console)窗口中输入Update-Database指令,如图:
更新数据库成功之后,可以查看 Member 数据表结构是否发生变化,以及数据表原来的数据是否存在:
NOTES
我们都知道,在客户端数据库通常是无法直接联机的,客户的生产环境通常也没有安装VS2012,那么如果数据库迁移动作要进行套用时,应该
怎么办呢?可以通过 Update-Database 指令的其他参数自动生产数据库迁移的 T-SQL 脚本,然后携带 T-SQL
脚本文件到正式主机部署或更新即可。
Update-Database 指令的–SourceMigration 参数可以指定来源斑斑驳驳,-Targetigration
参数可以指定目标版本, -Script 参数可以用来输出 T-SQL 脚本。以下是生成本次数据库迁移(升级)的 T-SQL 指令演示:
Update-Database –SourceMigration201309120825043_InitialCreate –TargetMigration 201309130055351_AddUsernamePassword-Script
如果要生成数据库降级的 T-SQL,则不能使用–SourceMigration 参数,直接指定–TargetMigration 参数即可,演示如下:
Update-Database –TargetMigration201309120825043_InitialCreate –Script
如果要还原数据库带添加 Code First 之前的初始状态,可以输入以下指令:
Update-Database -TragetMigration:$InitialDatabase –Script
自定义数据库迁移规则
当了解数据库迁移的规则之后,如果希望在数据库迁移的过程中进行一些微调,例如, Entity Framework
并不支持自动设置字段的默认值,假设我们在 Member 数据模型中想添加一个新的 CreatedOn
属性表示会员的注册日期,并且希望在数据库中自动加上 getdate() 默认值,这时就必须要自定义数据库迁移的规则。
首先更改 Member 数据模型,加上 CreatedOn 属性
Member.cs
- namespace CodeFirstDemo.Models
- {
- public partial class Member
- {
- public Member()
- {
- this.Guestbooks = new List<Guestbook>();
- }
- public int Id { get; set; }
- public string Name { get; set; }
- public string Email { get; set; }
- public string UserName { get; set; }
- public string Password { get; set; }
- public DateTime CreatedOn { get; set; }
- public virtual ICollection<Guestbook> Guestbooks { get; set; }
- }
- }
MemberMap.cs
- namespace CodeFirstDemo.Models.Mapping
- {
- public class MemberMap : EntityTypeConfiguration<Member>
- {
- public MemberMap()
- {
- // Primary Key
- this.HasKey(t => t.Id);
- // Properties
- this.Property(t => t.Id)
- .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
- this.Property(t => t.Name)
- .IsRequired()
- .HasMaxLength(10);
- this.Property(t => t.Email)
- .IsRequired()
- .HasMaxLength(200);
- this.Property(t => t.CreatedOn)
- .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
- // Table & Column Mappings
- this.ToTable("Member");
- this.Property(t => t.Id).HasColumnName("Id");
- this.Property(t => t.Name).HasColumnName("Name");
- this.Property(t => t.Email).HasColumnName("Email");
- }
- }
- }
然后运行一次 Add-Migration指令,并指定版本名称为 AddMemberCreatedOn
这时,再 Migrations 目录下多出一个201309130144538_AddMemberCreatedOn.cs 文件
- namespace CodeFirstDemo.Migrations
- {
- using System;
- using System.Data.Entity.Migrations;
- public partial class AddMemberCreatedOn : DbMigration
- {
- public override void Up()
- {
- AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));
- }
- public override void Down()
- {
- DropColumn("dbo.Member", "CreatedOn");
- }
- }
- }
这次我们用不一样的参数来运行数据库迁移,加上–Script 参数,Update-Database –Script
运行完后,会输出完整的数据库更新 T-SQL 脚本,其中第一行就是在 Member 数据表中新增一个 CreatedOn
字段,而且会看到该字段已经给予‘1900-01-01T00:00:00.000’ 这个默认值。第二行则是在
_MigrationHistory新增一条版本记录,如下图:
此时,可以自定义201309130144538_AddMemberCreatedOn.cs 类里的 Up()
方法,在新增字段的地方改用Sql()方法,传入一段自定义的 T-SQL
脚本来创建字段,并改用自己的方法新增字段,如此一来,即可让数据库迁移在升级是自动加上此字段的默认值。
- public override void Up()
- {
- //AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));
- Sql("ALTER TABLE [dbo].[Member] ADD [CreatedOn] [datetime] NOT NULL DEFAULT getdate()");
- }
最后,运行 Update-Database 指令,这是再去检查 Member 数据表,可以看到,数据库迁移升级后的 CreatedOn 字段拥有了我们想要的 getdate() 默认值,如下图:
TIPS
在数据库迁移类中除了有 Up() 方法外,还有 Down() 方法,必须留意当降级时必要的架构的变更动作,如果自定义数据库迁移的规则写不好,可能会导致降级失败或数据库结构紊乱
自动数据库迁移
如果要启用自动数据库迁移的话,在Database.SetInitializer()
方法中使用System.Data.Entity.MigrateDatabaseToLatestVersion泛型类型,并且传入两个参数,第一个是
数据上下文类,第二个是在启用数据库迁移时自动生成的 Configuration 类,这个类喂鱼 Migrations
目录下,所以记得要加上命名空间:
- Database.SetInitializer(new MigrateDatabaseToLatestVersion<CodeFirstDemoContext, Migrations.Configuration>());
接着再开启Migrations\Configuration.cs 设置AutomaticMigrationsEnbaled 属性为 ture 即可
- AutomaticMigrationsEnabled = true;
如此一来,日后所以的数据模型变动时,都会通过数据库迁移功能自动升级数据库,当每次自动升级发生时,也会在 dbo._MigrationHistory 系统数据表里记录,并以AutomaticMigration 命名,如下图:
如何避免数据库被自动创建或自动迁移
如果想要避免数据库被自动创建或自动迁移,则修改Database.SetInitializer() 方法,如:
- Database.SetInitializer<CodeFirstDemoContext>(null);
即可避免数据库被自动创建或自动迁移。
Code First 数据库迁移的更多相关文章
- Entity Framework 5.0系列之Code First数据库迁移
我们知道无论是"Database First"还是"Model First"当模型发生改变了都可以通过Visual Studio设计视图进行更新,那么对于Cod ...
- 3.3 使用Code First数据库迁移
当Entity Framework Code First的数据模型发生异动时,默认会引发一个System.InvalidOpertaionException异常.一种解决方法是在Global.asax ...
- 使用 Code First 数据库迁移
当 Entity Framework Code First 的数据模型发生改变时,默认会引发一个System.InvalidOperationException 的异常.解决方法是使用DropCrea ...
- MVC VS2012 Code First 数据库迁移教程
1.在“服务资源管理器”连接数据库 2.打开工具-Nuget程序包管理器“程序包管理器控制台” 3.控制台输入命令:PM> Enable-Migrations -StartUpProjectNa ...
- ASP.NET MVC 4下 Code First 数据库迁移
一.命令开启 1.打开控制台:视图->其他窗口->程序包管理器控制台: 2.启动数据库迁移,执行命令:enable-migrations 创建成功后会新增migrations目录等. 若 ...
- Code First数据库迁移
生成数据库 修改类文件PortalContext.cs的静态构造函数,取消当数据库模型发生改变时删除当前数据库重建新数据库的设置. PortalContext() { Database.SetInit ...
- EF Code First 数据库迁移Migration剖析
1.简介 Entity Framework 的Code First 方式,提供了一种方式:编写模型Model,生成模型变更,根据模型变更修改数据库. 而其所以来的环境就是强大的Nuget,如果还在是V ...
- [翻译][MVC 5 + EF 6] 5:Code First数据库迁移与程序部署
原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用 ...
- Asp.net Mvc Entity Framework Code First 数据库迁移
1.创建Mvc项目 2.安装Entity Framework 2.1.如下图打开程序包管理器控制台: 2.2.输入命令Install-Package EntityFramework,即可安装Entit ...
随机推荐
- Dubbo学习笔记0:RPC框架Dubbo介绍
整体来说,一个公司业务系统的演进流程基本都是从单体应用到多应用.在单体应用时,不同业务模块相互调用直接在本地JVM进程内就可以完成,而变为多个应用时,相互之间进行通信就不能简单的进行本地调用了,因为不 ...
- laravel new xxx 安装laravel 慢的问题
问题:使用官方文档上安装 laravel laravel new xxx 安装速度奇慢无比,设置了composer 全局镜像也没有用 composer config -g repo.packagist ...
- 标准vim配置文件 带注释(适合C++编译)
我的vim配置文件,适合大多数人习惯 """""""""""""&qu ...
- 钉钉头像大小设置 阿里cdn尺寸截取参数设置
默认api的接口返回的avatar字段,是原始图片大小字段,尺寸和空间都是原始大小,如果想节省流量或统一尺寸,可以用阿里cdn自带的尺寸截取功能, 比如钉钉头像 avatar字段 返回值为原始大小ht ...
- 《区块链100问》第81集:应用类项目Augur
Augur是基于以太坊区块链打造的去中心化预测平台,于2015年6月正式发布,是以太坊上的第一款应用. Augur采用了一个叫“群体智慧”的概念,它的意思是,一群人的智慧会高于这群人中最聪明的人.所以 ...
- 一步一步搭建oracle 11gR2 rac+dg之共享磁盘设置(三)【转】
一步一步在RHEL6.5+VMware Workstation 10上搭建 oracle 11gR2 rac + dg 之共享磁盘准备 (三) 注意:这一步是配置rac的过程中非常重要的一步,很多童鞋 ...
- 003_ElasticSearch详解与优化设计
简介 概念 安装部署 ES安装 数据索引 索引优化 内存优化 1简介 ElasticSearch(简称ES)是一个分布式.Restful的搜索及分析服务器,设计用于分布式计算:能够达到实时搜索,稳定, ...
- Python全局变量和局部变量
全局变量和局部变量 定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域. 局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问.调用函数时,所有在函数内声明的变量 ...
- 一篇文章读懂开源web引擎Crosswalk-《转载》
前言 Web技术的优势早已被广大应用开发者熟知,比如可与云服务轻松集成,基于响应式UI设计的精美布局,高度的开放性,跨平台能力, 高效的分发与部署等等.伴随着移动互联网的快速发展与HTML5技术的逐步 ...
- css同时满足两个类名才有效果的写法
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...