近期学习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。
这个系列的原文,可以上面网址找到。我也从网上找到了相关的译文。申明,这些译文,我不是原创,而是从网上找来的,为避免下次还要网上查询,现将这些译文整理放在上面。以后找时间将原文地址附上。
MVC5+EF6--1 创建Entity Framework数据模型
Contoso University示例网站演示如何使用Entity Framework 5创建ASP.NET MVC 4应用程序。
Entity Framework有三种处理数据的方式: Database First, Model First, and Code First. 本指南使用代码优先。其它方式请查询资料。
示例程序是为Contoso University建立一个网站。功能包括:学生管理、课程创建、教师分配。 本系列指南逐步讲述如何实现这一网站程序。
本示例程序基于 ASP.NET MVC.如果使用 ASP.NET Web Forms model, 请查看 Model Binding and Web Forms系列指南和 ASP.NET Data Access Content Map.
如有问题,可在这些讨论区提问: ASP.NET Entity Framework forum, the Entity Framework and LINQ to Entities forum, or StackOverflow.com.
(此指南的旧版本请查看 the EF 4.1 / MVC 3 e-book.)
Contoso University 应用程序
本指南将创建一个简单的大学网站.
用户可查看或更新学生、课程、教师的信息,以下是相关截图:
UI风格延续了默认模板的风格,以便更多关注于如何使用Entity Framework。
需求
使用 Visual Studio 2012 or Visual Studio 2012 Express for Web, 可从以下链接获取相关需求软件:
Windows Azure SDK for Visual Studio 2012
如果已经安装 Visual Studio,此链接将只安装缺少的组件.如果没有Visual Studio, 将安装Visual Studio 2012 Express for Web. 你也可使用Visual Studio 2013,但一些步骤和截图有所不同.
若使用 Visual Studio 2010,需要安装MVC 4 和 SQL Server LocalDB.
创建MVC Web程序
创建程序如下:
选择 Internet Application template.
选择 Razor
点击OK.
设置网站风格
菜单、布局有少许变动.
打开Views\Shared\_Layout.cshtml, 修改如下:黄色为修改后内容.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title - Contoso University</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<header>
<div class="content-wrapper">
<div class="float-left">
<p class="site-title">@Html.ActionLink("Contoso University", "Index", "Home")</p>
</div>
<div class="float-right">
<section id="login">
@Html.Partial("_LoginPartial")
</section>
<nav>
<ul id="menu">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Students", "Index", "Student")</li>
<li>@Html.ActionLink("Courses", "Index", "Course")</li>
<li>@Html.ActionLink("Instructors", "Index", "Instructor")</li>
<li>@Html.ActionLink("Departments", "Index", "Department")</li>
</ul>
</nav>
</div>
</div>
</header>
<div id="body">
@RenderSection("featured", required: false)
<section class="content-wrapper main-content clear-fix">
@RenderBody()
</section>
</div>
<footer>
<div class="content-wrapper">
<div class="float-left">
<p>© @DateTime.Now.Year - Contoso University</p>
</div>
</div>
</footer>
@Scripts.Render("~/bundles/jquery")
@RenderSection("scripts", required: false)
</body>
</html>
上面做了两点改变:
- 把 "My ASP.NET MVC Application" 和"your logo here" 替换为"Contoso University".
- 添加一些后面用到的超链接
在Views\Home\Index.cshtml, 替换为如下代码:
@{
ViewBag.Title = "Home Page";
}
@section featured {
<section class="featured">
<div class="content-wrapper">
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
<h2>@ViewBag.Message</h2>
</hgroup>
</div>
</section>
}
在 Controllers\HomeController.cs, 把 ViewBag.Message
值替换为 "Welcome to Contoso University!":
public ActionResult Index()
{
ViewBag.Message = "Welcome to Contoso University";
return View();
}
CTRL+F5 运行,界面如下.
创建数据模型
先创建如下三个数据模型:
Student
and Enrollment
实体是一对多关系,, Course
and Enrollment
实体也是一对多关系. 也就是说一个学生可以注册多门课程,一门课程允许多位学生注册。
为每个实体创建对应的类:
注意:在完成所有实体之前尝试编译,将导致编译失败.
Student Entity
在Models文件夹创建Student.cs ,代码如下:
using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
public class Student
{
public int StudentID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
StudentID
属性将成为数据表的主键列。默认Entity Framework将名为ID或者类名ID的属性设为主键。
Enrollments
是一个导航属性。导航属性记录和本实体相关的其它实体。在本例中,Enrollments
属性记录和 Student
属性相关的Enrollment。.如果数据库中某Student
记录和
两条Enrollment
记录
相关。(这两条记录的 StudentID
外键值等于该Student的StudentID
)
,那么Student
实体的 Enrollments
导航属性将包含这两个 Enrollment
实体.
Navigation properties 常定义为virtual
以便发挥Entity Framework的功能,如 lazy loading. (在 Reading Related Data 部分将详细讲述延迟加载).
如果navigation property 包含多记录 (如 many-to-many or one-to-many 关系), 类型最好是列表类型,如 ICollection
.
The Enrollment Entity
在Models文件夹, 创建 Enrollment.cs,代码如下:
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
Grade
类型
后面的问号表示Grade
属性是 nullable.
StudentID
property 是外键, 相应的导航属性是 Student
.一个 Enrollment
实体和一个 Student
实体相关,
CourseID
property是外键, 相应的导航属性是 Course
.
The Course Entity
在Models文件夹, 创建Course.cs, 代码如下:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Enrollments
属性是导航属性. 一个 Course
实体对应多个 Enrollment
实体.
下一节再对 [
DatabaseGenerated(
DatabaseGeneratedOption.None)]
特性进行讲解。 简而言之,此特性表明主键由你赋值而非数据库自动生成。
创建Database Context
将 Entity Framework 功能和给定数据模型相关联的类是 database context class. 此类继承自 System.Data.Entity.DbContext class.代码表明数据模型包含了哪些实体类型.本项目中数据上下文类名为 SchoolContext
.
创建 DAL文件夹 (for Data Access Layer). 在此文件夹创建SchoolContext.cs,代码如下:
using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ContosoUniversity.DAL
{
public class SchoolContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
代码为每一个实体集合创建 DbSet 属性。. 在Entity Framework,实体集合对应数据表,一个实体对应表中的一条记录。.
modelBuilder.Conventions.Remove
语句阻止表名使用实体的复数形式,如果没有此语句,则生成的数据表分别是 Students
, Courses
, andEnrollments
. 使用此语句,表名将和实体名一样 Student
, Course
, and Enrollment
. 这和编程风格相关,至于是否使用复数取决于你自己。
SQL Server Express LocalDB
LocalDB 是一个轻量级的SQL Server。这里不做翻译介绍。
打开根目录下的 Web.config 文件,在 connectionStrings
处添加连接字符串如下,(注意,如果没有localdb,而使用SQL Server或者Express版本,请修改连接字符串)
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\ContosoUniversity.mdf" providerName="System.Data.SqlClient" />
默认Entity Framework寻找和数据上下文类同名的连接字符串 (SchoolContext
for this project). 更多连接字符串信息,请查看 SQL Server Connection Strings for ASP.NET Web Applications.
也可不添加连接字符串,由程序自动生成。但会导致数据库文件没有放在程序的 App_data文件夹下,更多信息请查看 Code First to a New Database.
connectionStrings
集合默认包含一个名为 DefaultConnection
的连接字符串,是用来连接
membership database. 这里不会用到。两条连接字符串唯一不同之处是数据库名字不同
设置并执行 Code First Migration
在程序初期,数据模型经常发生变动,每次变动就会导致和数据库不一致。可将Entity Framework配置为变动后自动重建数据库。但在程序使用之后如果发生变动,更希望是更新数据库而非重建(重建导致数据丢失)。 Migrations 功能使得代码优先方式下更新数据库。如果希望重建可使用DropCreateDatabaseIfModelChanges实现每次变动后重建数据库。. 本例中我们直接使用Migration方法,更多信息请查看 Code First Migrations.
启用Code First Migrations
- 工具菜单,选择Library Package Manager ,Package Manager Console.
PM>
提示符下输入如下命令:
enable-migrations -contexttypename SchoolContext
命令将创建 Migrations文件夹,并在文件夹下创建Configuration.cs.
Configuration
类包含 Seed
方法,数据库创建或更新后将调用此方法。
internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.Models.SchoolContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(ContosoUniversity.Models.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
方法使得可设置自动插入到数据库中的数据
设置Seed方法
为了便于测试,我们在Seed中添加一些数据
- 替换 Configuration.cs内容如下:
2. namespace ContosoUniversity.Migrations
3. {
4. using System;
5. using System.Collections.Generic;
6. using System.Data.Entity.Migrations;
7. using System.Linq;
8. using ContosoUniversity.Models;
9.
10. internal sealed class Configuration : DbMigrationsConfiguration<ContosoUniversity.DAL.SchoolContext>
11. {
12. public Configuration()
13. {
14. AutomaticMigrationsEnabled = false;
15. }
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").StudentID,
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").StudentID,
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").StudentID,
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").StudentID,
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").StudentID,
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").StudentID,
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").StudentID,
88. CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
89. },
90. new Enrollment {
91. StudentID = students.Single(s => s.LastName == "Anand").StudentID,
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").StudentID,
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").StudentID,
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").StudentID,
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.StudentID == 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. }
}
由于此方法在创建或更新后调用,为了避免多次插入同一数据,调用AddOrUpdate方法,第一个参数用来检查数据是否已经存在。
context.Students.AddOrUpdate(p => p.LastName, s)
关于更多AddOrUpdate信息,请查看 Take care with EF 4.3 AddOrUpdate Method .
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s => s.Student.StudentID == e.Student.StudentID &&
s.Course.CourseID == e.Course.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
关于Seed中问题的调试,请查看 Seeding and Debugging Entity Framework (EF) DBs .
- 编译.
- 在 the Package Manager Console 执行命令:
创建并执行 First Migration
2. add-migration InitialCreate
update-database
add-migration
命令将添加 [DateStamp]_InitialCreate.cs 文件到Migrations文件夹,文件中包含数据库创建初始化信息。第一个参数 (InitialCreate)
作为文件名,前面会加上时间戳.
InitialCreate
文件代码如下:
namespace ContosoUniversity.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class InitialCreate : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.Student",
c => new
{
StudentID = c.Int(nullable: false, identity: true),
LastName = c.String(),
FirstMidName = c.String(),
EnrollmentDate = c.DateTime(nullable: false),
})
.PrimaryKey(t => t.StudentID);
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.Course",
c => new
{
CourseID = c.Int(nullable: false),
Title = c.String(),
Credits = c.Int(nullable: false),
})
.PrimaryKey(t => t.CourseID);
}
public override void Down()
{
DropIndex("dbo.Enrollment", new[] { "StudentID" });
DropIndex("dbo.Enrollment", new[] { "CourseID" });
DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
DropForeignKey("dbo.Enrollment", "CourseID", "dbo.Course");
DropTable("dbo.Course");
DropTable("dbo.Enrollment");
DropTable("dbo.Student");
}
}
}
The update-database
运行文件中的 Up
方法创建数据库,然后调用 Seed
方法.
名为 ContosoUniversity的数据库将被创建, .mdf文件被存放在 App_Data 文件夹,如你在连接字符串指定的一致.
以下步骤是在VS中查看数据库的操作,按图所示操作即可,不再翻译。
- From the View menu, click Server Explorer.
- Click the Add Connection icon.
- If you are prompted with the Choose Data Source dialog, click Microsoft SQL Server, and then clickContinue.
- In the Add Connection dialog box, enter (localdb)\v11.0 for the Server Name. Under Select or enter a database name, select ContosoUniversity.
- Click OK.
- Expand SchoolContext and then expand Tables.
- Right-click the Student table and click Show Table Data to see the columns that were created and the rows that were inserted into the table.
创建Student Controller and Views
- 右击Controllers文件夹,选择创建Controller,相关参数信息如下图所示:
- Visual Studio 打开 Controllers\StudentController.cs file. 数据库上下文对象已经创建
private SchoolContext db = new SchoolContext();
Index
action method 从数据库上下文获取 Students
属性,返回学生列表:
public ViewResult Index()
{
return View(db.Students.ToList());
}
The Student\Index.cshtml 视图显示了列表中的信息:
<table>
<tr>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.FirstMidName)
</th>
<th>
@Html.DisplayNameFor(model => model.EnrollmentDate)
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.FirstMidName)
</td>
<td>
@Html.DisplayFor(modelItem => item.EnrollmentDate)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.StudentID }) |
@Html.ActionLink("Details", "Details", new { id=item.StudentID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.StudentID })
</td>
</tr>
}
- Press CTRL+F5 运行。
点击Students 查看。
惯例
EF的这些惯例,使得以上所写代码不多:
- 实体类名的复数形式作为表名.
- 实体类的属性名作为表的列名.
ID
或 classnameID
作为主键.
惯例可以不必遵守(如本文不用复数形式作为表名),如果使用惯例或者覆盖惯例,请查看后面的 Creating a More Complex Data Model 。更多信息请查看. Code First Conventions.
总结
使用 Entity Framework 和SQL Server Express 创建了一个简单的web程序。随后将学习如何完成基本的 CRUD (create, read, update, delete) 操作.