原文地址:Creating an Entity Framework Data Model for an ASP.NET MVC Application (1 of 10)
Contoso 大学 Web 示例应用演示了如何使用 EF 技术创建 ASP.NET MVC 应用。示例中的 Contoso 大学是虚构的。应用包括了类似学生注册、课程创建以及教师分配等功能。
这个系列教程展示了创建 Contoso 大学应用的步骤。你可以 下载完整 的程序,或者按照教程一步一步创建它,这个教程中使用 C# 进行演示,下载的代码中同时包含 C# 和 VB 实现。如果你有与这个教程没有直接相关的问题,可以张贴到 ASP.NET Entity Framework forum 或者 Entity Framework and LINQ to Entities forum.
这个教程假设你知道如何使用 Visual Studio 来开发 ASP.NET MVC 程序,如果不是这样,basic ASP.NET MVC Tutorial 是不错的起点。如果你以前使用 Web Form 开发,可以先看看 Getting Started with the Entity Framework 和 Continuing with the Entity Framework 教程。
在开始之前,确信下列软件已经安装在你的计算机上:
- Visual Studio 2010 SP1 或者 Visual Web Developer Express 2010 SP1( 如果你使用这两个链接,下面的项目将会被自动安装 )
- ASP.NET MVC 3 Tools Update
- Microsoft SQL Server Compact 4.0
- Microsoft Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0
Contoso 大学 Web 应用
在这个教程中你将创建的应用是一个简单的大学网站。
用户可以查看和更新学生、课程、以及教师信息。你将会创建的一些界面如下所示:
由于教程主要关注于如何使用 EF ,所以界面的风格与内置模板的风格保持一致。
EF 开发方法
如下图所示,存在三种方式来使用 EF:数据库优先,模型优先和代码优先。
数据库优先
如果你已经创建了数据库,EF 可以自动生成创建数据模型,包含关联到数据库中表和字段的类和属性。关于数据库结构的信息(存储架构)、数据模型(概念模型)、它们之间的映射被存储在扩展名为 .edmx 的 XML 文件中。Visual Studio 提供了 EF 设计器,这是一个图形化的设计器,可以用来显示和编辑 .edmx 文件。Getting Started With the Entity Framework 和 Continuing With the Entity Framework 介绍了使用数据库优先的开发。
模型优先
如果你还没有数据库,你可以在 Visual Stdio 中使用 EF 的设计器通过创建模型来开始。当模型创建之后,设计器可以生成 DDL 语句来创建相应的数据库。这个方法也使用 .edmx 文件来存储模型以及映射信息。 What‘s New in the Entity Framework 4介绍了模型优先的开发。
代码优先
不管你是否已经有数据库,你仍然可以编写自己的类和数据关联到数据表和字段,使用 EF 而不需要 .edmx 文件。所有有时候这种方法又被称为 Code Onle。当然经典的名称为 Code First。在数据库的存储架构到概念模型之间的映射通过约定以及特定的映射 API 完成。如果你还没有数据库,EF 可以自动为你创建它,在模型改变的时候,先删除掉然后重新创建,这个教程使用代码优先的方式进行开发。
使用代码优先的方式进行数据库访问的 API 基于 DbContext 类。这也同样可以用于数据库优先或者模型优先的开发流程。 更多详细的内容,可以看 When is Code First not code first?
POCO (简单的老的 CLR 对象)
默认情况下,当你使用数据库优先或者模型优先开发方法的时候,你的数据模型对象需要派生自 EntityObject 类,通过它提供 EF 功能。这意味着这些类不能是 持久性无感知(persistence ignorant ) 的,所以不能符合领域驱动开发的要求。所有的 EF 开发方法都支持使用 POCO 类,由于不需要派生自 EntityObject ,所以,这样的类是持久性无感知的。在这个教程中,我们将会使用 POCO 类。
创建 MVC 应用
在开始之前,确信下列软件已经安装在你的计算机上:
- Visual Studio 2010 SP1 或者 Visual Web Developer Express 2010 SP1( 如果你使用这两个链接,下面的项目将会被自动安装 )
- ASP.NET MVC 3 Tools Update
- Microsoft SQL Server Compact 4.0
- Microsoft Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0
打开 Visual Studio,使用 ASP.NET MVC 3 Web Application 创建名为 ContosoUniversity 的应用。
在新 ASP.NET MVC3 项目对话框中,选中 Internet 应用程序模板和 Razor 视图引擎,清除创建单元测试项目复选框,然后选择确定。
设置站点的风格
通过一些简单的修改来设置站点的菜单,布局和主页。
为了设置 Contoso 大学的菜单,在 Views\Share\_Layout.cshtml 文件,替换现存的 h1 标题和菜单链接,如下所示:
在 Views\Home\Index.cshtml 文件中,删除位于 h2 标题之下的所有内容。
在 Controllers\HomeController.cs 文件中,将 "欢迎使用 ASP.NET MVC!" 替换为 "欢迎来到 Contoso University!"
在 Content\Site.css 文件中,做如下的修改,使得菜单放置到左边。
在 #main 定义中,增加 clear: both ,如下所示:
在 nav 和 #menucontainer 定义中,增加 clear: both; float: left; ,如下所示。
运行程序,应该看到带有主菜单的主页。
创建数据模型
下一步,需要创建 Contoso 大学应用的第一组实体类,从下面的三个实体开始。
在学生 (Student) 实体和 注册 (Enrollment
) 实体之间存在一对多的关联, 在课程 (Course) 和 注册 (Enrollment) 之间也存在一对多的关联。或者说,一个学生可以注册许多课程,一个课程可以被许多学生注册。
下面的时间,你需要为每个实体创建相应的类。
注意:如果你在创建完成所有这些类的时候编译程序,将会看到编译错误。
学生实体
在 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 属性将会映射到数据库中关联到这个类的表的主键,默认情况下,EF 将属性名为 ID 或者 类名ID 的属性作为主键。
Enrollments 属性是导航属性,导航属性用来导航到这个实体关联的其他实体。Student 类的 Enrollments 属性用来持有这个学生关联的所有注册实体。或者说,如果在数据库中,一个学生行有两个关联的注册行(通过 StudentId 这个外键关联到学生表),学生实体的注册属性将会包含包含两个注册实体。
导航属性典型地被定义为虚拟的 virtual,以便通过 EF 名为延迟加载的功能来获得好处,(延迟加载将在后面进行说明),如果导航属性可以持有多个实体,(在多对多情况下,或者一对多情况下),类型必须为 ICollection。
注册实体
在 Models 文件夹中,创建 Enrollment.cs 文件,将原有的代码替换为如下代码:
using System;using System.Collections.Generic;namespace ContosoUniversity.Models { public class Enrollment { public int EnrollmentID { get; set; } public int CourseID { get; set; } public int StudentID { get; set; } public decimal? Grade { get; set; } public virtual Course Course { get; set; } public virtual Student Student { get; set; } } }
在 decimal 类型后面的 ? 表示成绩 (Grade) 是可空的。成绩是空的不同于 0,空意味着还没有成绩,0 意味着成绩为 0。
StudentID 属性是外键,关联的导航属性为 Student。一个 Enrollment 关联一个 Student,所以这个属性只能持有一个 Student 实体 (不像 Student.Enrollments 导航属性)。
CourseID 属性也是外键,关联的导航属性为 Course,一个 Enrollment 关联一个 Course 实体。
课程实体
在 Models 文件夹中,创建 Course.cs 代码文件,使用下面的代码替换原有的代码。
using System;using System.Collections.Generic;namespace ContosoUniversity.Models { public class Course { public int CourseID { get; set; } public string Title { get; set; } public int Credits { get; set; } public virtual ICollection<Enrollment> Enrollments { get; set; } } }
Enrollments 属性是导航属性,一个 Course 实体可以关联多个注册实体。
创建数据库上下文
在 EF 中,主要的协调数据访问功能的类是数据库上下文类。你需要创建派生自 System.Data.Entity.DbContext 的类,在你的代码中指定数据模型中包含的实体。你可以定制某些 EF 的行为。在这个项目中,这个类的名字为 SchoolContext。
创建一个名为 DAL 的文件夹,在这个文件夹中创建名为 SchoolContext 的类,使用下面的代码替换原有的代码。
using System;using System.Collections.Generic;using System.Data.Entity;using ContosoUniversity.Models;using System.Data.Entity.ModelConfiguration.Conventions;namespace ContosoUniversity.Models{ 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 类型的属性定义每一个实体集。在 EF 术语中,一个实体集典型地关联到一个数据库中的表,一个实体关联到表中的一行。
在 OnModelCreating 方法中的代码防止数据库中的表名被多元化。如果没有这样处理的话,生成的表将会被命名为 Students,Courses 和 Enrollments。现在,表名将会被命名为 Student,Course 和 Enrollment。开发者可以决定表名是否被多元化。这个教程使用单数模式,但是重要的是你可以这行代码来选择喜欢的模式。
(这个类位于 Models 命名空间,有些时候 EF 假定实体类和上下文类在相同的命名空间中)
设置连接字符串
你还没有创建数据库连接串,如果你不去创建它,EF 将会自动为你创建一个 SQL Server Express 的数据库。这个教程中,你将要使用 SQL Server Compact,所以,需要你来创建一个数据库连接串。
打开项目的 web.config 文件,在 connectionStrings 配置中增加一个新的数据库连接串,如下所示,(确信是项目根目录中的 web.config ,注意在 Views 中还有一个,你不需要更新它)
<add name="SchoolContext" connectionString="Data Source=|DataDirectory|School.sdf" providerName="System.Data.SqlServerCe.4.0"/>
默认情况下,EF 查找与数据库上下文类同名的数据库连接串。你现在添加的链接串将会导致在 App_Data 文件夹中创建名为 School.sdf 的 SQL Server Compact 数据库文件。
使用测试数据初始化数据库
EF 可以在应用启动的时候,自动创建 (或者先删除再重新创建)数据库。你可以指定是在每次运行程序的时候还是在模型与数据库不一致的时候进行这个工作。你还可以指定一个 EF 在创建数据库之后可以自动调用的方法,以便填充测试数据。在这里,我们指定当模型与数据库不一致的时候删除然后重新创建数据库。
在 DAL 文件夹中,创建一个名为 SchoolInitializer.cs 的类,使用下面的代码替换原有的代码,使得在创建数据库之后为新的数据库填充测试数据。
using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Data.Entity;using ContosoUniversity.Models;namespace ContosoUniversity.DAL { public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext> { protected override void Seed(SchoolContext context) { var students = new List<Student> { new Student { FirstMidName = "Carson", LastName = "Alexander", EnrollmentDate = DateTime.Parse("2005-09-01") }, new Student { FirstMidName = "Meredith", LastName = "Alonso", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Arturo", LastName = "Anand", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Gytis", LastName = "Barzdukas", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Yan", LastName = "Li", EnrollmentDate = DateTime.Parse("2002-09-01") }, new Student { FirstMidName = "Peggy", LastName = "Justice", EnrollmentDate = DateTime.Parse("2001-09-01") }, new Student { FirstMidName = "Laura", LastName = "Norman", EnrollmentDate = DateTime.Parse("2003-09-01") }, new Student { FirstMidName = "Nino", LastName = "Olivetto", EnrollmentDate = DateTime.Parse("2005-09-01") } }; students.ForEach(s => context.Students.Add(s)); context.SaveChanges(); var courses = new List<Course> { new Course { Title = "Chemistry", Credits = 3, }, new Course { Title = "Microeconomics", Credits = 3, }, new Course { Title = "Macroeconomics", Credits = 3, }, new Course { Title = "Calculus", Credits = 4, }, new Course { Title = "Trigonometry", Credits = 4, }, new Course { Title = "Composition", Credits = 3, }, new Course { Title = "Literature", Credits = 4, } }; courses.ForEach(s => context.Courses.Add(s)); context.SaveChanges(); var enrollments = new List<Enrollment> { new Enrollment { StudentID = 1, CourseID = 1, Grade = 1 }, new Enrollment { StudentID = 1, CourseID = 2, Grade = 3 }, new Enrollment { StudentID = 1, CourseID = 3, Grade = 1 }, new Enrollment { StudentID = 2, CourseID = 4, Grade = 2 }, new Enrollment { StudentID = 2, CourseID = 5, Grade = 4 }, new Enrollment { StudentID = 2, CourseID = 6, Grade = 4 }, new Enrollment { StudentID = 3, CourseID = 1 }, new Enrollment { StudentID = 4, CourseID = 1, }, new Enrollment { StudentID = 4, CourseID = 2, Grade = 4 }, new Enrollment { StudentID = 5, CourseID = 3, Grade = 3 }, new Enrollment { StudentID = 6, CourseID = 4 }, new Enrollment { StudentID = 7, CourseID = 5, Grade = 2 }, }; enrollments.ForEach(s => context.Enrollments.Add(s)); context.SaveChanges(); } } }
Seed 方法将数据库上下文对象作为参数,方法中的代码使用这个对象为数据库添加实体。对于每种实体类型,代码创建实体集,将它们添加到相应的 DbSet 属性中,然后在数据库中保存修改。并不需要在每一组实体之后都要调用 SaveChanges 方法,这里这么做,是当写入数据到数据库中出现问题的时候,可以比较方便地找到问题。
在 Global.asax.cs 中做如下的修改,使得在应用开始的时候调用初始化代码。
增加 using 语句
using System.Data.Entity;using ContosoUniversity.Models;using ContosoUniversity.DAL;
在 Application_Start 方法中,调用 EF 方法,以便运行初始化器。
Database.SetInitializer<SchoolContext>(new SchoolInitializer());
现在应用已经设置完成,当程序开始运行的时候,EF 将会比较数据库,如果不同的话,应用会删除然后重建数据库。
注意:当应用部署到生产环境的时候,必须删除这段代码。
现在,你可以创建一个页面来显示数据了,请求数据的处理将会自动触发创建数据库。你需要通过创建新的控制器开始。但在开始这些操作之前,先编译项目,使得对于 MVC 的脚手架来说,模型和上下文对象已经存在。
创建学生控制器
为了创建 Student 控制器,在解决方案管理其中的 Controllers 文件夹上右击,选择添加,然后点击控制器,在添加控制器的对话框中,做如下的选择,然后添加。
控制器的名称为:StudentController。
模板为:包含读/写操作和视图的控制器 (使用 Entity Framework)
模型类:Student (ContosoUniversity.Models) (如果没有找到,重新编译项目之后再试一下)
数据上下文类:SchoolContext (ContosoUniversity.Models)
视图:Razor(CSHTML)
打开 Controllers\StudentController.cs 文件,你会看到一个类级的变量,赋予了一个数据上下文实例。
private SchoolContext db = new SchoolContext();
名为 Index 的 Action 方法通过数据上下文对象的 Students 属性获取一个学生的列表.
public ViewResult Index(){ return View(db.Students.ToList());}
脚手架还为我们创建了一系列的 Student 视图,为了定制 Index 视图,打开 Views\Student\Index.cshtml ,使用下面的代码替换原有的内容。
@model IEnumerable<ContosoUniversity.Models.Student>@{ ViewBag.Title = "Students";}<h2> Students</h2><p> @Html.ActionLink("Create New", "Create")</p><table> <tr> <th> </th> <th> Last Name </th> <th> First Name </th> <th> Enrollment Date </th> </tr> @foreach (var item in Model) { <tr> <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> <td> @Html.DisplayFor(modelItem => item.LastName) </td> <td> @Html.DisplayFor(modelItem => item.FirstMidName) </td> <td> @Html.DisplayFor(modelItem => item.EnrollmentDate) </td> </tr>}</table>
现在,运行程序,点击 Student 选项卡,你就可以看到学生的列表。
关掉浏览器,在解决方案管理器中,选择 ContosoUniversion 项目,点击 显示所有文件,如果已经在这个模式,点击刷新,然后展开 App_Data 文件夹,查看 School.sdf 文件。
双击 School.sdf 文件,打开服务器资源管理器,展开 Tables ,查看在数据库中已经创建的表。
注意:如果在双击 School.sdf 的时候得到一个错误提示,确信你已经安装了 Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0.
对于每一种实体对应一张表,外加一个附加表 EdmMetadata,这个表用于检测数据库与模型的同步。
右击一张表,然后选择 Show Table Data,查看在初始化器重添加的数据。
在完成查看之后,注意关闭连接。(如果不关闭连接的话,下次运行程序会遇到一个错误)
实体框架使用约定来最小化开发工作,注意下面的几点:
- 多元化实体名作为数据库中的表名
- 实体属性用于表中的列名
- 实体中名为 ID 或者类名加上 ID 作为表的主键
- EF 使用数据上下文的名字作为数据库连接串的名字