首页 > 代码库 > Entity Framework学习之创建Entity Framework数据模型

Entity Framework学习之创建Entity Framework数据模型

1.准备

  • Visual Studio 2013
  • .NET 4.5
  • Entity Framework 6 (EntityFramework 6.1.0 NuGet package)
  • Windows Azure SDK 2.2 (可选)

2.Contoso University web应用程序

此应用程序允许用户 查看、更新student、 course和instructor 信息:


网站UI样式是由内置模板生成的

3.新建MVC5 web应用程序

新建项目并命名为“ContosoUniversity”

选择MVC模板

更改身份认证方式为“No Authentication”


4.设置网站样式

打开Views\Shared\_Layout.cshtml并作以下更改,将出现的每一个"My ASP.NET Application" 和"Application name" 改为 "Contoso University",添加 Students, Courses, Instructor,和Departments菜单项,代码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - Contoso University</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Contoso University", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <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>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - Contoso University</p>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

打开Views\Home\Index.cshtml,使用下面的代码替换掉原来的内容:

@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <h1>Contoso University</h1>
</div>
<div class="row">
    <div class="col-md-4">
        <h2>Welcome to Contoso University</h2>
        <p>Contoso University is a sample application that
        demonstrates how to use Entity Framework 6 in an 
        ASP.NET MVC 5 web application.</p>
    </div>
    <div class="col-md-4">
        <h2>Build it from scratch</h2>
        <p>You can build the application by following the steps in the tutorial series on the ASP.NET site.</p>
        <p><a class="btn btn-default" href=http://www.mamicode.com/"http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/">See the tutorial »

>
按CTRL+F5 运行程序:



改变浏览器窗口大小,可以发现内容可以自适应窗口



5.安装Entity Framework 6

依次打开“Tools”->“Library Package Manager”->"Package Manager Console",在”Package Manager Console“中输入Install-Package EntityFramework命令后回车



使用手动添加Entity Framework的好处是可以明白接下来如何使用Entity Framework。
也可以使用另一种比较简单的方法添加Entity Framework:项目点右键选择“Manage NuGet Packages”,搜索“EntityFramework”:

6.创建数据模型

现在要创建下面三个实体Student和Enrollment之间是一对多的关系,Course和Enrollment也是一对多的关系。

注意:如果在创建完这三个实体类后尝试编译项目,会出现编译错误.


Student实体


在Models目录下新建Student.cs类,添加代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ContosoUniversity.Models
{
    public class Student
    {
            public int ID { get; set; }
            public string LastName { get; set; }
            public string FirstMidName { get; set; }
            public DateTime EnrollmentDate { get; set; }
            public virtual ICollection<Enrollment> Enrollments { get; set; }
        
    }
}

其中属性ID相当于数据库表中的primary key。默认情况下,Entity Framework将ID或者classnameID属性作为primary key。

属性Enrollments是一个导航属性(navigation property),导航属性可以将其他实体和此实体关联起来。本例中,Student实体中的Enrollments属性包含所有与Student实体相关联的Enrollments实体。换句话说,如果数据库中给定一条Student记录,此记录有两条相关联的Enrollment 记录,那么Student实体中的Enrollments导航属性就包含这两个两个Enrollment实体。

导航属性一般被定义为virtual类型,这样可以方便使用Entity Framework框架的一些特性,比如延迟加载( lazy loading)。

如果导航属性可包含多个实体(如多对多或者一对多的关系),那么导航属性的类型必须是一个可进行添加、删除、更新操作的列表类型比如ICollection。


Enrollment实体


在Models目录下新建Enrollment.cs类,添加代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace ContosoUniversity.Models
{
    public class Enrollment
    {
        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; }
        }
    }
}

其中属性EnrollmentID 是primary key,Enrollment实体使用了classnameID模式而不是直接使用ID。通常情况下你应该选择其中的一种模式来使用,并在项目中统一使用这种模式来命名。这里同时使用了者两种模式只是做演示用,但是在数据模型中直接使用ID可以更容易的实现继承机制。

属性Grade是枚举类型,问号表示Grade可以是nullable。

属性StudentID是一个外键( foreign key),相对应的导航属性是Student实体,一个Enrollment实体只关联一个Student实体,而不像Student.Enrollments导航属性可以关联多个Enrollment实体。

同样CourseID也是外键,相关的导航属性是Course,同样一个Enrollment实体只关联一个Course实体。

如果属性被命名为<navigation property name><primary key property name>,Entity Framework会自动将其定义为外键。


Course 实体


在Models目录下新建Course.cs类,添加代码:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Web;

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是一对多的关系。

7.创建数据库上下文(database context)

在一个数据模型中负责调用Entity Framework功能类是数据库上下文类,可以通过派生System.Data.Entity.DbContext来创建这个类。你可以指定数据模型中要包含有哪些实体,你也可以自定义某些Entity Framework的行为,在此项目中数据库上下文为“SchoolContext”。
在项目中新建文件夹并命名为DAL (for Data Access Layer),在此文件件中新建类并命名为SchoolContext.cs,添加以下代码:

using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Web;

namespace ContosoUniversity.DAL
{
    public class SchoolContext:DbContext
    {
        public SchoolContext(): base("SchoolContext")
        {
        }

        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中每一个实体集对应于一个数据库表,每一个实体对应于表中的一行。你可以省略DbSet<Enrollment> 和DbSet<Course> ,此时项目仍能正常运行。因为Entity Framework会自动将他们包含进来,因为Student实体引用了Enrollment实体,并且Enrollment实体引用了Course实体。

指定连接字符串

这里通过在web.config中添加连接字符串并将其传递给构造函数,你也可以直接将连接字符串本身作为参数传递给构造函数。如果你没有指定连接字符串的话,Entity Framework就会将类名作为连接字符串的名字。

public SchoolContext() : base("SchoolContext")
{
}

指定表名

在OnModelCreating方法中modelBuilder.Conventions.Remove语句可以防止生成的表名为复数。如果不这样做的话,数据库中生成的表名将为Students, Courses和Enrollments,这里对于表名使用复数或单数没有明确的要求,对于开发者来说可以根据自己的喜好自行选择。

8.通过Entity Framework使用测试数据初始化数据库

当应用程序运行时Entity Framework会自动创建数据库,如果数据库已经存在,Entity Framework会将其删除后重新建立。当然你也可以指定是在应用程序每次运行时还是数据模型和数据库结构不一致时执行创建数据库命令。Entity Framework会自动调用Seed方法并使用测试数据初始化数据库。

默认情况下只有数据库不存在时才由Entity Framework创建,如果数据库已经存在或者数据模型发生变化时,Entity Framework会抛出异常。在生产环境中是不建议这样做的,因为这样会丢掉数据库所有的数据。

在DAL文件夹,新建一个名为SchoolInitializer.cs的类,并添加代码:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;

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{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.Add(s));
            context.SaveChanges();
            var enrollments = new List<Enrollment>
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050,},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            enrollments.ForEach(s => context.Enrollments.Add(s));
            context.SaveChanges();
        }
    }
}
Seed方法将数据库上下文对象作为输入参数,并使用此对象将实体添加到数据库,对于每一个实体类型新建一个集合类型,并将它们添加到相应的DbSet。这里并不需要在每一个集合后使用SaveChanges 方法,但这样做的好处是能在数据发生异常时快速定位问题所在。为了能够使用自己定义的SchoolInitializer类,需要在web.config的entityFramework 节点下加入以下代码:

  <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=http://www.mamicode.com/"v11.0" />>

context 的Type属性指定了数据库上下文类名和它所的在程序集。databaseInitializer的Type属性指定了初始化类的名称和它所在的程序集。(如果你不想EF使用初始化程序,你可以设置disableDatabaseInitialization="true")。

作为另一种替代方法,你可以通过在Global.asax.cs中的Application_Start方法中添加Database.SetInitializer语句来实现同样的功能。

现在应用程序已经被设置为在应用程序第一次运行时,对模型和数据库结构进行比较,如果有区别,应用程序会删除并重新创建该数据库。

注意:当你将应用程序部署到生产环境中时,你必须删除或禁用删除并重新创建数据库的那些代码。

9.设置Entity Framework使用SQL Server Express LocalDB数据库

LocalDB是一个轻量级的SQL Server Express数据库引擎,它容易安装和配置, 并在用户模式下运行。LocalDB运行在一个特殊的执行模式并允许你直接使用数据库的.mdf文件,你可以把LocalDB 数据库复制到web项目的App_Data文件夹中。SQL Server Express的用户实例功能同样允许你直接使用.mdf文件,但是用户实例功能已经不赞成使用了。如果你使用的是isual Studio 2012或更高的版本,LocalDB默认已经被安装。

通常不建议在生产环境中使用SQL Server Express,因为它并不是为IIS而设计的。

打开应用程序的web.config文件(请确保此web.config文件是位于项目根目录下的文件,因为Views 目录下也有一个同样的文件),添加connectionStrings,代码如下:

<connectionStrings>
    <add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity1;Integrated Security=SSPI;" providerName="System.Data.SqlClient"/>
</connectionStrings>
<appSettings>
  <add key="webpages:Version" value=http://www.mamicode.com/"3.0.0.0" />>

添加的数据库连接字符串指定Entity Framework将使用名字为“ContosoUniversity1.mdf”的LocalDb数据库(数据库暂时还不存在,EF会自动创建它)。如果你想将你的数据库文件放在App_data目录中,您必须将AttachDBFilename=|DataDirectory|\ContosoUniversity1.mdf添加到到数据库连接字符串。

如果不在web.config中指定数据库连接字符串,Entity Framework会基于数据库上下文类并使用默认的连接诶字符串。

10.新建Student Controller和Views

现在新建一个网页来显示数据,并在请求数据的过程中自动触发数据库的创建。你首先需要创建一个新的Controller,但在此之前,编译项目并确保MVC controller框架可以正常使用此模型和数据库上下文类。

右键点击Controllers文件夹,选择“Add”,单击“New Scaffolded Item”。

在对话框中,选择“MVC 5 Controller with views, using Entity Framework”


在Add Controller对话框,使用下面的设置:

当你点击“Add”时,框架将创建一个StudentController.cs 文件和一组与之相关联的Views(.cshtml文件)。以后当使用 Entity Framework创建项目时还可以利用框架的一些额外功能:只需创建第一个模型类而无需创建连接字符串,然后在Add Controller对话框指定新的上下文类。
在打开的StudentController.cs文件,你将看到类中已经有一个实例化的数据库上下文对象:

private SchoolContext db = new SchoolContext();
Index方法从数据库上下文实例中的Students属性读取Students实体集来获取学生列表:

 public ViewResult Index()
{
    return View(db.Students.ToList());
}
在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.ID }) |
            @Html.ActionLink("Details", "Details", new { id=item.ID }) |
            @Html.ActionLink("Delete", "Delete", new { id=item.ID })
        </td>
    </tr>
}

CTRL+F5运行项目,点击Students 选项卡查看由Seed方法插入的测试数据:


11.查看数据库

当你打开Students页面并且程序试图访问数据库时,EF检查到没有已存在的数据库并尝试创建一个,然后运行Seed方法向数据库中填充数据。

你可以使用Server Explorer 或者SQL Server Object Explorer来查看数据库,这里将使用Server Explorer。

1.关闭浏览器

2.在Server Explorer中,展开Data Connections,展开School Context(ContosoUniversity),之后展开Tables ,你会看到数据中新建的表,如下图:



3.右击Student表并选择Show Table Data查看表中的数据:



4.关闭Server Explorer数据库连接

ContosoUniversity1.mdf 和 .ldf 数据库文件存放在C:\Users\<yourusername>文件夹下。

由于你使用了DropCreateDatabaseIfModelChanges初始化程序,你现在可以对Student类做出更改,重新运行应用程序,数据库会自动重新建立来匹配你所做出的更改。如果你添加EmailAddress属性到Student类中,重新运行应用程序并打开Student页面,然后检查数据库表中的数据,你会看到新的EmailAddress列。

12.约定

因为使用了约定,Entity Framework 可以为你创建一个完整的数据库,所以你可以只需要写少量的代码。这些约定已经在之前的教程中被你使用到,或许你没有意识到你正在使用它们:

  • 实体类的复数形式被用作表名
  • 实体的属性名被用作列名
  • 以ID或classnameID 命名的属性被用作主键
  • 以<navigation property name><primary key property name>命名的属性被用作外键(例如Student实体的primary key是ID,导航属性的外键是StudentID)。也可以以<primary key property name> 来命名Foreign key属性

其实约定是可以被重写的,例如指定表的名称不应当使用复数形式


项目源码:https://github.com/johnsonz/MvcContosoUniversity

THE END