引言

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

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

 

准备一个简单的 EFCodeFirst Demo

 

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

  • 添加 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();
}
}
}

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

运行程序后我们可以查看下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…出问题啦!!

 

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

运行完命令后我们会发现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. 现在我们看下如何通过修改自动生成的代码实现自定义Migration.

  • Blog 添加 Rating 属性
public int Rating { get; set; }

我们增加一个Post类

public class Post
{
public int PostId { get; set; }
[MaxLength(200)]
public string Title { get; set; }
public string Content { get; set; } public int BlogId { get; set; }
public Blog Blog { get; set; }
}

 

  • Blog 类增加一个 Posts collection 属性来组织Blog和Post的关系
public virtual List<Post> Posts { get; set; }

 

我们将用Add-Migration 生成 migration,并命名为AddPostClass.

  • 在Package Manager Console运行 Add-Migration AddPostClass .

Code First Migrations在组织修改时的确做的不错, 但我还是想做点修改:

  1. 首先 为 Posts.Title 列增加一个unique index
  2. 增加非空列 Blogs.Rating . 如果数据表中已经有数据,会给新增的该列一个CLR中该类型的默认值(因为Rating 是integer所以设为 0),但我们想为已经存在的数据,设置Rating 默认值为3 .
namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations; public partial class AddPostClass : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Posts",
c => new
{
PostId = c.Int(nullable: false, identity: true),
Title = c.String(maxLength: 200),
Content = c.String(),
BlogId = c.Int(nullable: false),
})
.PrimaryKey(t => t.PostId)
.ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true)
.Index(t => t.BlogId)
.Index(p => p.Title, unique: true); AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
} public override void Down()
{
DropIndex("dbo.Posts", new[] { "Title" });
DropIndex("dbo.Posts", new[] { "BlogId" });
DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs");
DropColumn("dbo.Blogs", "Rating");
DropTable("dbo.Posts");
}
}
}

 

我们修改migration来满足我们的需求 ,运行Update-Database 来升级数据库到最新版本. 我们可以通过指定 –Verbose 看到 SQL的执行情况.

  • 在 Package Manager Console运行 Update-Database –Verbose .

 

 

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

到目前我们了解了到Migration的操作不会修改数据也不会转移数据到新数据库,现在就让我们看下如何搬运我们的数据,这里没有原生支持,但我们可以在通过在指定的位置执行任意想要的SQL脚本来实现我们的想法。

添加 Post.Abstract 属性. 稍后我们将用 Content的内容来生成Abstract .

public string Abstract { get; set; }

我们将用 Add-Migration 命令让Code First Migrations 为我们生成最接近我们想要migration.

  • 在Package Manager Console运行 Add-Migration AddPostAbstract 命令.
  • 生成的migration 注意到schema 的变化,但没能实现用Post的前100个字符content 来填充Abstract 列. 我可以通过SQL 运行UPDATE 命令,在创建Abstract 列.

    (Adding in line 12 in the code below)

namespace MigrationsDemo.Migrations
{
using System;
using System.Data.Entity.Migrations; public partial class AddPostAbstract : DbMigration
{
public override void Up()
{
AddColumn("dbo.Posts", "Abstract", c => c.String()); Sql("UPDATE dbo.Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL");
} public override void Down()
{
DropColumn("dbo.Posts", "Abstract");
}
}
}

我们修改migration来满足我们的需求 ,运行Update-Database 来升级数据库到最新版本. 我们可以通过指定 –Verbose 看到 SQL的执行情况.

  • 在 Package Manager Console运行 Update-Database –Verbose .

 

 

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

到目前为止我们已经可以将数据库更新到最新版本Migration,但有时需要升级或降级到指定的Migration.

现在有个特殊情况想让数据库回到运行完AddBlogUrl migration的状态。这事我们就可以使用–TargetMigration 来降级数据库到该版本

  • 在Package Manager Console 运行Update-Database –TargetMigration: AddBlogUrl 命令.

该命令将会运行  AddBlogAbstractAddPostClass migration的 Down脚本 .

如果想回到最初 Update-Database –TargetMigration: $InitialDatabase 这条命令将会帮你一步到位.

 

 

获取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();
}
}
}

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

 

源码下载 MigrationsDemo.rar

 

参考

Code First Migrations

Code First Migrations in Team Environments

Entity Framework 6 中 Code First 的好处

Entity Framework 6 Code First Migrations with Multiple Data Contexts

Understanding Entity Framework Code First Migrations

EF6 DataMigration 从入门到进阶的更多相关文章

  1. SQL Server 扩展事件(Extented Events)从入门到进阶(1)——从SQL Trace到Extented Events

    由于工作需要,决定深入研究SQL Server的扩展事件(Extended Events/xEvents),经过资料搜索,发现国外大牛的系列文章,作为“学习”阶段,我先翻译这系列文章,后续在工作中的心 ...

  2. Wireshark入门与进阶系列(一)

    摘自http://blog.csdn.net/howeverpf/article/details/40687049 Wireshark入门与进阶系列(一) “君子生非异也,善假于物也”---荀子 本文 ...

  3. Wireshark入门与进阶系列(二)

    摘自http://blog.csdn.net/howeverpf/article/details/40743705 Wireshark入门与进阶系列(二) “君子生非异也,善假于物也”---荀子 本文 ...

  4. Wireshark入门与进阶---数据包捕获与保存的最基本流程

    Wireshark入门与进阶系列(一) "君子生非异也.善假于物也"---荀子 本文由CSDN-蚍蜉撼青松 [主页:http://blog.csdn.net/howeverpf]原 ...

  5. 用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的Code First迁移和部署

    用 MVC 5 的 EF6 Code First 入门 系列:MVC程序中实体框架的Code First迁移和部署 这是微软官方SignalR 2.0教程Getting Started with En ...

  6. SQL Server AlwaysON从入门到进阶(3)——基础架构

    本文属于SQL Server AlwaysON从入门到进阶系列文章 前言: 本文将更加深入地讲解WSFC所需的核心组件.由于AlwaysOn和FCI都需要基于WSFC之上,因此我们首先要了解在Wind ...

  7. SQL Server AlwaysON从入门到进阶(2)——存储

    本文属于SQL Server AlwaysON从入门到进阶系列文章 前言: 本节讲解关于SQL Server 存储方面的内容,相对于其他小节而言这节比较短.本节会提供一些关于使用群集或者非群集系统过程 ...

  8. SQL Server AlwaysON从入门到进阶(1)——何为AlwaysON?

    本文属于SQL Server AlwaysON从入门到进阶系列文章 本文原文出自Stairway to AlwaysOn系列文章.根据工作需要在学习过程中顺带翻译以供参考.系列文章包含: SQL Se ...

  9. SQL Server 扩展事件(Extented Events)从入门到进阶(4)——扩展事件引擎——基本概念

    本文属于 SQL Server 扩展事件(Extented Events)从入门到进阶 系列 在第一二节中,我们创建了一些简单的.类似典型SQL Trace的扩展事件会话.在此过程中,介绍了很多扩展事 ...

随机推荐

  1. LINQ to SQL语句(11)之Update

    说明:更新操作,先获取对象,进行修改操作之后,直接调用SubmitChanges()方法即可提交.注意,这里是在同一个DataContext中,对于不同的DataContex看下面的讲解. 1.简单形 ...

  2. [WCF编程]12.事务:事务概述

    一.事务概述 维护系统一致性和正确地处理错误恢复挑战的最佳方式是使用事务. 一个事务就是一个复杂操作的集合,这个集合中任何一个操作的失败都会引起整个集合的失败. 尽管在事务进行时系统被允许暂时出于不一 ...

  3. PHP JSON数组与对象的理解

    在PHP后端和客户端数据交互的过程中,JSON数据中有时格式不定,一会儿是数组,一会儿是对象,弄得客户端开发人员要崩溃的感觉. 因此,前后端相关人员先对PHP的json_encode函数原理有必要的了 ...

  4. powershell脚本,命令行参数传值,并绑定变量的例子

    这是小技巧文章,所以文章不长.但原创唯一,非常重要.我搜了下,还真没有人发 powershell怎样 [命令行 参数 绑定],所以我决定写成博客. 搜索关键字如下: powershell 命令行 参数 ...

  5. Spring学习系列(二) 自动化装配Bean

    一.Spring装配-自动化装配 @Component和@ComponentScan 通过spring注解(@Component)来表明该类会作为组件类,并告知Spring要为这类创建bean,不过组 ...

  6. 改善SQL语句(转)

    二.改善SQL语句          很多人不知道SQL语句在SQL SERVER中是如何执行的,他们担心自己所写的SQL语句会被SQL SERVER误解.比如:   select * from ta ...

  7. [deviceone开发]-do_SegmentView和do_SlideView联动的示例

    一.简介 示例展示do_SegmentView和do_SlideView联动的使用,这二个组件很常用,而且这个组合也非常常用,类似网易新闻的效果,上面滑动带动下面的slideview滑动,反过来也是. ...

  8. [python]沪深龙虎榜数据导入通达信的自选板块,并标注于K线图上

    将沪深龙虎榜数据导入通达信的自选板块,并标注于K线图上 原理:python读取前一次处理完的计算5日后涨跌幅输出的csv文件 文件名前加"[paint]" 安照通达信的画图文件和板 ...

  9. 使用setTimeout模拟setInterval效果

    由于现在部分浏览器基于对系统性能的优化,在使用setInterval的时候,在页面没有获得关注的状态,浏览器可以会自动将setInterval终端,等到该页面重新获得关注时再开启.这样就会使得一些基于 ...

  10. php文件之间传值的三种主流并且常用的方式

    一.表单传值 在<form>中的action填入要跳转页面的路径,method填入POST或者GET方法.表单中的提交按钮按下后,就会把<form>中有value都传到要跳转的 ...