当 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 实体类定义如下:

  1. namespace CodeFirstDemo.Models
  2. {
  3. public partial class Member
  4. {
  5. public Member()
  6. {
  7. this.Guestbooks = new List<Guestbook>();
  8. }
  9. public int Id { get; set; }
  10. public string Name { get; set; }
  11. public string Email { get; set; }
  12. public virtual ICollection<Guestbook> Guestbooks { get; set; }
  13. }
  14. }

Guestbook 实体类定义如下:

  1. namespace CodeFirstDemo.Models
  2. {
  3. public partial class Guestbook
  4. {
  5. public int Id { get; set; }
  6. public string Message { get; set; }
  7. public System.DateTime CreatedOn { get; set; }
  8. public int MemberId { get; set; }
  9. public virtual Member Member { get; set; }
  10. }
  11. }

在Models 文件夹下建立Mapping 文件夹,并建立对应实体类的关系映射类MemberMap 、GuestbookMap

MemberMap 类定义如下:

  1. namespace CodeFirstDemo.Models.Mapping
  2. {
  3. public class MemberMap : EntityTypeConfiguration<Member>
  4. {
  5. public MemberMap()
  6. {
  7. // Primary Key
  8. this.HasKey(t => t.Id);
  9. // Properties
  10. this.Property(t => t.Id)
  11. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
  12. this.Property(t => t.Name)
  13. .IsRequired()
  14. .HasMaxLength(10);
  15. this.Property(t => t.Email)
  16. .IsRequired()
  17. .HasMaxLength(200);
  18. // Table & Column Mappings
  19. this.ToTable("Member");
  20. this.Property(t => t.Id).HasColumnName("Id");
  21. this.Property(t => t.Name).HasColumnName("Name");
  22. this.Property(t => t.Email).HasColumnName("Email");
  23. }
  24. }
  25. }

GuestbookMap 类定义如下:

  1. namespace CodeFirstDemo.Models.Mapping
  2. {
  3. public class GuestbookMap : EntityTypeConfiguration<Guestbook>
  4. {
  5. public GuestbookMap()
  6. {
  7. // Primary Key
  8. this.HasKey(t => t.Id);
  9. // Properties
  10. this.Property(t => t.Message)
  11. .IsRequired()
  12. .HasMaxLength(200);
  13. // Table & Column Mappings
  14. this.ToTable("Guestbook");
  15. this.Property(t => t.Id).HasColumnName("Id");
  16. this.Property(t => t.Message).HasColumnName("Message");
  17. this.Property(t => t.CreatedOn).HasColumnName("CreatedOn");
  18. this.Property(t => t.MemberId).HasColumnName("MemberId");
  19. // Relationships
  20. this.HasRequired(t => t.Member)
  21. .WithMany(t => t.Guestbooks)
  22. .HasForeignKey(d => d.MemberId);
  23. }
  24. }
  25. }

在Models 建立数据库上下文类CodeFirstDemoContext

CodeFirstDemoContext 类定义如下:

  1. namespace CodeFirstDemo.Models
  2. {
  3. public partial class CodeFirstDemoContext : DbContext
  4. {
  5. static CodeFirstDemoContext()
  6. {
  7. //Database.SetInitializer<CodeFirstDemoContext>(new DropCreateDatabaseIfModelChanges<CodeFirstDemoContext>());
  8. }
  9. public CodeFirstDemoContext()
  10. : base("Name=CodeFirstDemoContext")
  11. {
  12. }
  13. public DbSet<Guestbook> Guestbooks { get; set; }
  14. public DbSet<Member> Members { get; set; }
  15. protected override void OnModelCreating(DbModelBuilder modelBuilder)
  16. {
  17. modelBuilder.Configurations.Add(new GuestbookMap());
  18. modelBuilder.Configurations.Add(new MemberMap());
  19. }
  20. }
  21. }

Models 文件夹结构下

以上就是一个简单的 Code First 结构了

接下来在Web.config 添加数据库连接字符串

  1. <connectionStrings>
  2. <add name="CodeFirstDemoContext" connectionString="Data Source=vin-pc;Initial Catalog=CodeFirstDemo;Persist Security Info=True;User ID=sa;Password=123456;MultipleActiveResultSets=True"
  3. providerName="System.Data.SqlClient" />
  4. </connectionStrings>

然后添加一个控制器HomeController

  1. namespace CodeFirstDemo.Controllers
  2. {
  3. public class HomeController : Controller
  4. {
  5. //
  6. // GET: /Home/
  7. public ActionResult Index()
  8. {
  9. CodeFirstDemoContext db = new CodeFirstDemoContext();
  10. Member member = new Member { Name = "tt", Email = "qwe@qq.com" };
  11. db.Members.Add(member);
  12. db.SaveChanges();
  13. return View();
  14. }
  15. }
  16. }

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 值读出,并创建这个类的属性,其属性就是包含那次数据模型的完整描述。

  1. namespace CodeFirstDemo.Migrations
  2. {
  3. using System;
  4. using System.Data.Entity.Migrations;
  5. public partial class InitialCreate : DbMigration
  6. {
  7. public override void Up()
  8. {
  9. CreateTable(
  10. "dbo.Guestbook",
  11. c => new
  12. {
  13. Id = c.Int(nullable: false, identity: true),
  14. Message = c.String(nullable: false, maxLength: 200),
  15. CreatedOn = c.DateTime(nullable: false),
  16. MemberId = c.Int(nullable: false),
  17. })
  18. .PrimaryKey(t => t.Id)
  19. .ForeignKey("dbo.Member", t => t.MemberId, cascadeDelete: true)
  20. .Index(t => t.MemberId);
  21. CreateTable(
  22. "dbo.Member",
  23. c => new
  24. {
  25. Id = c.Int(nullable: false, identity: true),
  26. Name = c.String(nullable: false, maxLength: 5),
  27. Email = c.String(nullable: false, maxLength: 200),
  28. })
  29. .PrimaryKey(t => t.Id);
  30. }
  31. public override void Down()
  32. {
  33. DropIndex("dbo.Guestbook", new[] { "MemberId" });
  34. DropForeignKey("dbo.Guestbook", "MemberId", "dbo.Member");
  35. DropTable("dbo.Member");
  36. DropTable("dbo.Guestbook");
  37. }
  38. }
  39. }

2.      Configuration.cs

这个类定义了运行数据库迁移时该有的行为。默认情况下,数据库并不会发生迁移动作,除非将 Configuration() 内的 AutomaticMigrationsEnabled 改为 true,才会让 CodeFirst 自动迁移数据库。

  1. namespace CodeFirstDemo.Migrations
  2. {
  3. using System;
  4. using System.Data.Entity;
  5. using System.Data.Entity.Migrations;
  6. using System.Linq;
  7. internal sealed class Configuration : DbMigrationsConfiguration<CodeFirstDemo.Models.CodeFirstDemoContext>
  8. {
  9. public Configuration()
  10. {
  11. AutomaticMigrationsEnabled = false;
  12. }
  13. protected override void Seed(CodeFirstDemo.Models.CodeFirstDemoContext context)
  14. {
  15. //  This method will be called after migrating to the latest version.
  16. //  You can use the DbSet<T>.AddOrUpdate() helper extension method
  17. //  to avoid creating duplicate seed data. E.g.
  18. //
  19. //    context.People.AddOrUpdate(
  20. //      p => p.FullName,
  21. //      new Person { FullName = "Andrew Peters" },
  22. //      new Person { FullName = "Brice Lambson" },
  23. //      new Person { FullName = "Rowan Miller" }
  24. //    );
  25. //
  26. }
  27. }
  28. }

运行数据库迁移

下面来更改 Member 的数据模型,添加两个字段,分别是 UserName 和 Password 属性。

  1. namespace CodeFirstDemo.Models
  2. {
  3. public partial class Member
  4. {
  5. public Member()
  6. {
  7. this.Guestbooks = new List<Guestbook>();
  8. }
  9. public int Id { get; set; }
  10. public string Name { get; set; }
  11. public string Email { get; set; }
  12. public string UserName { get; set; }
  13. public string Password { get; set; }
  14. public virtual ICollection<Guestbook> Guestbooks { get; set; }
  15. }
  16. }

通过Package Manager Console 输入 Add-Migration 指令,来新增一条数据库迁移版本,输入时必须带上一个“版本名称”参数。例如,想要取名为AddUsernamePassword,则可以输入以下指令:

运行完成后,会在 Migrations 文件夹新增一个文件,如下图:

这次运行 Add-Migration 指令,所代表的意思就是新增一次运行数据库迁移命令,VS2012会自动对比当前数据库中的 Model 定义与当前更改过的数据模型,并将差异的字段变化写入这个自动新增的类内,程序代码如下:

  1. namespace CodeFirstDemo.Migrations
  2. {
  3. using System;
  4. using System.Data.Entity.Migrations;
  5. public partial class AddUsernamePassword : DbMigration
  6. {
  7. public override void Up()
  8. {
  9. AddColumn("dbo.Member", "UserName", c => c.String());
  10. AddColumn("dbo.Member", "Password", c => c.String());
  11. }
  12. public override void Down()
  13. {
  14. DropColumn("dbo.Member", "Password");
  15. DropColumn("dbo.Member", "UserName");
  16. }
  17. }
  18. }

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

  1. namespace CodeFirstDemo.Models
  2. {
  3. public partial class Member
  4. {
  5. public Member()
  6. {
  7. this.Guestbooks = new List<Guestbook>();
  8. }
  9. public int Id { get; set; }
  10. public string Name { get; set; }
  11. public string Email { get; set; }
  12. public string UserName { get; set; }
  13. public string Password { get; set; }
  14. public DateTime CreatedOn { get; set; }
  15. public virtual ICollection<Guestbook> Guestbooks { get; set; }
  16. }
  17. }

MemberMap.cs

  1. namespace CodeFirstDemo.Models.Mapping
  2. {
  3. public class MemberMap : EntityTypeConfiguration<Member>
  4. {
  5. public MemberMap()
  6. {
  7. // Primary Key
  8. this.HasKey(t => t.Id);
  9. // Properties
  10. this.Property(t => t.Id)
  11. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
  12. this.Property(t => t.Name)
  13. .IsRequired()
  14. .HasMaxLength(10);
  15. this.Property(t => t.Email)
  16. .IsRequired()
  17. .HasMaxLength(200);
  18. this.Property(t => t.CreatedOn)
  19. .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
  20. // Table & Column Mappings
  21. this.ToTable("Member");
  22. this.Property(t => t.Id).HasColumnName("Id");
  23. this.Property(t => t.Name).HasColumnName("Name");
  24. this.Property(t => t.Email).HasColumnName("Email");
  25. }
  26. }
  27. }

然后运行一次 Add-Migration指令,并指定版本名称为 AddMemberCreatedOn

这时,再 Migrations 目录下多出一个201309130144538_AddMemberCreatedOn.cs 文件

  1. namespace CodeFirstDemo.Migrations
  2. {
  3. using System;
  4. using System.Data.Entity.Migrations;
  5. public partial class AddMemberCreatedOn : DbMigration
  6. {
  7. public override void Up()
  8. {
  9. AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));
  10. }
  11. public override void Down()
  12. {
  13. DropColumn("dbo.Member", "CreatedOn");
  14. }
  15. }
  16. }

这次我们用不一样的参数来运行数据库迁移,加上–Script 参数,Update-Database –Script

运行完后,会输出完整的数据库更新 T-SQL 脚本,其中第一行就是在 Member 数据表中新增一个 CreatedOn
字段,而且会看到该字段已经给予‘1900-01-01T00:00:00.000’ 这个默认值。第二行则是在
_MigrationHistory新增一条版本记录,如下图:

此时,可以自定义201309130144538_AddMemberCreatedOn.cs 类里的 Up()
方法,在新增字段的地方改用Sql()方法,传入一段自定义的 T-SQL
脚本来创建字段,并改用自己的方法新增字段,如此一来,即可让数据库迁移在升级是自动加上此字段的默认值。

  1. public override void Up()
  2. {
  3. //AddColumn("dbo.Member", "CreatedOn", c => c.DateTime(nullable: false));
  4. Sql("ALTER TABLE [dbo].[Member] ADD [CreatedOn] [datetime] NOT NULL DEFAULT getdate()");
  5. }

最后,运行 Update-Database 指令,这是再去检查 Member 数据表,可以看到,数据库迁移升级后的 CreatedOn 字段拥有了我们想要的 getdate() 默认值,如下图:

TIPS

在数据库迁移类中除了有 Up() 方法外,还有 Down() 方法,必须留意当降级时必要的架构的变更动作,如果自定义数据库迁移的规则写不好,可能会导致降级失败或数据库结构紊乱

自动数据库迁移

如果要启用自动数据库迁移的话,在Database.SetInitializer()
方法中使用System.Data.Entity.MigrateDatabaseToLatestVersion泛型类型,并且传入两个参数,第一个是
数据上下文类,第二个是在启用数据库迁移时自动生成的 Configuration 类,这个类喂鱼 Migrations
目录下,所以记得要加上命名空间:

  1. Database.SetInitializer(new MigrateDatabaseToLatestVersion<CodeFirstDemoContext, Migrations.Configuration>());

接着再开启Migrations\Configuration.cs 设置AutomaticMigrationsEnbaled 属性为 ture 即可

  1. AutomaticMigrationsEnabled = true;

如此一来,日后所以的数据模型变动时,都会通过数据库迁移功能自动升级数据库,当每次自动升级发生时,也会在 dbo._MigrationHistory 系统数据表里记录,并以AutomaticMigration 命名,如下图:

如何避免数据库被自动创建或自动迁移

如果想要避免数据库被自动创建或自动迁移,则修改Database.SetInitializer() 方法,如:

  1. Database.SetInitializer<CodeFirstDemoContext>(null);

即可避免数据库被自动创建或自动迁移。

Code First 数据库迁移的更多相关文章

  1. Entity Framework 5.0系列之Code First数据库迁移

    我们知道无论是"Database First"还是"Model First"当模型发生改变了都可以通过Visual Studio设计视图进行更新,那么对于Cod ...

  2. 3.3 使用Code First数据库迁移

    当Entity Framework Code First的数据模型发生异动时,默认会引发一个System.InvalidOpertaionException异常.一种解决方法是在Global.asax ...

  3. 使用 Code First 数据库迁移

    当 Entity Framework Code First 的数据模型发生改变时,默认会引发一个System.InvalidOperationException 的异常.解决方法是使用DropCrea ...

  4. MVC VS2012 Code First 数据库迁移教程

    1.在“服务资源管理器”连接数据库 2.打开工具-Nuget程序包管理器“程序包管理器控制台” 3.控制台输入命令:PM> Enable-Migrations -StartUpProjectNa ...

  5. ASP.NET MVC 4下 Code First 数据库迁移

     一.命令开启 1.打开控制台:视图->其他窗口->程序包管理器控制台: 2.启动数据库迁移,执行命令:enable-migrations 创建成功后会新增migrations目录等. 若 ...

  6. Code First数据库迁移

    生成数据库 修改类文件PortalContext.cs的静态构造函数,取消当数据库模型发生改变时删除当前数据库重建新数据库的设置. PortalContext() { Database.SetInit ...

  7. EF Code First 数据库迁移Migration剖析

    1.简介 Entity Framework 的Code First 方式,提供了一种方式:编写模型Model,生成模型变更,根据模型变更修改数据库. 而其所以来的环境就是强大的Nuget,如果还在是V ...

  8. [翻译][MVC 5 + EF 6] 5:Code First数据库迁移与程序部署

    原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application 1.启用 ...

  9. Asp.net Mvc Entity Framework Code First 数据库迁移

    1.创建Mvc项目 2.安装Entity Framework 2.1.如下图打开程序包管理器控制台: 2.2.输入命令Install-Package EntityFramework,即可安装Entit ...

随机推荐

  1. Swift动态添加UIImageView并添加事件

    Swift动态添加UIImageView并添加事件: 1. 创建UIImageView实例,并进行初始化 2. 设置UIImageView的用户交互属性userInteractionEnabled为T ...

  2. asp.net后台操作javascript:confirm返回值

    在asp.net中使用confirm可以分为两种: 1.没有使用ajax,confirm会引起也面刷新 2.使用了ajax,不会刷新 A.没有使用ajax,可以用StringBuilder来完成. ( ...

  3. mysql的force index

    MSQL中使用order by 有个坑,会默认走order by 后面的索引.而不走where条件里应该走的索引.大家在使用时要绕过此坑. 如下语句因为order by 走了settle_id这个主键 ...

  4. win7下设置挂载Linux服务器nfs共享的数据 -- 转

    最近学习NFS文件系统的使用,Ubuntu上配置好了,想和Win7共享数据,所以网上搜到了这篇文章.借花献佛,跟大家共享一下: http://www.2cto.com/os/201207/139132 ...

  5. CF 1008C Reorder the Array

    You are given an array of integers. Vasya can permute (change order) its integers. He wants to do it ...

  6. 2016.6.26——Maximum Depth of Binary Tree

    Maximum Depth of Binary Tree 本题收获 1.树时使用递归 2.注意边界条件时输出的值,仔细阅读题意,若是面试时,问清边界条件. 题目: Given a binary tre ...

  7. 阿里面试回来,想和Java程序员谈一谈

    引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...

  8. awk的常用内置函数的使用【转】

    手把手教你在linux下熟悉使用awk的指令结构 (15) 大家好,今天和大家说一下awk吧.反正正则 早晚也要和大家说,不如一点一点和大家先交代清楚了,省得以后和大家说的时候,大家有懵的感觉... ...

  9. Python Challenge 第 4 关攻略:linkedlist

    代码 import requests url = "http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing={}" ...

  10. 我看到的最棒的Twisted入门教程!

    http://www.douban.com/note/232204441/ http://www.cnblogs.com/sevenyuan/archive/2010/11/18/1880681.ht ...