教程:在 ASP.NET MVC Web 应用中使用 EF Core 入门Tutorial: Get started with EF Core in an ASP.NET MVC web app

本教程介绍具有控制器和视图的 ASP.NET Core MVC 和 Entity Framework Core。This tutorial teaches ASP.NET Core MVC and Entity Framework Core with controllers and views. Razor 页面是 ASP.NET Core 2.0 中的一个新选择,它是基于页面的编程模型,可以实现更简单、更高效地生成 Web UI。Razor Pages is a new alternative in ASP.NET Core 2.0, a page-based programming model that makes building web UI easier and more productive. 建议使用 MVC 版本的 Razor 页面教程。We recommend the Razor Pages tutorial over the MVC version. Razor 页面教程:The Razor Pages tutorial:

  • 易于关注。Is easier to follow.
  • 提供更多 EF Core 最佳做法。Provides more EF Core best practices.
  • 使用更高效的查询。Uses more efficient queries.
  • 通过最新 API 更新到更高版本。Is more current with the latest API.
  • 涵盖更多功能。Covers more features.

Contoso University 示例 Web 应用程序演示如何使用 Entity Framework (EF) Core 2.2 和 Visual Studio 2017 或 2019 创建 ASP.NET Core 2.2 MVC Web 应用程序。The Contoso University sample web application demonstrates how to create ASP.NET Core 2.2 MVC web applications using Entity Framework (EF) Core 2.2 and Visual Studio 2017 or 2019.

示例应用程序供一个虚构的 Contoso 大学网站使用。The sample application is a web site for a fictional Contoso University. 它包括诸如学生入学、 课程创建和导师分配等功能。It includes functionality such as student admission, course creation, and instructor assignments. 这是一系列教程中的第一个,这一系列教程主要展示了如何从零开始构建 Contoso 大学示例应用程序。This is the first in a series of tutorials that explain how to build the Contoso University sample application from scratch.

在本教程中,你将了解:In this tutorial, you:

  • 创建 ASP.NET Core MVC Web 应用Create an ASP.NET Core MVC web app
  • 设置网站样式Set up the site style
  • 了解 EF Core NuGet 包Learn about EF Core NuGet packages
  • 创建数据模型Create the data model
  • 创建数据库上下文Create the database context
  • 为依赖关系注入注册上下文Register the context for dependency injection
  • 使用测试数据初始化数据库Initialize the database with test data
  • 创建控制器和视图Create a controller and views
  • 查看数据库View the database

系统必备Prerequisites

疑难解答Troubleshooting

如果遇到无法解决的问题,可以通过与 已完成的项目对比代码来查找解决方案。If you run into a problem you can't resolve, you can generally find the solution by comparing your code to the completed project. 常见错误以及对应的解决方案,请参阅 最新教程中的故障排除For a list of common errors and how to solve them, see the Troubleshooting section of the last tutorial in the series. 如果没有找到遇到的问题的解决方案,可以将问题发布到StackOverflow.com 的 ASP.NET CoreEF Core 版块。If you don't find what you need there, you can post a question to StackOverflow.com for ASP.NET Core or EF Core.

提示

这是一系列一共有十个教程,其中每个都是在前面教程已完成的基础上继续。This is a series of 10 tutorials, each of which builds on what is done in earlier tutorials. 请考虑在完成每一个教程后保存项目的副本。Consider saving a copy of the project after each successful tutorial completion. 之后如果遇到问题,你可以从保存的副本中开始寻找问题,而不是从头开始。Then if you run into problems, you can start over from the previous tutorial instead of going back to the beginning of the whole series.

Contoso University Web 应用Contoso University web app

你将在这些教程中学习构建一个简单的大学网站的应用程序。The application you'll be building in these tutorials is a simple university web site.

用户可以查看和更新学生、 课程和教师信息。Users can view and update student, course, and instructor information. 以下是一些你即将创建的页面。Here are a few of the screens you'll create.

“学生索引”页

学生编辑页

创建 Web 应用Create web app

  • 打开 Visual Studio。Open Visual Studio.

  • 从“文件”菜单中选择“新建”>“项目”。From the File menu, select New > Project.

  • 从左窗格中依次选择“已安装”>“Visual C#”>“Web”。From the left pane, select Installed > Visual C# > Web.

  • 选择“ASP.NET Core Web 应用程序”项目模板。Select the ASP.NET Core Web Application project template.

  • 输入“ContosoUniversity”作为名称,然后单击“确定”。Enter ContosoUniversity as the name and click OK.

    “新建项目”对话框

  • 等待“新建 ASP.NET Core Web 应用程序”对话框显示出来。Wait for the New ASP.NET Core Web Application dialog to appear.

  • 选择“.NET Core”、“ASP.NET Core 2.2”和“Web 应用程序(模型-视图-控制器)”模板。Select .NET Core, ASP.NET Core 2.2 and the Web Application (Model-View-Controller) template.

  • 请确保 身份验证 设置为 不进行身份验证Make sure Authentication is set to No Authentication.

  • 选择“确定”Select OK

    新的 ASP.NET Core 项目对话框

设置网站样式Set up the site style

通过几个简单的更改设置站点菜单、 布局和主页。A few simple changes will set up the site menu, layout, and home page.

打开 Views/Shared/_Layout.cshtml 并进行以下更改:Open Views/Shared/_Layout.cshtml and make the following changes:

  • 将文件中的"ContosoUniversity"更改为"Contoso University"。Change each occurrence of "ContosoUniversity" to "Contoso University". 需要更改三个地方。There are three occurrences.

  • 添加“关于”、“学生”、“课程”“讲师”和“院系”的菜单项,并删除“隐私”菜单项。Add menu entries for About, Students, Courses, Instructors, and Departments, and delete the Privacy menu entry.

突出显示所作更改。The changes are highlighted.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>

    <environment include="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"
              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
              crossorigin="anonymous"
              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>
    </environment>
    <link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <partial name="_CookieConsentPartial" />
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <footer class="border-top footer text-muted">
        <div class="container">
            &copy; 2019 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
        </div>
    </footer>

    <environment include="Development">
        <script src="~/lib/jquery/dist/jquery.js"></script>
        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    </environment>
    <environment exclude="Development">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
                asp-fallback-test="window.jQuery"
                crossorigin="anonymous"
                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">
        </script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"
                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"
                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
                crossorigin="anonymous"
                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">
        </script>
    </environment>
    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)
</body>
</html>

Views/Home/Index.cshtml,将文件的内容替换为以下代码以将有关 ASP.NET 和 MVC 的内容替换为有关此应用程序的内容:In Views/Home/Index.cshtml, replace the contents of the file with the following code to replace the text about ASP.NET and MVC with text about this application:

@{
    ViewData["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 Core in an
            ASP.NET Core MVC 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 a series of tutorials.</p>
        <p><a class="btn btn-default" href="https://docs.asp.net/en/latest/data/ef-mvc/intro.html">See the tutorial &raquo;</a></p>
    </div>
    <div class="col-md-4">
        <h2>Download it</h2>
        <p>You can download the completed project from GitHub.</p>
        <p><a class="btn btn-default" href="https://github.com/aspnet/AspNetCore.Docs/tree/master/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
    </div>
</div>

按 CTRL + F5 来运行该项目或从菜单选择 调试 > 开始执行(不调试)Press CTRL+F5 to run the project or choose Debug > Start Without Debugging from the menu. 你会看到首页,以及通过这个教程创建的页对应的选项卡。You see the home page with tabs for the pages you'll create in these tutorials.

Contoso University 主页

关于 EF Core NuGet 包About EF Core NuGet packages

若要为项目添加 EF Core 支持,需要安装相应的数据库驱动包。To add EF Core support to a project, install the database provider that you want to target. 本教程使用 SQL Server,相关驱动包Microsoft.EntityFrameworkCore.SqlServerThis tutorial uses SQL Server, and the provider package is Microsoft.EntityFrameworkCore.SqlServer. 此包包含在 Microsoft.AspNetCore.App 元包中,因此无需引用该包。This package is included in the Microsoft.AspNetCore.App metapackage, so you don't need to reference the package.

EF SQL Server 包和其依赖项(Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Relational)一起提供 EF 的运行时支持。The EF SQL Server package and its dependencies (Microsoft.EntityFrameworkCore and Microsoft.EntityFrameworkCore.Relational) provide runtime support for EF. 你将在之后的 迁移 教程中学习添加工具包。You'll add a tooling package later, in the Migrations tutorial.

有关其他可用于 EF Core 的数据库驱动的信息,请参阅 数据库驱动For information about other database providers that are available for Entity Framework Core, see Database providers.

创建数据模型Create the data model

接下来你将创建 Contoso 大学应用程序的实体类。Next you'll create entity classes for the Contoso University application. 你将从以下三个实体类开始。You'll start with the following three entities.

Course-Enrollment-Student 数据模型关系图

StudentEnrollment实体之间是一对多的关系,CourseEnrollment 实体之间也是一个对多的关系。There's a one-to-many relationship between Student and Enrollment entities, and there's a one-to-many relationship between Course and Enrollment entities. 换而言之,一名学生可以修读任意数量的课程, 并且某一课程可以被任意数量的学生修读。In other words, a student can be enrolled in any number of courses, and a course can have any number of students enrolled in it.

接下来,你将创建与这些实体对应的类。In the following sections you'll create a class for each one of these entities.

Student 实体The Student entity

Student 实体关系图

Models 文件夹中,创建一个名为 Student.cs 的类文件并且将模板代码替换为以下代码。In the Models folder, create a class file named Student.cs and replace the template code with the following code.

using System;
using System.Collections.Generic;

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 ICollection<Enrollment> Enrollments { get; set; }
    }
}

ID 属性将成为对应于此类的数据库表中的主键。The ID property will become the primary key column of the database table that corresponds to this class. 默认情况下,EF 将会将名为 IDclassnameID 的属性解析为主键。By default, the Entity Framework interprets a property that's named ID or classnameID as the primary key.

Enrollments 属性是导航属性The Enrollments property is a navigation property. 导航属性中包含与此实体相关的其他实体。Navigation properties hold other entities that are related to this entity. 在这个案例下,Student entity 中的 Enrollments 属性会保留所有与 Student 实体相关的 EnrollmentIn this case, the Enrollments property of a Student entity will hold all of the Enrollment entities that are related to that Student entity. 换而言之,如果在数据库中有两行描述同一个学生的修读情况 (两行的 StudentID 值相同,而且 StudentID 作为外键和某位学生的主键值相同), Student 实体的 Enrollments 导航属性将包含那两个 Enrollment 实体。In other words, if a given Student row in the database has two related Enrollment rows (rows that contain that student's primary key value in their StudentID foreign key column), that Student entity's Enrollments navigation property will contain those two Enrollment entities.

如果导航属性可以具有多个实体 (如多对多或一对多关系),那么导航属性的类型必须是可以添加、 删除和更新条目的容器,如 ICollection<T>If a navigation property can hold multiple entities (as in many-to-many or one-to-many relationships), its type must be a list in which entries can be added, deleted, and updated, such as ICollection<T>. 你可以指定 ICollection<T> 或实现该接口类型,如 List<T>HashSet<T>You can specify ICollection<T> or a type such as List<T> or HashSet<T>. 如果指定 ICollection<T>,EF在默认情况下创建 HashSet<T> 集合。If you specify ICollection<T>, EF creates a HashSet<T> collection by default.

Enrollment 实体The Enrollment entity

修读实体关系图

Models 文件夹中,创建 Enrollment.cs 并且用以下代码替换现有代码:In the Models folder, create Enrollment.cs and replace the existing code with the following code:

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 Course Course { get; set; }
        public Student Student { get; set; }
    }
}

EnrollmentID 属性将被设为主键; 此实体使用 classnameID 模式而不是如 Student 实体那样直接使用 IDThe EnrollmentID property will be the primary key; this entity uses the classnameID pattern instead of ID by itself as you saw in the Student entity. 通常情况下,你选择一个主键模式,并在你的数据模型自始至终使用这种模式。Ordinarily you would choose one pattern and use it throughout your data model. 在这里,使用了两种不同的模式只是为了说明你可以使用任一模式来指定主键。Here, the variation illustrates that you can use either pattern. 后面的教程,你将了解到使用ID这种模式可以更轻松地在数据模型之间实现继承。In a later tutorial, you'll see how using ID without classname makes it easier to implement inheritance in the data model.

Grade 属性是 enumThe Grade property is an enum. Grade 声明类型后的?表示 Grade 属性可以为 null。The question mark after the Grade type declaration indicates that the Grade property is nullable. 评级为 null 和评级为零是有区别的 --null 意味着评级未知或者尚未分配。A grade that's null is different from a zero grade -- null means a grade isn't known or hasn't been assigned yet.

StudentID 属性是一个外键,Student 是与其且对应的导航属性。The StudentID property is a foreign key, and the corresponding navigation property is Student. Enrollment 实体与一个 Student 实体相关联,因此该属性只包含单个 Student 实体 (与前面所看到的 Student.Enrollments 导航属性不同后,Student中可以容纳多个 Enrollment 实体)。An Enrollment entity is associated with one Student entity, so the property can only hold a single Student entity (unlike the Student.Enrollments navigation property you saw earlier, which can hold multiple Enrollment entities).

CourseID 属性是一个外键, Course 是与其对应的导航属性。The CourseID property is a foreign key, and the corresponding navigation property is Course. Enrollment 实体与一个 Course 实体相关联。An Enrollment entity is associated with one Course entity.

如果一个属性名为 <navigation property name><primary key property name>,Entity Framework 就会将这个属性解析为外键属性(例如, Student 实体的主键是IDStudentEnrollment的导航属性所以Enrollment实体中 StudentID 会被解析为外键)。Entity Framework interprets a property as a foreign key property if it's named <navigation property name><primary key property name> (for example, StudentID for the Student navigation property since the Student entity's primary key is ID). 此外还可以将需要解析为外键的属性命名为 <primary key property name> (例如,CourseID 由于 Course 实体的主键所以 CourseID 也被解析为外键)。Foreign key properties can also be named simply <primary key property name> (for example, CourseID since the Course entity's primary key is CourseID).

Course 实体The Course entity

Course 实体关系图

Models 文件夹中,创建 Course.cs 并且用以下代码替换现有代码:In the Models folder, create Course.cs and replace the existing code with the following code:

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 ICollection<Enrollment> Enrollments { get; set; }
    }
}

Enrollments 属性是导航属性。The Enrollments property is a navigation property. 一个 Course 体可以与任意数量的 Enrollment 实体相关。A Course entity can be related to any number of Enrollment entities.

我们在本系列 后面的教程 中会有更多有关 DatabaseGenerated 特性的例子。We'll say more about the DatabaseGenerated attribute in a later tutorial in this series. 简单来说,此特性让你能自行指定主键,而不是让数据库自动指定主键。Basically, this attribute lets you enter the primary key for the course rather than having the database generate it.

创建数据库上下文Create the database context

使得给定的数据模型与 Entity Framework 功能相协调的主类是数据库上下文类。The main class that coordinates Entity Framework functionality for a given data model is the database context class. 可以通过继承 Microsoft.EntityFrameworkCore.DbContext 类的方式创建此类。You create this class by deriving from the Microsoft.EntityFrameworkCore.DbContext class. 在该类中你可以指定数据模型中包含哪些实体。In your code you specify which entities are included in the data model. 你还可以定义某些 Entity Framework 行为。You can also customize certain Entity Framework behavior. 在此项目中将数据库上下文类命名为 SchoolContextIn this project, the class is named SchoolContext.

在项目文件夹中,创建名为的文件夹 DataIn the project folder, create a folder named Data.

Data 文件夹创建名为 SchoolContext.cs 的类文件,并将模板代码替换为以下代码:In the Data folder create a new class file named SchoolContext.cs, and replace the template code with the following code:

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

此代码将为每个实体集创建 DbSet 属性。This code creates a DbSet property for each entity set. 在 Entity Framework 中,实体集通常与数据表相对应,具体实体与表中的行相对应。In Entity Framework terminology, an entity set typically corresponds to a database table, and an entity corresponds to a row in the table.

在这里可以省略 DbSet<Enrollment>DbSet<Course> 语句,实现的功能没有任何改变。You could've omitted the DbSet<Enrollment> and DbSet<Course> statements and it would work the same. Entity Framework 会隐式包含这两个实体因为 Student 实体引用了 Enrollment 实体、Enrollment 实体引用了 Course 实体。The Entity Framework would include them implicitly because the Student entity references the Enrollment entity and the Enrollment entity references the Course entity.

当数据库创建完成后, EF 创建一系列数据表,表名默认和 DbSet 属性名相同。When the database is created, EF creates tables that have names the same as the DbSet property names. 集合属性的名称一般使用复数形式,但不同的开发人员的命名习惯可能不一样,开发人员根据自己的情况确定是否使用复数形式。Property names for collections are typically plural (Students rather than Student), but developers disagree about whether table names should be pluralized or not. 在定义 DbSet 属性的代码之后,添加下面高亮代码,对 DbContext 指定单数的表名来覆盖默认的表名。For these tutorials you'll override the default behavior by specifying singular table names in the DbContext. 此教程在最后一个 DbSet 属性之后,添加以下高亮显示的代码。To do that, add the following highlighted code after the last DbSet property.

using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }
    }
}

注册 SchoolContextRegister the SchoolContext

ASP.NET Core 默认实现 依赖注入ASP.NET Core implements dependency injection by default. 在应用程序启动过程通过依赖注入注册相关服务 (例如 EF 数据库上下文)。Services (such as the EF database context) are registered with dependency injection during application startup. 需要这些服务的组件 (如 MVC 控制器) 可以通过向构造函数添加相关参数来获得对应服务。Components that require these services (such as MVC controllers) are provided these services via constructor parameters. 在本教程后面你将看到控制器构造函数的代码,就是通过上述方式获得上下文实例。You'll see the controller constructor code that gets a context instance later in this tutorial.

若要将 SchoolContext 注册为一种服务,打开 Startup.cs ,并将高亮代码添加到 ConfigureServices 方法中。To register SchoolContext as a service, open Startup.cs, and add the highlighted lines to the ConfigureServices method.

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<SchoolContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddMvc();
}

通过调用 DbContextOptionsBuilder 中的一个方法将数据库连接字符串在配置文件中的名称传递给上下文对象。The name of the connection string is passed in to the context by calling a method on a DbContextOptionsBuilder object. 进行本地开发时, ASP.NET Core 配置系统appsettings.json 文件中读取数据库连接字符串。For local development, the ASP.NET Core configuration system reads the connection string from the appsettings.json file.

添加 using 语句引用 ContosoUniversity.DataMicrosoft.EntityFrameworkCore 命名空间,然后生成项目。Add using statements for ContosoUniversity.Data and Microsoft.EntityFrameworkCore namespaces, and then build the project.

using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;

打开 appsettings.json 文件,并如以下示例所示添加连接字符串。Open the appsettings.json file and add a connection string as shown in the following example.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}

SQL Server Express LocalDBSQL Server Express LocalDB

数据库连接字符串指定使用 SQL Server LocalDB 数据库。The connection string specifies a SQL Server LocalDB database. LocalDB 是 SQL Server Express 数据库引擎的轻量级版本,用于应用程序开发,不在生产环境中使用。LocalDB is a lightweight version of the SQL Server Express Database Engine and is intended for application development, not production use. LocalDB 作为按需启动并在用户模式下运行的轻量级数据库没有复杂的配置。LocalDB starts on demand and runs in user mode, so there's no complex configuration. 默认情况下, LocalDB 在 C:/Users/<user> 目录下创建 .mdf 数据库文件。By default, LocalDB creates .mdf database files in the C:/Users/<user> directory.

使用测试数据初始化数据库Initialize DB with test data

Entity Framework 已经为你创建了一个空数据库。The Entity Framework will create an empty database for you. 在本部分中,你将编写一个方法用于向数据库填充测试数据,该方法会在数据库创建完成之后执行。In this section, you write a method that's called after the database is created in order to populate it with test data.

此处将使用 EnsureCreated 方法来自动创建数据库。Here you'll use the EnsureCreated method to automatically create the database. 后面的教程 你将了解如何通过使用 Code First Migration 来更改而不是删除并重新创建数据库来处理模型更改。In a later tutorial you'll see how to handle model changes by using Code First Migrations to change the database schema instead of dropping and re-creating the database.

Data 文件夹中,创建名为的新类文件 DbInitializer.cs 并且将模板代码替换为以下代码,使得在需要时能创建数据库并向其填充测试数据。In the Data folder, create a new class file named DbInitializer.cs and replace the template code with the following code, which causes a database to be created when needed and loads test data into the new database.

using ContosoUniversity.Models;
using System;
using System.Linq;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new 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")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var courses = new 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}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var enrollments = new 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},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }
}

这段代码首先检查是否有学生数据在数据库中,如果没有的话,就可以假定数据库是新建的,然后使用测试数据进行填充。The code checks if there are any students in the database, and if not, it assumes the database is new and needs to be seeded with test data. 代码中使用数组存放测试数据而不是使用 List<T> 集合是为了优化性能。It loads test data into arrays rather than List<T> collections to optimize performance.

Program.cs,修改 Main 方法,使得在应用程序启动时能执行以下操作:In Program.cs, modify the Main method to do the following on application startup:

  • 从依赖注入容器中获取数据库上下文实例。Get a database context instance from the dependency injection container.
  • 调用 seed 方法,将上下文传递给它。Call the seed method, passing to it the context.
  • Seed 方法完成此操作时释放上下文。Dispose the context when the seed method is done.
public static void Main(string[] args)
{
     var host = CreateWebHostBuilder(args).Build();

    using (var scope = host.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        try
        {
            var context = services.GetRequiredService<SchoolContext>();
            DbInitializer.Initialize(context);
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred while seeding the database.");
        }
    }

    host.Run();
}

添加 using 语句:Add using statements:

using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;

在旧版教程中,你可能会在 Startup.cs 中的 Configure 方法看到类似的代码。In older tutorials, you may see similar code in the Configure method in Startup.cs. 我们建议你只在为了设置请求管道时使用 Configure 方法。We recommend that you use the Configure method only to set up the request pipeline. 将应用程序启动代码放入 Main 方法。Application startup code belongs in the Main method.

现在首次运行该应用程序,创建数据库并使用测试数据作为种子数据。Now the first time you run the application, the database will be created and seeded with test data. 每当你更改数据模型时,可以删除数据库、 更新你的 Initialize 方法,然后使用上述方式更新新数据库。Whenever you change your data model, you can delete the database, update your seed method, and start afresh with a new database the same way. 在之后的教程中,你将了解如何在数据模型更改时,只需修改数据库而无需删除重建数据库。In later tutorials, you'll see how to modify the database when the data model changes, without deleting and re-creating it.

创建控制器和视图Create controller and views

接下来,将使用 Visual Studio 中的基架引擎添加一个 MVC 控制器,以及使用 EF 来查询和保存数据的视图。Next, you'll use the scaffolding engine in Visual Studio to add an MVC controller and views that will use EF to query and save data.

CRUD 操作方法和视图的自动创建被称为基架。The automatic creation of CRUD action methods and views is known as scaffolding. 基架与代码生成不同,基架的代码是一个起点,可以修改基架以满足自己需求,而你通常无需修改生成的代码。Scaffolding differs from code generation in that the scaffolded code is a starting point that you can modify to suit your own requirements, whereas you typically don't modify generated code. 当你需要自定义生成代码时,可使用一部分类或需求发生变化时重新生成代码。When you need to customize generated code, you use partial classes or you regenerate the code when things change.

  • 右键单击 解决方案资源管理器 中的 Controllers 文件夹选择 添加 > 新搭建基架的项目Right-click the Controllers folder in Solution Explorer and select Add > New Scaffolded Item.

  • 在“添加基架”对话框中:In the Add Scaffold dialog box:

    • 选择 视图使用 Entity Framework 的 MVC 控制器Select MVC controller with views, using Entity Framework.

    • 单击 添加Click Add. 随即将显示“使用 Entity Framework 添加包含视图的 MVC 控制器”对话框。The Add MVC Controller with views, using Entity Framework dialog box appears.

      构架 Student

    • 模型类 选择 StudentIn Model class select Student.

    • 在“数据上下文类”中选择 SchoolContext。In Data context class select SchoolContext.

    • 使用 StudentsController 作为默认名称。Accept the default StudentsController as the name.

    • 单击 添加Click Add.

    当你单击 添加 后,Visual Studio 基架引擎创建 StudentsController.cs 文件和一组对应于控制器的视图 (.cshtml 文件) 。When you click Add, the Visual Studio scaffolding engine creates a StudentsController.cs file and a set of views (.cshtml files) that work with the controller.

(如果你之前手动创建数据库上下文,基架引擎还可以自动创建。(The scaffolding engine can also create the database context for you if you don't create it manually first as you did earlier for this tutorial. 你可以在 添加控制器 对话框中单击右侧的加号框 数据上下文类 来指定在一个新上下文类。You can specify a new context class in the Add Controller box by clicking the plus sign to the right of Data context class. 然后,Visual Studio 将创建你的 DbContext,控制器和视图类。)Visual Studio will then create your DbContext class as well as the controller and views.)

注意控制器将 SchoolContext 作为构造函数参数。You'll notice that the controller takes a SchoolContext as a constructor parameter.

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

ASP.NET Core 依赖关系注入负责将 SchoolContext 实例传递到控制器。ASP.NET Core dependency injection takes care of passing an instance of SchoolContext into the controller. 在前面的教程中已经通过修改 Startup.cs 文件来配置注入规则。You configured that in the Startup.cs file earlier.

控制器包含 Index 操作方法,用于显示数据库中的所有学生。The controller contains an Index action method, which displays all students in the database. 该方法从学生实体集中获取学生列表,学生实体集则是通过读取数据库上下文实例中的 Students 属性获得:The method gets a list of students from the Students entity set by reading the Students property of the database context instance:

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

本教程后面部分将介绍此代码中的异步编程元素。You'll learn about the asynchronous programming elements in this code later in the tutorial.

Views/Students/Index.cshtml 视图使用table标签显示此列表:The Views/Students/Index.cshtml view displays this list in a table:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <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>
    </thead>
    <tbody>
@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>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

按 CTRL + F5 来运行该项目,或从菜单选择 调试 > 开始执行(不调试)Press CTRL+F5 to run the project or choose Debug > Start Without Debugging from the menu.

单击学生选项卡以查看 DbInitializer.Initialize 插入的测试的数据。Click the Students tab to see the test data that the DbInitializer.Initialize method inserted. 你将看到 Students 选项卡链接在页的顶部或在单击右上角后的导航图标中,具体显示在哪里取决于浏览器窗口宽度。Depending on how narrow your browser window is, you'll see the Students tab link at the top of the page or you'll have to click the navigation icon in the upper right corner to see the link.

Contoso University 主页宽窄

“学生索引”页

查看数据库View the database

当你启动了应用程序,DbInitializer.Initialize 方法调用 EnsureCreatedWhen you started the application, the DbInitializer.Initialize method calls EnsureCreated. EF 没有检测到相关数据库,因此自己创建了一个,接着 Initialize 方法的其余代码向数据库中填充数据。EF saw that there was no database and so it created one, then the remainder of the Initialize method code populated the database with data. 你可以使用 Visual Studio 中的 SQL Server 对象资源管理器 (SSOX) 查看数据库。You can use SQL Server Object Explorer (SSOX) to view the database in Visual Studio.

关闭浏览器。Close the browser.

如果 SSOX 窗口尚未打开,请从Visual Studio 中的视图 菜单中选择。If the SSOX window isn't already open, select it from the View menu in Visual Studio.

在 SSOX 中,单击 (localdb) \MSSQLLocalDB > 数据库,然后单击和 appsettings.json 文件中的连接字符串对应的数据库。In SSOX, click (localdb)\MSSQLLocalDB > Databases, and then click the entry for the database name that's in the connection string in your appsettings.json file.

展开“表”节点,查看数据库中的表。Expand the Tables node to see the tables in your database.

SSOX 中的表

右键单击 Student 表,然后单击 查看数据,即可查看已创建的列和已插入到表的行。Right-click the Student table and click View Data to see the columns that were created and the rows that were inserted into the table.

SSOX 中的 Student 表

.mdf 和 .ldf 数据库文件位于 C:\Users\<yourusername> 文件夹中。The .mdf and .ldf database files are in the C:\Users\<yourusername> folder.

因为调用 EnsureCreated 的初始化方法在启动应用程序时才运行,所以在这之前你可以更改 Student 类、 删除数据库、 再运行一次应用程序,这时候数据库将自动重新创建,以匹配所做的更改。Because you're calling EnsureCreated in the initializer method that runs on app start, you could now make a change to the Student class, delete the database, run the application again, and the database would automatically be re-created to match your change. 例如,如果向 Student 类添加 EmailAddress 属性,重新的创建表中会有 EmailAddress 列。For example, if you add an EmailAddress property to the Student class, you'll see a new EmailAddress column in the re-created table.

约定Conventions

由于 Entity Framwork 有一定的约束条件,你只需要按规则编写很少的代码就能够创建一个完整的数据库。The amount of code you had to write in order for the Entity Framework to be able to create a complete database for you is minimal because of the use of conventions, or assumptions that the Entity Framework makes.

  • DbSet 类型的属性用作表名。The names of DbSet properties are used as table names. 如果实体未被 DbSet 属性引用,实体类名称用作表名称。For entities not referenced by a DbSet property, entity class names are used as table names.

  • 使用实体属性名作为列名。Entity property names are used for column names.

  • 以 ID 或 classnameID 命名的实体属性被视为主键属性。Entity properties that are named ID or classnameID are recognized as primary key properties.

  • 如果属性名为 <导航属性名><主键属性名>(例如,StudentID 对应 Student 导航属性,因为 Student 实体的主键是 ID),其将被解释为外键属性。A property is interpreted as a foreign key property if it's named <navigation property name><primary key property name> (for example, StudentID for the Student navigation property since the Student entity's primary key is ID). 此外还可以将外键属性仅命名为 <主键属性名>(例如 EnrollmentID,因为 Enrollment 实体的主键为 EnrollmentID)。Foreign key properties can also be named simply <primary key property name> (for example, EnrollmentID since the Enrollment entity's primary key is EnrollmentID).

约定行为可以重写。Conventional behavior can be overridden. 例如,本教程前半部分显式指定表名称。For example, you can explicitly specify table names, as you saw earlier in this tutorial. 本系列 后面教程 则设置列名称并将任何属性设置为主键或外键。And you can set column names and set any property as primary key or foreign key, as you'll see in a later tutorial in this series.

异步代码Asynchronous code

异步编程是 ASP.NET Core 和 EF Core 的默认模式。Asynchronous programming is the default mode for ASP.NET Core and EF Core.

Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。A web server has a limited number of threads available, and in high load situations all of the available threads might be in use. 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。When that happens, the server can't process new requests until the threads are freed up. 使用同步代码时,可能会出现多个线程被占用但不能执行任何操作的情况,因为它们正在等待 I/O 完成。With synchronous code, many threads may be tied up while they aren't actually doing any work because they're waiting for I/O to complete. 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。With asynchronous code, when a process is waiting for I/O to complete, its thread is freed up for the server to use for processing other requests. 因此,异步代码使得服务器更有效地使用资源,并且该服务器可以无延迟地处理更多流量。As a result, asynchronous code enables server resources to be used more efficiently, and the server is enabled to handle more traffic without delays.

异步代码在运行时,会引入的少量开销,在低流量时对性能的影响可以忽略不计,但在针对高流量情况下潜在的性能提升是可观的。Asynchronous code does introduce a small amount of overhead at run time, but for low traffic situations the performance hit is negligible, while for high traffic situations, the potential performance improvement is substantial.

在以下代码中,async 关键字、Task<T> 返回值、await 关键字和 ToListAsync 方法让代码异步执行。In the following code, the async keyword, Task<T> return value, await keyword, and ToListAsync method make the code execute asynchronously.

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • async 关键字用于告知编译器该方法主体将生成回调并自动创建 Task<IActionResult> 返回对象。The async keyword tells the compiler to generate callbacks for parts of the method body and to automatically create the Task<IActionResult> object that's returned.

  • 返回类型 Task<IActionResult> 表示正在进行的工作返回的结果为 IActionResult 类型。The return type Task<IActionResult> represents ongoing work with a result of type IActionResult.

  • await 关键字会使得编译器将方法拆分为两个部分。The await keyword causes the compiler to split the method into two parts. 第一部分是以异步方式结束已启动的操作。The first part ends with the operation that's started asynchronously. 第二部分是当操作完成时注入调用回调方法的地方。The second part is put into a callback method that's called when the operation completes.

  • ToListAsyncToList 方法的的异步扩展版本。ToListAsync is the asynchronous version of the ToList extension method.

使用 Entity Framework 编写异步代码时的一些注意事项:Some things to be aware of when you are writing asynchronous code that uses the Entity Framework:

  • 只有导致查询或发送数据库命令的语句才能以异步方式执行。Only statements that cause queries or commands to be sent to the database are executed asynchronously. 包括 ToListAsyncSingleOrDefaultAsync,和 SaveChangesAsyncThat includes, for example, ToListAsync, SingleOrDefaultAsync, and SaveChangesAsync. 不包括只需更改 IQueryable 的语句,如 var students = context.Students.Where(s => s.LastName == "Davolio")It doesn't include, for example, statements that just change an IQueryable, such as var students = context.Students.Where(s => s.LastName == "Davolio").

  • EF 上下文是线程不安全的: 请勿尝试并行执行多个操作。An EF context isn't thread safe: don't try to do multiple operations in parallel. 当调用异步 EF 方法时,始终使用 await 关键字。When you call any async EF method, always use the await keyword.

  • 如果你想要利用异步代码的性能优势,请确保你所使用的任何库和包在它们调用导致 Entity Framework 数据库查询方法时也使用异步。If you want to take advantage of the performance benefits of async code, make sure that any library packages that you're using (such as for paging), also use async if they call any Entity Framework methods that cause queries to be sent to the database.

有关在 .NET 异步编程的详细信息,请参阅 异步概述For more information about asynchronous programming in .NET, see Async Overview.

获取代码Get the code

下载或查看已完成的应用程序。Download or view the completed application.

后续步骤Next steps

在本教程中,你将了解:In this tutorial, you:

  • 已创建 ASP.NET Core MVC Web 应用Created ASP.NET Core MVC web app
  • 设置网站样式Set up the site style
  • 已了解 EF Core NuGet 包Learned about EF Core NuGet packages
  • 已创建数据模型Created the data model
  • 已创建数据库上下文Created the database context
  • 已注册 SchoolContextRegistered the SchoolContext
  • 已使用测试数据初始化数据库Initialized DB with test data
  • 已创建控制器和视图Created controller and views
  • 已查看数据库Viewed the database

在下一个教程中,你将学习如何执行基本的 CRUD (创建、 读取、 更新、 删除) 操作。In the following tutorial, you'll learn how to perform basic CRUD (create, read, update, delete) operations.

请继续阅读下一篇教程,了解如何执行基本的 CRUD(创建、读取、更新、删除)操作。Advance to the next tutorial to learn how to perform basic CRUD (create, read, update, delete) operations.