MVC5+EF6--5 Code First迁移和部署html
近期学习MVC5+EF6,找到了Microsoft的原文,一个很是棒的系列,Getting Started with Entity Framework 6 Code First using MVC 5,网址:http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application。web
这个系列的原文,能够上面网址找到。我也从网上找到了相关的译文。申明,这些译文,我不是原创,而是从网上找来的,为避免下次还要网上查询,现将这些译文整理放在上面。之后找时间将原文地址附上。数据库
MVC5+EF6--5 Code First迁移和部署浏览器
原文网址:http://www.itnose.net/detail/6105449.html安全
到目前为止,应用程序一直在本地IIS Express 上运行。为了让其余人可以经过互联网访问你的应用程序,你须要将它部署到WEB服务器。服务器
本文章包含如下内容:架构
1.启用Code First迁移mvc
当你在开发应用程序时,你会对数据模型进行频繁的更改,随着每一次的更改,数据模型与数据库架构将再也不一致。你已经对Entity Framework进行了配置让其在每一次数据模型更改时自动的删除并从新建立数据库。当你添加、删除或更改实体类或更改DbContext类后从新运行应用程序,它会自动的删除已存在的数据库并建立一个和当前数据模型相匹配的数据库,最后使用测试数据初始化。app
在你将应用程序部署到生产环境以前,这种方法会一直保持数据模型和数据库架构的一致性。当应用程序运行在生产环境并已经存储了一部分数据时,你不想在数据模型变动(例如添加一个列)时丢掉这部分数据,Code First迁移功能可以经过更新数据库架构而不是删除并重建数据库来很好的解决这个问题。框架
1.禁用初始化程序,注释或删除Web.config文件中的contexts元素
<entityFramework>
<!--<contexts>
<context type="ContosoUniversity.DAL.SchoolContext, ContosoUniversity">
<databaseInitializer type="ContosoUniversity.DAL.SchoolInitializer, ContosoUniversity" />
</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>
2.一样在Web.config文件中,将数据库链接字符串中数据库的名字修改成ContosoUniversity2
<connectionStrings>
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
</connectionStrings>
上面的更改可让应用程序在作第一次迁移时建立一个新的数据库,但这不是必须的。
3.依次打开Tools -> Library Package Manager -> Package Manager Console
4.在Package Manager Console中输入如下命令
enable-migrations
add-migration InitialCreate
enable-migrations命令会新建一个Migrations 文件夹,同时文件夹中包含一个Configuration.cs文件,你能够经过编辑该文件来配置迁移的相关设置。
若是你在上面的步骤中忘记更改数据库名称,迁移程序会找到现有的数据库并自动执行add-migration命令,不过这没什么关系,它只是意味着你在部署数据库以前不会对迁移代码进行测试,接下来当你执行update-database命令时,数据库架构不会发生任何变化由于数据库已经存在。
就像以前的初始化程序同样,Configuration类中包含有一个Seed方法
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方法来插入测试数据,而应该在你须要时再插入相应的测试数据。例如,当你将应用部署到生产环境时,你但愿数据库Deparment表中应该包含department的相关数据。
在本文中,你会使用迁移功能来部署应用程序,可是为了可以更容易的观察应用程序是如何无需人工操做而自动插入数据的,咱们会一直使用Seed方法来插入测试数据。
打开Configuration.cs,使用下面的代码替换;
namespace ContosoUniversity.Migrations
{
using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
ContextKey = "ContosoUniversity.DAL.SchoolContext"
}
protected override void Seed(ContosoUniversity.DAL.SchoolContext context)
{
var students = new List<Student>
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-08-11") }
};
students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();
var courses = new List<Course>
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3, },
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3, },
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3, },
new Course {CourseID = 1045, Title = "Calculus", Credits = 4, },
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4, },
new Course {CourseID = 2021, Title = "Composition", Credits = 3, },
new Course {CourseID = 2042, Title = "Literature", Credits = 4, }
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.Title, s));
context.SaveChanges();
var enrollments = new List<Enrollment>
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
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);
}
}
context.SaveChanges();
}
}
}
Seed方法将数据库上下文对象做为输入参数,并使用该对象将实体添加到数据库,对于每个实体类型,上面的代码建立了一个新的实体集合并将它们添加到适当的DbSet属性,最后将更改保存到数据库。在每一组实体以后调用SaveChanges方法并非必需的,这里之因此这样作,是由于在将数据写入到数据库时若是发生异常,可让你更容易的定位问题所在。
插入数据时使用AddOrUpdate方法来执行"upsert"操做,是由于在每次执行update-database命令时Seed方法老是会被调用,一般在每次迁移以后,你不只仅是插入了数据,由于你试图添加的行可能在建立数据库后的第一次迁移中已经存在。"upsert"操做能够在你试图添加一个已存在的行时防止错误的发生,可是它会在你测试应用程序时重置你对数据所作的更改。你也许不但愿发生下面的状况:在某些状况下你可能但愿保留你在测试阶段对测试数据所作的更改,在另外一种状况下,你但愿作一个条件插入操做:当且仅当行不存在时才执行插入操做。Seed方法同时使用以上两种方法。
AddOrUpdate方法的第一个参数指定了一个属性来检查行是否已存在。对于提供的测试数据,LastName属性被用做检查列的惟一性
context.Students.AddOrUpdate(p => p.LastName, s)
上面的代码假定LastName具备惟一约束,若是你添加了一个与已有学生的LastName相同的学生,那么在执行迁移操做时会发生以下异常
Sequence contains more than one element
上面的代码在建立Enrollment实体时假定Students集合中的实体已经拥有ID值,即便在建立这些集合时没有为它们设置该值。
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
这里可使用ID属性,由于当你为学生集合调用SaveChanges方法时ID值会被设置。EF在将实体保存至数据库时会自动获取该实体的主键值并将该值更新至内存中实体的ID属性。
将每一个Enrollment实体添加到Enrollments实体集合的代码并无使用AddOrUpdate方法,它会检查实体是否已存在,若是不存在,则插入该实体,这种方法将保存在应用程序界面对 enrollment grade所作的更改。该代码遍历Enrollment列表中的每一个成员,若是在数据库中没有该Enrollment,就将该Enrollment添加至数据库。当你第一次更新数据库时,该数据库是空的,因此每一个enrollment实体都将被添加至数据库中。
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s => s.Student.ID == e.Student.ID &&
s.Course.CourseID == e.Course.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
接下来请编译项目
当执行add-migration命令时,迁移将生成用来建立数据库的代码,该代码位于Migrations文件夹中的名字为<timestamp>_InitialCreate.cs的文件中。InitialCreate类中的Up方法会根据实体集合的数据模型来建立数据库表,Down方法用来删除它们。
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"。
若是你建立了一个初始化迁移,可是数据库却已经存在,那么生成的用来建立数据库的代码是不会运行的,由于数据库架构和数据模型是一致的。当你将应用程序部署到另外一个数据库尚不存在的环境中时,该代码将会运行以建立数据库,但最好是提早测试一下。这就是为何要修改链接字符串中数据库的名称的缘由所在,这样就能够建立一个全新的数据库。
在Package Manager Console窗口输入如下命令
update-database
************************************************************************************************************************
******************************如下内容仅供了解,大陆地区暂不能申请测试帐号**********************************
************************************************************************************************************************
2.部署到Windows Azure
到目前为止应用程序一直运行在本地IIS Express,为了让其余人可以经过互联网访问你的应用程序,你须要将它部署到WEB服务器。接下来将教你如何将应用程序部署到Windows Azure。
当你使用Visual Studio的配置设定来建立发布配置文件时,你须要勾选名为Execute Code First Migrations的复选框。此设置能够在部署过程当中在目标服务器上自动配置应用程序的Web.config文件,以便Code First使用MigrateDatabaseToLatestVersion初始化类。
当部署程序将项目拷贝到目标服务器时,Visual Studio不须要作任何事情。当你运行已经部署的项目,且该项目第一次访问数据库时,Code First会检查数据库和数据模型是否一致。若是它们不一致,Code First会自动建立数据库(若是数据库不存在)或者更新数据库架构到最新版本(数据库存在可是模型不一致)。若是应用程序实现了迁移的Seed方法,那么该方法会在数据被建立或者架构被更新后执行。
Seed方法的做用是插入测试数据。若是你在将项目部署到生成环境,你必须修改Seed方法以便该方法仅在你想将数据插入到生产数据库时插入数据。举个例子来讲,在当前的数据模型中,你可能但愿在开发环境数据库中拥有真实的课程信息和虚构的学生信息,你能够在Seed方法中将这些信息添加至开发环境数据库,并在将项目部署到生产环境时注释掉添加虚构学生信息的那些代码。你也能够仅在Seed方法中添加课程信息,而后在应用程序界面中添加虚构的学生信息到测试数据库。
你须要一个Windows Azure帐号,若是你尚未可是却拥有一个MSDN订阅,你能够经过激活MSDN订阅来得到该帐号。
Windows Azure网站运行在共享主机中,这意味着该网站运行在虚拟机中。共享主机环境是一种低成本的云端的环境,若是之后你的网站流量增长时,你能够将该网站运行在专用虚拟机上来知足须要。
你须要将数据库部署到Windows Azure SQL数据库中,SQL数据库是使用SQL Server技术创建的基于云的关系型数据库服务。运行在SQL Server中的工具盒应用一样也能够运行在SQL数据库中。
1.在Windows Azure Management Portal中,点击左侧的Web Sites选项卡,而后点击New
2.点击CUSTOM CREATE
打开New Web Site - Custom Create向导
3.在向导的New Web Site步骤,在URL文本框中输入做为应用程序URL的字符串,完整的URL应该包括你刚才输入的加上文本框右侧显示的字符串。下图中使用了"ConU"做为URL,但该URL可能已被别人使用,因此你必须填写一个未被使用过的名称做为URL。
4.在Region 下拉列表框中选择一个离你最近的选项,该设置会指定你的应用程序运行在哪一个数据中心。
5.在Database下拉列表框中选择Create a free 20 MB SQL database选项。
6.在 DB CONNECTION STRING NAME中输入 SchoolContext
7.点击右下角的箭头图标,进入Database Settings步骤
8.在Name文本框中输入ContosoUniversityDB
9.在Server下拉框中选择New SQL Database server。另外,若是你以前建立了一个服务器,你能够从下拉框中选择你所建立的那个服务器。
10.输入管理员 LOGIN NAME 和PASSWORD,若是你选择的是New SQL Database server,这里输入的用户名和密码是事后访问数据库时的用户名和密码,若是你选择的是以前建立的服务器,你须要输入相应认证信息。
11.选择Region,要和以前选择的一致
12.点击右下角的检查图标,这表示你已经完成配置过程
完成以后会从Management Portal跳转至Web Sites页面,Status列会显示网站正在被建立,过一会以后(一般少于一分钟),Status列会显示网站已经被成功建立。
1.打开Visual Studio,在Solution Explorer中右键点击项目,选择Publish
2.在 Publish Web向导的Profile选项卡中点击 Import
3.若是以前你没有在Visual Studio中添加Windows Azure订阅,请执行接下来的操做,这些操做可让Visual Studio链接至Windows Azure订阅,以便Import from a Windows Azure web site下的下拉列表框中包含你所建立的网站。
做为另外一种替代方法,你能够直接登陆至 Windows Azure而不须要下载一个订阅文件,若是使用此方法,请在接下来的步骤中点击 Sign In 而不是Manage subscriptions。这种方法比较简单,可是本教程写于2013年9月,当时必须使用下载的订阅文件才能将Server Explorer链接至 Windows Azure SQL Database。
a.在 Import Publish Profile 对话框,点击 Manage subscriptions
b.在Manage Windows Azure Subscriptions对话框,点击Certificates 选项卡,而后点击Import.
c.在Import Windows Azure Subscriptions对话框,点击Download subscription file
d.保存.publishsettings文件
安全注意事项:publishsettings文件中包含有你的凭证(未编码)以用来管理Windows Azure订阅和服务。对此文件来讲最安全的作法就是先将其暂时存放在在某一个文件夹中,一旦导入完成就将其删除。若是被恶意用户访问到此文件,他就能够修改、新建或删除你的Windows Azure服务。
e.在 Import Windows Azure Subscriptions对话框,点击Browse并找到 .publishsettings文件
f.点击Import
4.关闭 Manage Windows Azure Subscriptions对话框
5.在Import Publish Profile对话框,选择 Import from a Windows Azure web site,从下拉列表框中选择你所建立的网站,点击OK
6. 在Connection选项卡,点击Validate Connection 来确保设置是正确的
7.当验证经过,Validate Connection旁边会出现一个绿色的对号标记,点击Next
8.打开SchoolContext 下面的Remote connection string下拉框,选择链接字符串
9.选择Execute Code First Migrations (runs on application start)
该设置会使部署程序在目的服务器上自动的配置应用程序的Web.config文件,以便Code First可使用 MigrateDatabaseToLatestVersion初始化类
10.点击Next
11.在Preview 选项卡,点击Start Preview
该选项卡会显示将要被拷贝到目的服务器中的文件列表,当你再次部署该应用程序时,此时将会只显示那些被更改过的文件列表
12.点击Publish,Visual Studio会将文件拷贝至Windows Azure服务器
13.Output窗口会显示捕获到的部署动做和已成功完成的部署
14.部署成功以后,默认浏览器会自动打开该网站,该网站已成功的运行在云端,点击Students选项卡
此时SchoolContext 数据库已经在Windows Azure SQL Database中被成功建立,由于你选择了Execute Code First Migrations (runs on app start)选项。网站的Web.config文件已经被修改以便 MigrateDatabaseToLatestVersion初始化程序在第一次运行时读写数据库(当你点击Students选项卡时)。
在部署过程当中一样也为Code First迁移建立了一个查询字符串(SchoolContext_DatabasePublish)来更新数据库架构并插入数据。
你能够在你本机的ContosoUniversity\obj\Release\Package\PackageTmp\Web.config中找到Web.config文件部署的版本。
注意:部署的web应用程序并不执行安全检查,因此任何知道此URL的人均可以修改数据库中的数据。你能够经过使用Windows Azure Management Portal 或者 Visual Studio中的 Server Explorer中止运行该网站以防止别人修改你的数据。
高级迁移方案
若是你经过运行迁移程序部署了一个数据库,那么接下来你将部署一个能运行在多个服务器上的网站,要作到这一点,你须要在多台服务器上同时运行迁移程序。迁移过程是原子的,若是在两台服务器上运行同一个迁移程序,则其中一个会成功,另外一个会失败(假设操做不能被执行两次)。在这种状况下,若是你但愿避免出现这个问题,你能够手动的调用迁移程序并修改你的代码以确保迁移只发生一次。
Code First初始化程序
在部署程序那一节,能够看到使用的是MigrateDatabaseToLatestVersion初始化程序,Code First同时也提供了其余的初始化程序,包括CreateDatabaseIfNotExists(默认)、DropCreateDatabaseIfModelChanges(以前使用的)和DropCreateDatabaseAlways。DropCreateAlways初始化程序对于为单元测试设置条件是很是有用的。你也能够编写你本身的初始化程序,若是你不想一直等直到应用程序可以读写数据库,你能够显示的调用初始化程序。
在部署部分中,您看到了正在使用的MigrateDatabaseToLatestVersion初始化。代码首先还提供了其它的初始化,包括(你以前使用)CreateDatabaseIfNotExists(默认),DropCreateDatabaseIfModelChanges和DropCreateDatabaseAlways。该DropCreateAlways初始化能够设立条件的单元测试很是有用。您也能够编写本身的初始化,您能够显式地调用一个初始化,若是你不想一直等到应用程序的读取或写入到数据库中。在本教程中被写入11月份2013的时候,你只能使用建立和DropCreate初始化启用迁移以前。实体框架团队正在努力使这些初始化实用与迁移为好。