这是微软官方SignalR 2.0教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第五篇:MVC程序中实体框架的Code First迁移和部署程序员
原文:Code First Migrations and Deployment with the Entity Framework in an ASP.NET MVC Application数据库
译文版权全部,谢绝全文转载——但您能够在您的网站上添加到该教程的连接。windows
到目前为止,应用程序已经能够在您本地机器上正常地运行。但若是您想将它发布在互联网上以便更多的人来使用,您须要将程序部署到WEB服务器。在本教程中你会将Contoso大学应用程序部署到Windows Azure网站的云中。服务器
本教程包含如下章节:架构
咱们建议使用源代码管理的持续集成过程部署,但本教程并不包含那些主题。更多的信息请参见source control和Building Real-World Cloud Apps with Windows Azure。mvc
当你进行新应用程序的开发时,你的数据模型会频繁地变更。而且随着每一次变更都会使数据模型与数据库脱节。你已经成功配置了实体框架让其在每一次你变动数据模型时自动删除并从新建立数据库。当您添加、删除或更改实体类或者更改你的DbContext类时,从新运行应用程序会使它自动删除已经存在的数据库并建立一个和当前数据模型相匹配的数据库。而且填充测试数据。app
这种方法在保持数据模型和数据库架构同步方面作得很是好,直到你准备将应用程序部署到生产环境。当应用程序开始生产并存储生产数据,你固然不想由于数据模型的变动而丢失成产数据(好比添加一个新列)。Code First Migrations功能解决了这个问题。经过启用Code First迁移来更新数据库架构,而不是删除和重建数据库。在本教程中,您会部署该应用程序,并准备启用迁移。框架
<entityFramework> <!--<contexts> <context type="ContosoUniversity.DAL.SchoolContext,ContosoUniversity"> <databaseInitializer type="ContosoUniversity.DAL.SchoolInitializer,ContosoUniversity"></databaseInitializer> </context> </contexts>--> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework>
<connectionStrings> <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\ContosoUniversity1.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
此更改设置该项目的第一次迁移将建立一个新的数据库,这不是必须的,但您稍后将看到为这么这样作是一个不错的主意。asp.net
enable-migrations
add-migration InitialCreate
enable-migrations命令将在项目中建立一个迁移文件夹。同时文件夹中包含一个Configuration.cs文件,你能够编辑该文件来配置迁移。
若是你在上一步中没有更改数据库名称,迁移将找到现有的数据库并自动执行add-migration命令,这没有关系。它只是意味着你不会在部署数据库以前运行迁移代码的测试。以后当您运行update-database将不会作任何改变由于数据库已经存在。
如同以前教程中,Configuration类中一样包含Seed方法。ide
internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext> { public Configuration() { AutomaticMigrationsEnabled = false; ContextKey = "ContosoUniversity.DAL.SchoolContext"; } protected override void Seed(ContosoUniversity.DAL.SchoolContext 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" } // ); // } }
Seed方法的目的是使您在Code First建立或更新数据库后插入或更新测试数据。当数据库每次建立和更新数据库架构时将调用该方法。
当您每次更改数据模型后,删除和从新建立数据库时你可使用初始类的Seed方法来插入测试数据。由于每次模型更改数据库后,数据库将被删除,全部的测试数据都将丢失。在Code First前一种,测试数据在数据库更改后是保留的。因此在Seed方法中包含测试数据一般不是必要的。事实上,你并不想要在使用迁移部署数据库到生产环境时让Seed方法来插入测试数据,由于Seed方法会在生产环境中调用。在这种状况下,只有真正须要时,才使用Seed方法来在生产环境中插入数据。例如你可能想要在部署到生产环境时在Deparment表中包含实际部门的名称。
对于本教程,您将使用迁移来部署。但为了让你可以跟容易地看到程序功能是如何无需人工操做而插入数据的,咱们将使用Seed方法来插入测试数据。
1 namespace ContosoUniversity.Migrations 2 { 3 using ContosoUniversity.Models; 4 using System; 5 using System.Collections.Generic; 6 using System.Data.Entity; 7 using System.Data.Entity.Migrations; 8 using System.Linq; 9 10 internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext> 11 { 12 public Configuration() 13 { 14 AutomaticMigrationsEnabled = false; 15 ContextKey = "ContosoUniversity.DAL.SchoolContext"; 16 } 17 protected override void Seed(ContosoUniversity.DAL.SchoolContext context) 18 { 19 var students = new List<Student> 20 { 21 new Student { FirstMidName = "Carson", LastName = "Alexander", 22 EnrollmentDate = DateTime.Parse("2010-09-01") }, 23 new Student { FirstMidName = "Meredith", LastName = "Alonso", 24 EnrollmentDate = DateTime.Parse("2012-09-01") }, 25 new Student { FirstMidName = "Arturo", LastName = "Anand", 26 EnrollmentDate = DateTime.Parse("2013-09-01") }, 27 new Student { FirstMidName = "Gytis", LastName = "Barzdukas", 28 EnrollmentDate = DateTime.Parse("2012-09-01") }, 29 new Student { FirstMidName = "Yan", LastName = "Li", 30 EnrollmentDate = DateTime.Parse("2012-09-01") }, 31 new Student { FirstMidName = "Peggy", LastName = "Justice", 32 EnrollmentDate = DateTime.Parse("2011-09-01") }, 33 new Student { FirstMidName = "Laura", LastName = "Norman", 34 EnrollmentDate = DateTime.Parse("2013-09-01") }, 35 new Student { FirstMidName = "Nino", LastName = "Olivetto", 36 EnrollmentDate = DateTime.Parse("2005-08-11") } 37 }; 38 students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s)); 39 context.SaveChanges(); 40 41 var courses = new List<Course> 42 { 43 new Course {CourseID = 1050, Title = "Chemistry", Credits = 3, }, 44 new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3, }, 45 new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3, }, 46 new Course {CourseID = 1045, Title = "Calculus", Credits = 4, }, 47 new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4, }, 48 new Course {CourseID = 2021, Title = "Composition", Credits = 3, }, 49 new Course {CourseID = 2042, Title = "Literature", Credits = 4, } 50 }; 51 courses.ForEach(s => context.Courses.AddOrUpdate(p => p.Title, s)); 52 context.SaveChanges(); 53 54 var enrollments = new List<Enrollment> 55 { 56 new Enrollment { 57 StudentID = students.Single(s => s.LastName == "Alexander").ID, 58 CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 59 Grade = Grade.A 60 }, 61 new Enrollment { 62 StudentID = students.Single(s => s.LastName == "Alexander").ID, 63 CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 64 Grade = Grade.C 65 }, 66 new Enrollment { 67 StudentID = students.Single(s => s.LastName == "Alexander").ID, 68 CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 69 Grade = Grade.B 70 }, 71 new Enrollment { 72 StudentID = students.Single(s => s.LastName == "Alonso").ID, 73 CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 74 Grade = Grade.B 75 }, 76 new Enrollment { 77 StudentID = students.Single(s => s.LastName == "Alonso").ID, 78 CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 79 Grade = Grade.B 80 }, 81 new Enrollment { 82 StudentID = students.Single(s => s.LastName == "Alonso").ID, 83 CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 84 Grade = Grade.B 85 }, 86 new Enrollment { 87 StudentID = students.Single(s => s.LastName == "Anand").ID, 88 CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID 89 }, 90 new Enrollment { 91 StudentID = students.Single(s => s.LastName == "Anand").ID, 92 CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID, 93 Grade = Grade.B 94 }, 95 new Enrollment { 96 StudentID = students.Single(s => s.LastName == "Barzdukas").ID, 97 CourseID = courses.Single(c => c.Title == "Chemistry").CourseID, 98 Grade = Grade.B 99 }, 100 new Enrollment { 101 StudentID = students.Single(s => s.LastName == "Li").ID, 102 CourseID = courses.Single(c => c.Title == "Composition").CourseID, 103 Grade = Grade.B 104 }, 105 new Enrollment { 106 StudentID = students.Single(s => s.LastName == "Justice").ID, 107 CourseID = courses.Single(c => c.Title == "Literature").CourseID, 108 Grade = Grade.B 109 } 110 }; 111 112 foreach (Enrollment e in enrollments) 113 { 114 var enrollmentInDataBase = context.Enrollments.Where( 115 s => 116 s.Student.ID == e.StudentID && 117 s.Course.CourseID == e.CourseID).SingleOrDefault(); 118 if (enrollmentInDataBase == null) 119 { 120 context.Enrollments.Add(e); 121 } 122 } 123 context.SaveChanges(); 124 } 125 } 126 }
Seed方法使用数据库上下文对象做为输入参数,并在代码中使用该对象来添加新实体到数据库。对于每一个实体类型,代码建立一个新实体的集合并将它们添加到适当的DbSet属性,而后将更改保存到数据库。在每组实体后马上调用SaveChanges方法并非必须的,但这样作能够在出现问题时让你更容易地定位问题的根源。
大多数插入对象的语句是使用AddOrUpdate方法来执行"upsert"操做。由于你每次执行更新数据库命令时Seed方法都会运行,一般在每一个迁移后你不能只是插入数据。由于您试图添加的行有可能在建立数据库后的第一次迁移中已经存在。"upsert"操做能够防止你试图添加一个已经存在的行,可是它会重写你在测试阶段对数据进行的修改。你或许不但愿这种状况在某些数据表中发生:在某些状况下你可能但愿保留你在测试阶段对测试数据所进行的更改。在这种状况下,你须要作一个条件插入操做:仅当它不存在时插入行。Seed方法同时使用以上两种方法。context.Students.AddOrUpdate(p => p.LastName, s)
此代码假定LastName是惟一的。若是您手动添加具备重复LastName的学生,你就会获得一个“序列包含多个元素”的异常。
有关如何处理容易数据,请参阅Seeding and Debugging Entity Framework (EF) DBs。有关AddOrUpdate方法的更多信息,请参阅Take care with EF 4.3 AddOrUpdate Method。
建立Enrollment实体的代码假定你在学生集合中的实体已经拥有ID值,虽然你没有在建立集合的代码中设置该值。
new Enrollment { StudentID = students.Single(s => s.LastName == "Alonso").ID, CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, Grade = Grade.B },
你在这里可使用ID值,由于当你为学生集合调用SaveChanges方法时,ID值被设置。当实体被插入到数据库时,实体框架会自动获取该实体的主键值而且更新内存中实体上的ID属性。
添加每一个Enrollment实体到Enrollments实体集合的代码不会使用AddOrUpdate方法,它会检查每个实体是否存在,若是不存在,则插入该实体。这种方法将保留经过使用应用程序UI对成绩所作的修改。代码遍历Enrollment列表中的每一个成员。若是在数据库中没有该成员,就向数据库中添加它。当你第一次更新数据库时,该数据库是空的,因此集合中的每一个enrollment实体都将被添加。
foreach (Enrollment e in enrollments) { var enrollmentInDataBase = context.Enrollments.Where( s => s.Student.ID == e.StudentID && s.Course.CourseID == e.CourseID).SingleOrDefault(); if (enrollmentInDataBase == null) { context.Enrollments.Add(e); } }
当您执行add-migration命令时,迁移将生成代码用来建立数据库。该代码一样在Migrations文件夹中,在文件名为<时间戳>_InitalCreate.cs的文件中。该类中的Up方法将按照数据模型实体集来建立数据库表格,Down方法用来删除它们。
namespace ContosoUniversity.Migrations { using System; using System.Data.Entity.Migrations; public partial class InitialCreate : DbMigration { public override void Up() { CreateTable( "dbo.Course", c => new { CourseID = c.Int(nullable: false), Title = c.String(), Credits = c.Int(nullable: false), }) .PrimaryKey(t => t.CourseID); CreateTable( "dbo.Enrollment", c => new { EnrollmentID = c.Int(nullable: false, identity: true), CourseID = c.Int(nullable: false), StudentID = c.Int(nullable: false), Grade = c.Int(), }) .PrimaryKey(t => t.EnrollmentID) .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true) .ForeignKey("dbo.Student", t => t.StudentID, cascadeDelete: true) .Index(t => t.CourseID) .Index(t => t.StudentID); CreateTable( "dbo.Student", c => new { ID = c.Int(nullable: false, identity: true), LastName = c.String(), FirstMidName = c.String(), EnrollmentDate = c.DateTime(nullable: false), }) .PrimaryKey(t => t.ID); } public override void Down() { DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student"); DropForeignKey("dbo.Enrollment", "CourseID", "dbo.Course"); DropIndex("dbo.Enrollment", new[] { "StudentID" }); DropIndex("dbo.Enrollment", new[] { "CourseID" }); DropTable("dbo.Student"); DropTable("dbo.Enrollment"); DropTable("dbo.Course"); } } }
迁移调用Up方法来实现数据模型所作的更改。当你输入一个命令回滚更新,迁移将调用Down方法。
这是您输入add-migration InitialCreate命令时建立的初始迁移。参数(在该示例中是InitialCreate)用于文件的名称,固然也能够是任意你想要的其余名称。一般你会选择一个单词或短语来总结迁移中所作的改变。例如您能够能会命名以后的迁移为"AddDeparmentTable"。
若是你建立了一个在数据库已经存在的状况下的迁移,则生成的的数据库建立代码不会运行。由于数据库已经和数据模型匹配。将应用程序部署到另外一个还没有建立数据库的环境时,代码才会运行以建立数据库。因此最好是提早测试一下。这就是为何以前你更改了链接字符串中数据库的名称,以便迁移能够从零开始建立一个新的数据库。
update-database
由于咱没有Windows Azure的试用帐号,因此这部分翻译就跳过了……反正也不影响学习的。
在本节中你看到了如何使用Code First迁移,在下一节中你会开始进入高级主题,扩展数据模型。
Tom Dykstra - Tom Dykstra是微软Web平台及工具团队的高级程序员,做家。