教程:在 ASP.NET MVC 应用中使用 EF 迁移并部署到 Azure
到目前为止,Contoso University 示例 Web 应用程序已在开发计算机上的 IIS Express 本地运行。 若要使实际应用程序可供其他人通过 Internet 使用,必须将其部署到 Web 托管提供商。 在本教程中,将启用 Code First 迁移并将应用程序部署到 Azure 中的云:
- 启用Code First 迁移。 借助迁移功能,可以通过更新数据库架构来更改数据模型并将更改部署到生产环境,而无需删除并重新创建数据库。
- 部署到 Azure。 此步骤是可选的;可以继续学习其余教程,而无需部署项目。
建议将持续集成过程与源代码管理配合使用进行部署,但本教程不介绍这些主题。 有关详细信息,请参阅 使用 GitHub Actions 和 Azure Pipelines自动部署云原生 .NET 微服务。
在本教程中,你将了解:
- 启用代码优先迁移
- 在 Azure 中部署应用(可选)
先决条件
启用代码优先迁移
开发新应用程序时,数据模型会频繁更改。每当模型更改时,模型都无法与数据库保持同步。 已将 Entity Framework 配置为每次更改数据模型时自动删除并重新创建数据库。 添加、删除或更改实体类或更改 DbContext
类时,下次运行应用程序时,它会自动删除现有数据库,创建一个与模型匹配的新数据库,并使用测试数据为它定种子。
这种使数据库与数据模型保持同步的方法适用于多种情况,但将应用程序部署到生产环境的情况除外。 当应用程序在生产环境中运行时,它通常存储要保留的数据,并且你不希望每次进行更改时丢失所有内容,例如添加新列。 Code First 迁移功能通过启用 Code First 来更新数据库架构,而不是删除并重新创建数据库来解决此问题。 在本教程中,你将部署应用程序,并准备启用迁移。
通过注释掉或删除添加到应用程序 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>
此外,在应用程序 Web.config 文件中,将连接字符串中的数据库名称更改为 ContosoUniversity2。
<connectionStrings> <add name="SchoolContext" connectionString="Data Source=(LocalDb)\MSSQLLocalDB;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;" providerName="System.Data.SqlClient" /> </connectionStrings>
此更改设置项目,以便第一次迁移创建新数据库。 这不是必需的,但稍后你将看到为什么这是个好主意。
从“工具”菜单中,选择“NuGet 程序包管理器”“包管理器控制台”>。
在
PM>
提示符下输入以下命令:enable-migrations add-migration InitialCreate
该
enable-migrations
命令在 ContosoUniversity 项目中创建 Migrations 文件夹,并将该文件夹中 放入可编辑以配置迁移的Configuration.cs 文件。(如果错过了上述指示更改数据库名称的步骤,迁移将找到现有数据库并自动执行
add-migration
该命令。没关系,只是意味着在部署数据库之前,不会对迁移代码运行测试。稍后运行update-database
命令时,不会发生任何操作,因为数据库已存在。打开 ContosoUniversity\Migrations\Configuration.cs 文件。 与前面看到的初始值设定项类一样,该
Configuration
类包括一个Seed
方法。internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext> { public Configuration() { AutomaticMigrationsEnabled = false; } 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 方法
删除并重新创建每个数据模型更改的数据库时,可以使用初始值设定项类 Seed
的方法插入测试数据,因为每次模型更改数据库后,都会丢失所有测试数据。 使用Code First 迁移时,测试数据会在数据库更改后保留,因此通常不需要在 Seed 方法中包含测试数据。 事实上,如果要使用迁移将数据库部署到生产环境,则不希望 Seed
该方法插入测试数据,因为 Seed
该方法将在生产环境中运行。 在这种情况下,你希望 Seed
该方法只插入到数据库中,而只插入生产中所需的数据。 例如,当应用程序在生产环境中可用时,你可能希望数据库在表中包括实际的部门名称 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; } 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”操作。
Seed
由于该方法每次执行update-database
命令时都会运行,通常在每个迁移后,你不能只插入数据,因为尝试添加的行在创建数据库的第一次迁移之后就已存在。 如果尝试插入已存在的行,则“upsert”操作会阻止发生错误,但它 会替代 测试应用程序时对数据所做的任何更改。 对于某些表中的测试数据,你可能不希望发生这种情况:在某些情况下,在测试时更改数据时,希望更改在数据库更新后保留。 在这种情况下,需要执行条件插入操作:仅当行尚不存在时插入行。 Seed 方法使用这两种方法。传递给 AddOrUpdate 方法的第一个参数指定用于检查是否存在行的属性。 对于您提供的测试学生数据,该属性可用于此目的,
LastName
因为列表中的每个姓氏都是唯一的:context.Students.AddOrUpdate(p => p.LastName, s)
此代码假定姓氏是唯一的。 如果手动添加姓氏重复的学生,下次执行迁移时,将看到以下异常:
序列包含多个元素
有关如何处理冗余数据的信息,例如两个名为“Alexander Carson”的学生,请参阅 Rick Anderson 博客上的种子设定和调试实体框架 (EF) DB 。 有关该方法
AddOrUpdate
的详细信息,请参阅 Julie Lerman 博客上的 EF 4.3 AddOrUpdate 方法 。创建
Enrollment
实体的代码假定你在集合中的实体中ID
具有students
值,尽管未在创建集合的代码中设置该属性。new Enrollment { StudentID = students.Single(s => s.LastName == "Alexander").ID, CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, Grade = Grade.A },
可以在此处使用该
ID
属性,ID
因为在调用SaveChanges
students
集合时设置了值。 EF 在将实体插入数据库时自动获取主键值,并更新ID
内存中实体的属性。将每个
Enrollment
实体添加到Enrollments
实体集的代码不使用AddOrUpdate
该方法。 它会检查实体是否已存在,如果实体不存在,则插入该实体。 此方法将使用应用程序 UI 保留对注册成绩所做的更改。 该代码循环访问列表的每个成员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
命令时,迁移会生成从头开始创建数据库的代码。 此代码也位于 “迁移” 文件夹中,该文件名为 <timestamp>_InitialCreate.cs。 类 Up
的方法 InitialCreate
创建与数据模型实体集对应的数据库表,该方法 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
在本示例中)用于文件名,可以是所需的任何内容;通常选择一个单词或短语来汇总迁移中正在执行的操作。 例如,可将后面的迁移命名为“AddDepartmentTable”。
如果创建初始迁移时已存在数据库,则会生成数据库创建代码,但此代码不必运行,因为数据库已与数据库模型相匹配。 将应用部署到其中尚不存在数据库的其他环境时,此代码将运行以创建数据库,因此最好提前进行测试。 这就是为什么你之前在连接字符串中更改了数据库的名称,以便迁移可以从头开始创建新的数据库。
在“程序包管理器控制台”窗口中,输入以下命令:
update-database
该
update-database
命令运行Up
方法以创建数据库,然后运行Seed
方法来填充数据库。 部署应用程序后,同一进程将自动在生产环境中运行,如以下部分所示。使用 服务器资源管理器 检查第一个教程中的数据库,并运行应用程序来验证所有内容是否仍与之前相同。
部署到 Azure
到目前为止,应用程序已在开发计算机上的 IIS Express 本地运行。 若要使其可供其他人通过 Internet 使用,必须将其部署到 Web 托管提供商。 在本教程的本节中,你将将其部署到 Azure。 此部分是可选的;可以跳过此教程并继续学习以下教程,也可以根据所选的其他托管提供商调整本部分中的说明。
使用 Code First 迁移部署数据库
若要部署数据库,请使用Code First 迁移。 创建用于配置从 Visual Studio 部署的设置的发布配置文件时,将选中标记为“更新数据库”的复选框。 此设置会导致部署过程在目标服务器上自动配置应用程序 Web.config 文件,以便 Code First 使用 MigrateDatabaseToLatestVersion
初始值设定项类。
在将项目复制到目标服务器期间,Visual Studio 不会对数据库执行任何操作。 运行已部署的应用程序并在部署后首次访问数据库时,Code First 会检查数据库是否与数据模型匹配。 如果不匹配,Code First 会自动创建数据库(如果尚不存在),或者将数据库架构更新到最新版本(如果数据库存在但与模型不匹配)。 如果应用程序实现 Migrations Seed
方法,则该方法在创建数据库或更新架构后运行。
迁移 Seed
方法插入测试数据。 如果要部署到生产环境,则必须更改 Seed
该方法,以便它只插入要插入到生产数据库中的数据。 例如,在当前数据模型中,你可能想要有真正的课程,但开发数据库中虚构的学生。 可以在开发中编写一种方法 Seed
来加载两者,然后在部署到生产环境之前注释掉虚构的学生。 或者,可以编写一种方法 Seed
来仅加载课程,并使用应用程序的 UI 手动在测试数据库中输入虚构的学生。
获取 Azure 帐户
需要一个 Azure 帐户。 如果你还没有订阅,但你确实拥有 Visual Studio 订阅,则可以 激活订阅权益。 否则,只需几分钟即可创建一个免费试用帐户。 有关详细信息,请参阅 Azure 免费试用。
在 Azure 中创建网站和 SQL 数据库
Azure 中的 Web 应用将在共享托管环境中运行,这意味着它在与其他 Azure 客户端共享的虚拟机(VM)上运行。 共享宿主环境是一种在云中开始工作的低成本方式。 稍后,如果你的 Web 流量增加,则应用程序可进行扩展,通过在专用 VM 上运行来满足需要。 若要详细了解Azure App 服务的定价选项,请阅读App 服务定价。
将数据库部署到 Azure SQL 数据库。 SQL 数据库是基于 SQL Server 技术构建的基于云的关系数据库服务。 使用 SQL Server 的工具和应用程序也适用于 SQL 数据库。
在 Azure 管理门户中,选择左侧选项卡中的“创建资源”,然后选择“新建”窗格(或边栏选项卡)上的所有资源以查看所有可用资源。 在“所有内容”边栏选项卡的“Web”部分选择“Web 应用 + SQL”。 最后,选择“ 创建”。
将打开用于创建新的 Web 应用 + SQL 资源的窗体。
在 “应用名称 ”框中输入一个字符串,用作应用程序的唯一 URL。 完整的 URL 将包含在此处输入的内容以及默认的 Azure App 服务s 域(.azurewebsites.net)。 如果应用名称已被采用,向导会通知你红色应用名称不可用的消息。 如果应用名称可用,则会看到绿色复选标记。
在“订阅”框中,选择要在其中驻留App 服务的 Azure 订阅。
在 “资源组 ”文本框中,选择资源组或创建新资源组。 此设置指定你的网站将在哪个数据中心运行。 有关资源组的详细信息,请参阅 资源组。
通过单击“App 服务”部分、“新建”并填写App 服务计划(可以与App 服务名称相同)、位置和定价层(有一个免费选项),创建新的App 服务计划。
单击SQL 数据库,然后选择“创建新数据库”或选择现有数据库。
在“ 名称 ”框中,输入数据库的名称。
单击“目标服务器”框,然后选择“创建新服务器”。 或者,如果以前创建了服务器,则可以从可用服务器列表中选择该服务器。
选择 “定价层 ”部分,选择“ 免费”。 如果需要其他资源,可以随时纵向扩展数据库。 若要详细了解 Azure SQL 定价,请参阅Azure SQL 数据库定价。
根据需要修改 排序 规则。
输入管理员 SQL 管理员用户名 和 SQL 管理员密码。
- 如果选择了“新建SQL 数据库服务器”,请定义稍后访问数据库时要使用的新名称和密码。
- 如果选择了之前创建的服务器,请输入该服务器的凭据。
可以使用 Application Insights 为App 服务启用遥测收集。 使用很少的配置,Application Insights 将收集有价值的事件、异常、依赖项、请求和跟踪信息。 若要了解有关 Application Insights 的详细信息,请参阅 Azure Monitor。
单击底部的“创建”,指示已完成。
管理门户返回到仪表板页面, 页面顶部的“通知 ”区域显示正在创建网站。 过了一会儿(通常不到一分钟),会通知部署成功。 在左侧导航栏中,新的App 服务显示在“App 服务s”部分,新的 SQL 数据库显示在“SQL 数据库”部分中。
将应用部署到 Azure
在 Visual Studio 中,在“解决方案资源管理器”中右键单击项目,并从上下文菜单中选择“发布”。
在“选取发布目标”页上,选择App 服务,然后选择“现有”,然后选择“发布”。
如果以前尚未在 Visual Studio 中添加 Azure 订阅,请在屏幕上执行这些步骤。 这些步骤使 Visual Studio 能够连接到 Azure 订阅,以便App 服务列表将包括网站。
在“App 服务”页上,选择已将App 服务添加到的订阅。 在“视图”下,选择“资源组”。 展开添加App 服务的资源组,然后选择App 服务。 选择“确定”以发布应用。
“输出”窗口会显示已执行的部署操作并报告部署的成功完成。
成功完成部署后,默认浏览器会自动打开并指向已部署网站的 URL。
应用现在在云中运行。
此时,已在 Azure SQL 数据库中创建了 SchoolContext 数据库,因为你选择了“执行”Code First 迁移(在应用启动时运行)。 已部署网站中的 Web.config 文件已更改,以便 MigrateDatabaseToLatestVersion 初始值设定项首次在数据库中读取或写入数据时运行(在选择“学生”选项卡时发生):
部署过程还创建了一个新的连接字符串(SchoolContext_DatabasePublish),供Code First 迁移用于更新数据库架构和种子设定数据库种子。
可以在 ContosoUniversity\obj\Release\Package\PackageTmp\Web.config 中自己的计算机上 找到 Web.config 的已部署版本。可以使用 FTP 访问已 部署的 Web.config 文件本身。 有关说明,请参阅 使用 Visual Studio ASP.NET Web 部署:部署代码更新。 按照以“若要使用 FTP 工具”开头的说明进行操作,需要以下三项操作:FTP URL、用户名和密码。
注意
Web 应用不实现安全性,因此找到 URL 的任何人都可以更改数据。 有关如何保护网站的说明,请参阅 将成员资格、OAuth 和 SQL 数据库的安全 ASP.NET MVC 应用部署到 Azure。 可以通过在 Visual Studio 中使用 Azure 管理门户或 服务器资源管理器 停止服务来阻止其他人使用站点。
高级迁移方案
如果通过运行迁移自动部署数据库,如本教程所示,并且要部署到在多个服务器上运行的网站,则可以让多个服务器同时尝试运行迁移。 迁移是原子的,因此,如果两个服务器尝试运行相同的迁移,一个服务器会成功,另一个服务器将失败(假设操作不能执行两次)。 在这种情况下,如果想要避免这些问题,可以手动调用迁移并设置自己的代码,以便仅发生一次。 有关详细信息,请参阅 Rowan Miller 博客和Migrate.exe(用于从命令行执行迁移)上的代码运行和脚本迁移。
有关其他迁移方案的信息,请参阅 迁移屏幕广播系列。
更新特定迁移
update-database -target MigrationName
该 update-database -target MigrationName
命令运行目标迁移。
忽略对数据库的迁移更改
Add-migration MigrationName -ignoreChanges
ignoreChanges
使用当前模型作为快照创建空迁移。
代码优先初始值设定项
在部署部分中,你看到了正在使用的 MigrateDatabaseToLatestVersion 初始值设定项。 Code First 还提供其他初始值设定项,包括 CreateDatabaseIfNotExists (默认值)、 DropCreateDatabaseIfModelChanges (之前使用的)和 DropCreateDatabaseAlways。 初始 DropCreateAlways
值设定项可用于设置单元测试的条件。 还可以编写自己的初始值设定项,如果不想等待应用程序从数据库读取或写入数据库,则可以显式调用初始值设定项。
有关初始值设定项的详细信息,请参阅 Book Programming Entity Framework 的第一 章和第 6 章:Code First by Julie Lerman 和 Rowan Miller。
获取代码
其他资源
可以在 ASP.NET 数据访问 - 建议的资源中找到 指向其他 Entity Framework 资源的链接。
后续步骤
在本教程中,你将了解:
- 已启用代码优先迁移
- 在 Azure 中部署应用(可选)
请继续学习下一篇文章,了解如何为 ASP.NET MVC 应用程序创建更复杂的数据模型。