你的位置:首页 > ASP.net教程

[ASP.net教程]EF6 DataMigration


引言

在EntityFramework的开发过程中我们有时因需求变化或者数据结构设计的变化经常会改动表结构。但数据库Schema发生变化时EF会要求我们做DataMigrationUpdateDatabase。但在这个过程中如何才能保证现有数据库的数据存在。

另外本文只针对CodeFirst的方式来做。

 

准备一个简单的 EFCodeFirst Demo

 

  • 创建一个控制台程序 MigrationsDemo
  • NuGet 获取最新版 EntityFramework 
    • Tools –> Library Package Manager –> Package Manager Console
    • Run the Install-Package EntityFramework command

image

  • 添加 Model.cs文件. 定义一个 Blog 类作为我们的业务模型 并为其添加 BlogContext 使用 EF (Code First )模式的DataContext

 

using System.Data.Entity;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Data.Entity.Infrastructure;namespace MigrationsDemo{  public class BlogContext : DbContext  {    public DbSet<Blog> Blogs { get; set; }  }  public class Blog  {    public int BlogId { get; set; }    public string Name { get; set; }  }}

在Program.cs文件中简单演示下如何使用我们刚才定义好的业务类和DataContext。

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace MigrationsDemo{  class Program  {    static void Main(string[] args)    {      using (var db = new BlogContext())      {        db.Blogs.Add(new Blog { Name = "Another Blog " });        db.SaveChanges();        foreach (var blog in db.Blogs)        {          Console.WriteLine(blog.Name);        }      }      Console.WriteLine("Press any key to exit...");      Console.ReadKey();    }  }}

迫不及待的运行下我们的小程序:

image

运行程序后我们可以查看下EF为我们自动创建的MigrationsCodeDemo.BlogContext 数据库:

If SQL Express is installed (included in Visual Studio 2010) then the database is created on your local SQL Express instance (.\SQLEXPRESS). If SQL Express is not installed then Code First will try and use LocalDb ((localdb)\v11.0) - LocalDb is included with Visual Studio 2012.
Note: SQL Express will always get precedence if it is installed, even if you are using Visual Studio 2012

 

 

启用 Migration

现在我想为我们的 Blog 类增加一个URL属性。

在Blog类增加如下一行代码:

public string Url { get; set; }

再次运行我们的程序!

Ops…出问题啦!!

image

 

If you were to run the application again you would get an InvalidOperationException stating The model backing the 'BlogContext' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269).

 

根据异常信息提示,我们要为DataContext启用Migrations

  • 在Package Manager Console 窗口中 运行命令 Enable-Migrations

imageimage

运行完命令后我们会发现Project树中多了一个 Migrations folder to our project, this new folder contains two files:

  • The Configuration class. 这个类用于自定义Migration的行为. 这里我们使用默认Configuration.
    我们项目中只是一个简单的 Code First context , Enable-Migrations 自动将context type 应用到 configuration 的泛型类型参数.
  • An InitialCreate migration. 因为我们在启用migration之前已经有Code First 数据库,所以就会生成这个migration。 这个migration 的代码表示了数据库中已有的 objects . 在该例中 就是 Blog 表有 BlogIdName 两列. 文件名中含有一个时间戳,这样方便我们排序。

If the database had not already been created this InitialCreate migration would not have been added to the project. Instead, the first time we call Add-Migration the code to create these tables would be scaffolded to a new migration.

如果刚开始没有创建数据库,InitialCreate  migration将会在第一次执行 Add-Migration 命令时生成。

 

同一个数据库同时对应多个Model

When using versions prior to EF6, only one Code First model could be used to generate/manage the schema of a database. This is the result of a single __MigrationsHistory table per database with no way to identify which entries belong to which model.

Starting with EF6, the Configuration class includes a ContextKey property. This acts as a unique identifier for each Code First model. A corresponding column in the __MigrationsHistorytable allows entries from multiple models to share the table. By default, this property is set to the fully qualified name of your context.

 

如何生成和运行Migration

 

Code First Migrations 有两个主要的命令需要熟悉.

  • Add-Migration 组织基于上次Migration后的一切修改生成这次的Migration。
  • Update-Database 将所有挂起(未执行)的migration应用到数据库。

 

我们生成的Migration需要注意添加了个新的Url属性,Add-Migration  允许我们给Migration 名字,这里我指定为 AddBlogUrl.

  • 在Package Manager Console 中执行 Add-Migration AddBlogUrl 命令
  • 在Migrations文件夹中生成了一个 AddBlogUrl migration.并且自动为名字添加了个时间戳,方便我们排序。
namespace MigrationsDemo.Migrations{  using System;  using System.Data.Entity.Migrations;    public partial class AddBlogUrl : DbMigration  {    public override void Up()    {      AddColumn("dbo.Blogs", "Url", c => c.String());    }        public override void Down()    {      DropColumn("dbo.Blogs", "Url");    }  }}

 

我们可以编辑这个 migration,但这里我们无需做任何修改 .该是使用 Update-Database 将Migration应用到数据库.

  • 在Package Manager Console 运行 Update-Database 命令
  • Code First Migrations 将把Migrations 文件夹中的Migration于已经应用到数据库中Migration进行比较。 最终我们会看到AddBlogUrl 将会被应用数据库.

最终 MigrationsDemo.BlogContext 数据库中的Blogs表会增加 Url 列.

 

自定义 Migration

 

Migration过程中的数据迁移|自定义SQL

 

将数据库更新到指定的版本Migration(包括升级 ,降级或者说是回滚)

 

获取Migration的SQL 脚本

如果另外一个开发人员想要应用我们对数据库的修改,他能通过Source Control 获取我签入的最新代码,一旦他有了最新代码就可以通过Update-Database 将所有修改应用到本地。然而有时我们想把我们的修改发布到测试服务器甚至生产环境中,这样我们或许就需要一份可以交给DBA的SQL脚本。

  • 运行Update-Database 的同时指定 –Script 这样就只会生成脚本而不是应用Migration。我们也可以指定Migration的Source和Target。eg. 从空数据库 ($InitialDatabase) 到最新版本(migration AddPostAbstract).
    If you don’t specify a target migration, Migrations will use the latest migration as the target. If you don't specify a source migrations, Migrations will use the current state of the database.(如果没有指定目标版本,将会默认设置Target到最新版本Migration,如果没有指定源,将默认将当前数据库的状态设置为Source)
  • 在Package Manager Console中Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract

 

Code First Migrations 将会把实际要应用的修改生成一个.sql文件而不是应用到数据库。一旦SQL脚本生成VisualStudio会自动打开该脚本,你可以选择是否保存。

Generating Idempotent Scripts (EF6 onwards)

Starting with EF6, if you specify –SourceMigration $InitialDatabase then the generated script will be ‘idempotent’. Idempotent scripts can upgrade a database currently at any version to the latest version (or the specified version if you use –TargetMigration). The generated script includes logic to check the __MigrationsHistory table and only apply changes that haven't been previously applied.

 

应用程序启动时自动升级数据库导最新版本

在部署程序后,如果想在程序启动时自动升级数据库(通过应用Migration),你可以通过注册MigrateDatabaseToLatestVersion database initializer来实现该目的. 它是一个简单的 database initializer 但能确保数据库正确升级到最新版本. This logic is run the first time the context is used within the application process (AppDomain).

修改下Program.cs 如下示, 给BlogContext设置MigrateDatabaseToLatestVersion initializer . 注意需要引入 System.Data.Entity 命名空间

When we create an instance of this initializer we need to specify the context type (BlogContext) and the migrations configuration (Configuration) - the migrations configuration is the class that got added to our Migrations folder when we enabled Migrations.

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Data.Entity;using MigrationsDemo.Migrations;namespace MigrationsDemo{  class Program  {    static void Main(string[] args)    {      Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());      using (var db = new BlogContext())      {        db.Blogs.Add(new Blog { Name = "Another Blog " });        db.SaveChanges();        foreach (var blog in db.Blogs)        {          Console.WriteLine(blog.Name);        }      }      Console.WriteLine("Press any key to exit...");      Console.ReadKey();    }  }}

现在我们的程序就可以在启动时,自动将数据库升级到最新版本啦。

 

参考

Code First Migrations

Code First Migrations in Team Environments

Entity Framework 6 中 Code First 的好处