【EntityFramework系列教程一,翻译】为ASP.NET MVC创建一个数据模型

原文:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application

【序】

Contoso大学示例程序为您演示了如何使用EntityFramework技术来创建一个完整的ASP.NET MVC程序,这个示例是一个虚拟大学的网站,它包含了诸如“学生注册”、“课程创建”以及“指定授课讲师”等虚拟功能。

这个教程系列解释了如何一步步地去创建一个完整的Contoso大学示例程序,你可以点击此处下载示例代码,或者遵照教程中的步骤来创建它;教程用C#讲授,示例代码给出了C#和VB.NET两个版本,如果你有任何在本教程中尚未涉及到的问题,您可以直接去以下论坛提问:ASP.NET Entity Framework forum 或者是 Entity Framework and LINQ to Entities forum(均是英文版)。

本教程假设你已经熟悉了解了ASP.NET MVC。在开始之前,请确保您的机器上已经安装了以下部件:

【Contoso大学网站应用程序示例】

1)在本系列教程中您要创建的是一个简单大学网站:

Contoso_University_home_page

2)用户可以查看、更新学生、课程和教师等信息,以下是一些程序示例的截屏:

Students_Index_page

Students_Create_page

Instructors_index_page

3)由于界面设计基本保持和自动生成的模板一致,因此此项目将重点关注如何使用EntityFramework:

3.1)走进EntityFramework:

如图所示,你有三种方式使用EntityFramework:1)数据库优先 2)模型优先 3)代码优先

Development_approaches_diagram

3.2)数据库优先:

如果你已经创建了数据库,那么EntityFramework将会自动生成包含这些数据库的类和公共属性,分别对应数据库中那些表和字段。数据库的信息(存储架构),你的数据模型(概念模型),以及之间的映射关系都被一个edmx通过xml的形式保存者。Visual Studio提供了一个EntityFramework设计器,这是一个图形化的设计器,意味着你可以用它显示或者编辑edmx文件。Getting Started With the Entity FrameworkContinuing With the Entity Framework在传统Web Forms开发中使用了“数据库优先”方法。

3.3)模型优先:

如果你尚未有数据库,你可以直接通过EntityFramework设计器来创建一个数据模型。当这个模型创建完毕,设计器将自动生成DDL(数据定义语言)以便创建一个数据库,此方法同样使用edmx文件存储模型和映射信息。What's New in the Entity Framework 4教程中包含了关于此的一个简短示例。

3.4)代码优先:

无论数据库存在与否,你都可以直接通过代码编写类以及公共属性来对应实际数据表和字段,而无需使用edmx文件。这就是为什么此方法被成为“纯代码”——尽管官方名称是“代码优先”。存储架构以及你所表现出的概念模型通过一种特殊的API方式得以转化,映射基于此得以实现。假定你没有数据库,那么EntityFramework会为您创建它;如果模型发生改变,那么它会自动删除并从新创建。本系列用的是此方式。

3.5)POCO(Plain Old CLR Objects——最原始通用语言运行时对象):

默认情况下,当你用EntityFramework(数据库优先或者是模型优先),生成的模型继承自EntityObject;这个类提供了一些基本功能,这就意味着这些类对其有一定依赖,不能完全满足“领域驱动设计”需求。EntityFramework所有开发方法都可以和POCO一起工作,从本质上来说这些类相互独立,因为他们不从EntityObject直接继承,在本系列中你将使用POCO类。

【创建一个MVC应用程序】

开始之前请确定你已经安装了所需的一切东西。

打开VS然后创建一个叫做“Contoso University”的新项目,请使用ASP.NET MVC3模板。

New_project_dialog_box

在弹出的新建对话框中,请选择“Internet Application”模板和Razor引擎,去掉默认“创建测试单元“的钩,点击“确定”按钮。

Project_template_options

【创建站点样式】

我们对网站菜单,布局以及主页略作一些细微修改。为了创建Contoso大学的网站菜单,我们用以下代码替换已经存在“Views\Shared\_Layout.cshtml”文件中“h1”的标题文字和菜单链接:

<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
</head>
<body>
<div class="page">
<div id="header">
<div id="title">
<h1>Contoso University</h1>
</div>

<div id="logindisplay">
@Html.Partial("_LogOnPartial")
</div>
<div id="menucontainer">
<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>
</div>
</div>
<div id="main">
@RenderBody()
</div>
<div id="footer">
</div>
</div>
</body>
</html>

删除“Views\Home\Index.cshtml”所有“h2”(二级标题样式)下的全部内容。
把“Controllers\HomeController.cs”中的“Welcome to ASP.NET MVC!”替换成“Welcome to Contoso University!”。

对“Content\Site.css”做如下修改:

1)在“#main”处增加“clear:both”:

#main 
{
clear
: both;
padding
: 30px 30px 15px 30px;
background-color
: #fff;
border-radius
: 4px 0 0 0;
-webkit-border-radius
: 4px 0 0 0;
-moz-border-radius
: 4px 0 0 0;
}

2)在“nav”以及“#menucontainer”处,增加“clear:both; float:left”:

nav, 
#menucontainer
{
margin-top
: 40px;
clear
: both;
float
: left;
}

现在可以运行您的程序了,将会看到带有菜单的主页面:

Contoso_University_home_page

【创建数据模型实体】

下一步您将为Contoso大学首次创建模型实体,您要创建三个这样的类:

Class_diagram

在“Student”和“Enrollment”之间会有一个“一对多”的关系,在“Course”和“Enrollment”之间也是如此——换句话说,一个学生有多门课程,一门课程也会有多个学生选修。以下您将为每一个数据模型创建一个对应的类。

注意:请不要在完成这些类的创建之前编译整个项目,因为那会导致编译错误。

【Student模型实体】

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”将会成为该数据表的主键。默认情况下,EntityFramework把“ID”或者是“类名ID”形式翻译理解为主键。
“Enrollments”是导航属性,“导航属性”包含了其它与之相关的属性。在这个示例中,“Enrollments”将包含与自身“Student”相关的全部Enrollment实体。打个比方,如果给定一个Student,它与数据表中的两个Enrollment发生关系(Enrollment数据表中StudentID作为外键包含了来自Student表的主键内容),那么Student实体所包含的Enrollments导航属性必然也包含了这两个Enrollment实体。

导航属性通常被定义成virtual,这样我们便可以利用EntityFramework的优势——慢加载(“慢加载”将在“读取相关数据”一篇中介绍)。如果一个导航属性是包含多个实体的,它必定是ICollection,无论是“多对多”还是“多对一”的情况下。

【Enrollment模型实体】

Enrollment_entity

在“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”。

此处StudentID是一个外键,对应Stduent模型实体中的导航属性。一个Enrollment一次只和一个Student发生联系,因此它只能包含一个Student。这和之前你看到的“Student.Enrollments“,可以包含多个Enrollment是不同的)。

CourseID同样也是外键,对应的导航属性是Course。一个Enrollment同样也只包含一个Course。

【Course模型实体】

Course_entity

在“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; }
}
}

【创建数据库上下文】

对于一个给定的数据模型实体,与EntityFramework功能上匹配的主要类是”数据库上下文“类。先创建一个类,然后通过继承System.Data.Entity.DbContext得以实现。在代码中您需指定包含哪些实体模型,当然你可以自定义某些EntityFramework的行为,本代码中这个类是SchoolContext。

创建一个DAL文件夹,在里边创建一个SchoolContext.cs的类,用以下代码替换生成的代码:

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,在EntityFramework的专业术语,“实体集”对应这个数据表,而一个“实体”对应表中的一条记录。
在“OnModelCreating”方法中的代码避免了数据表名称复数化,如果你不这样做,那么生成的数据表名称变成Students,Courses和Enrollments;如果你写了以上代码,那么生成的数据表名称则为Student,Course和Enrollment;对于数据表名称是否复数化程序员并不认可,本示例采用单数形式——当然,最关键的是你可以由自己的喜好决定是否加入这一句话。另外这个类是在Model命名空间下,因为EntityFramework假定实体类和数据库上下文类是处于同一个命名空间内的。

【设置连接字符串】

你不必创建一个数据库连接字符串,如不创建,那么EntityFramework将会为您自动创建一个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"/>

默认情况下,EntityFramework将会寻找和连接字符串同名的数据库上下文类进行创建。你添加的字符串表示你即将创建一个School.sdf的SQL Server Compact数据库,它位于App_Data文件夹下。

【用测试数据初始化数据库】

当你运行程序时,EntityFramework将会自动创建(或者删除后重新创建)一个数据库。为此你可以指定是否每次都重建数据库,或者是当数据模型实体与数据表不匹配的时候才这样做。你可以创建一个类包含一个方法,使得EntityFramework能够在创建数据库之后自动填充一些测试数据。本示例中你指定“当数据模型实体发生改变时,数据库将被删除重建”。

在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”中做如下改变,确保在程序运行时候数据库被初始化:

1)添加“using”引用对象:

using System.Data.Entity;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;

2)在“Application_Start”方法中,调用EntityFramework一个方法运行初始化代码:

Database.SetInitializer<SchoolContext>(new SchoolInitializer());

程序现在已经启动,当您第一次访问数据时,EntityFramework将会比较数据库和数据模型(你的SchoolContext),如有区别,立即删除并且重建数据库。

注意:当发布程序时,请删除初始化数据库的代码(笔者:Seed方法要删除)。
现在您创建了页面显示数据,这个请求将会自动触发创建数据库。你应该创建一个新的控制器(Controller)——不过在做此步骤之前,请编译整个项目以便让ASP.NET MVC架构“认识”这个定义的模型和上下文。

【创建Student控制器】

为了创建一个Student控制器,请在“解决方案”的Controller文件夹处右击,选择“添加”,然后点击“Controller”。在“Add Controller”对话框中,做如下改变之后点击“Add”:

  • 控制器名(Controller name): StudentController.
  • 模板(Template):使用EntityFramework读、写和预览的控制器(默认)。
  • 模型类(Model class): Student (ContosoUniversity.Models). (如在下拉列表中看不到,请重新编译整个程序之后再看看)
  • Data context class: SchoolContext (ContosoUniversity.Models).
  • Views: Razor (CSHTML). (默认)。

Add_Controller_dialog_box_for_Student_controller

打开“Controllers\StudentController.cs”,您将发现实例化数据库上下文的对象已经被创建:

private SchoolContext db = new SchoolContext();

“Index”选项从数据库上下文对象中的Students集合中获取全部的Student集合:

public ViewResult Index()
{
return View(db.Students.ToList());
}

自动架构同时生成了一系列的Student视图,为了自定义标题和数据列顺序,打开“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>

运行此网站程序,单击“Students”选项卡,您将看到列表形式显示的学生:

Students_Index_page

关闭浏览器,在“解决方案管理器”中选择“Contoso University”项目(确保是项目被选中,而非解决方案被选中),点击“显示所有文件”(图中红色方框左边第一个按钮)再点击“刷新”按钮(图中红色方框左边第二个按钮),展开App_Data文件夹,您将看到School.sdf文件:

 School.sdf_file_in_Solution_Explorer

双击该文件打开“服务器资源管理器”,然后展开“表”文件夹就看到表已经创建于其中:

 Server_Explorer_showing_School_tables

注意:如果你双击该文件发生错误,请确保您已经安装了 Visual Studio 2010 SP1 Tools for SQL Server Compact 4.0(安装链接请参考本页顶部“【序】”的“部件”部分;如已经安装,请关闭Visual Studio然后重新打开)。

除了每一个实体集都有一张对应的表之外,还有一个“EdmMetaData”表——该表被EntityFramework所使用,以便决定数据模型实体和物理表是否异步(不等)。

右键选择其中的某个表,选择“展示表数据”,您将看到所有被SchoolInitializer类加载的数据。

Table_data_in_Student_table

看完之后,请关闭数据连接,否则下次运行程序会发生异常。

Close_the_SQL_Compact_connection

【转换】

为了使得EntityFramework能够创建一个完整的数据库,您所编写的代码量是最小的;这是因为“转换”在起作用,或者EntityFramework已经做了默认的一些假设。一些已经被提及:

  • 实体类复数形式的名字被用于表。
  • 实体类公共属性被当成表字段。
  • 实体类带有ID或者是“类名ID”的公共属性被当成是主键。
  • Entity Framework通过寻找和你数据上下文类同名的那个名称从而确定你的连接字符串(示例中: SchoolContext)。

你已经看到这些转换可以被重写(比如你指定表不能被复数化),今后您将在“创建一个更复杂的数据模型”教程中继续学习更多关于转换的知识。

现在您已经用EntityFramework以及SQL Server Compact创建了一个简单的,可以存储并且显示数据的项目;在接下来的一章中您将学习如何执行基本的CRUD(增删改查)的操作。

关于其它EntityFramework资源您可以本系列最后一篇末尾处找到。

posted @ 2012-04-27 17:09  Serviceboy  阅读(2335)  评论(3编辑  收藏  举报