在EntityFramework的开发过程中我们有时因需求变化或者数据结构设计的变化经常会改动表结构。但数据库Schema发生变化时EF会要求我们做DataMigration 和UpdateDatabase。但在这个过程中如何才能保证现有数据库的数据存在。
另外本文只针对CodeFirst的方式来做。
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
现在我想为我们的 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
运行完命令后我们会发现Project树中多了一个 Migrations folder to our project, this new folder contains two files:
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 命令时生成。
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.
Code First Migrations 有两个主要的命令需要熟悉.
我们生成的Migration需要注意添加了个新的Url属性,Add-Migration 允许我们给Migration 名字,这里我指定为 AddBlogUrl.
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应用到数据库.
最终 MigrationsDemo.BlogContext 数据库中的Blogs表会增加 Url 列.
如果另外一个开发人员想要应用我们对数据库的修改,他能通过Source Control 获取我签入的最新代码,一旦他有了最新代码就可以通过Update-Database 将所有修改应用到本地。然而有时我们想把我们的修改发布到测试服务器甚至生产环境中,这样我们或许就需要一份可以交给DBA的SQL脚本。
Code First Migrations 将会把实际要应用的修改生成一个.sql文件而不是应用到数据库。一旦SQL脚本生成VisualStudio会自动打开该脚本,你可以选择是否保存。
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 的好处