ASP.NET Core 中的 Razor Pages 和 Entity Framework Core - 第 1 个教程(共 8 个)Razor Pages with Entity Framework Core in ASP.NET Core - Tutorial 1 of 8

作者:Tom DykstraRick AndersonBy Tom Dykstra and Rick Anderson

本文是系列教程的第一篇,这些教程展示如何在 ASP.NET Core Razor Pages 应用中使用 Entity Framework (EF) Core。This is the first in a series of tutorials that show how to use Entity Framework (EF) Core in an ASP.NET Core Razor Pages app. 这些教程为虚构的 Contoso University 生成一个网站。The tutorials build a web site for a fictional Contoso University. 网站包括学生录取、课程创建和讲师分配等功能。The site includes functionality such as student admission, course creation, and instructor assignments. 本教程使用代码优先方法。The tutorial uses the code first approach. 有关使用数据库优先方法学习本教程的信息,请参阅此 Github 问题For information on following this tutorial using the database first approach, see this Github issue.

下载或查看已完成的应用。Download or view the completed app. 下载说明Download instructions.

先决条件Prerequisites

数据库引擎Database engines

Visual Studio 指令使用 SQL Server LocalDB,它是只在 Windows 上运行的一种 SQL Server Express 版本。The Visual Studio instructions use SQL Server LocalDB, a version of SQL Server Express that runs only on Windows.

Visual Studio Code 指令使用 SQLite,一种跨平台数据库引擎。The Visual Studio Code instructions use SQLite, a cross-platform database engine.

如果选择使用 SQLite,请下载并安装适用于 SQLite 的数据库浏览器等第三方工具,用于管理和查看 SQLite 数据库。If you choose to use SQLite, download and install a third-party tool for managing and viewing a SQLite database, such as DB Browser for SQLite.

疑难解答Troubleshooting

如果遇到无法解决的问题,请将你的代码与完成的项目进行比较。If you run into a problem you can't resolve, compare your code to the completed project. 获取帮助的一个好方法是使用 ASP.NET Core 标记EF Core 标记将问题发布到 StackOverflow.com。A good way to get help is by posting a question to StackOverflow.com, using the ASP.NET Core tag or the EF Core tag.

示例应用The sample app

这些教程中所构建的应用是一个基本的大学网站。The app built in these tutorials is a basic university web site. 用户可以查看和更新学生、课程和讲师信息。Users can view and update student, course, and instructor information. 以下是在本教程中创建的几个屏幕。Here are a few of the screens created in the tutorial.

“学生索引”页

学生编辑页

此网站的 UI 样式基于内置的项目模板。The UI style of this site is based on the built-in project templates. 本教程侧重于如何使用 EF Core,而不是如何自定义 UI。The tutorial's focus is on how to use EF Core, not how to customize the UI.

单击页面顶部的链接,获取已完成项目的源代码。Follow the link at the top of the page to get the source code for the completed project. “cu30”文件夹中有本教程的 ASP.NET Core 3.0 版本的代码。The cu30 folder has the code for the ASP.NET Core 3.0 version of the tutorial. 在“cu30snapshots”文件夹中可以找到反映教程 1-7 代码状态的文件。Files that reflect the state of the code for tutorials 1-7 can be found in the cu30snapshots folder.

若要在下载完成的项目之后运行应用,请执行以下操作:To run the app after downloading the completed project:

  • 生成项目。Build the project.

  • 在包管理器控制台 (PMC) 中运行以下命令:In Package Manager Console (PMC) run the following command:

    Update-Database
    
  • 运行项目,设定数据库种子。Run the project to seed the database.

创建 Web 应用项目Create the web app project

  • 从 Visual Studio“文件”菜单中选择“新建”>“项目”。From the Visual Studio File menu, select New > Project.
  • 选择“ASP.NET Core Web 应用程序”。Select ASP.NET Core Web Application.
  • 将该项目命名为 ContosoUniversity 。Name the project ContosoUniversity. 请务必使用此名称(含大写),确保在复制和粘贴代码时与命名空间相匹配。It's important to use this exact name including capitalization, so the namespaces match when code is copied and pasted.
  • 在下拉列表中选择“.NET Core”和“ASP.NET Core 3.0”,然后选择“Web 应用程序” 。Select .NET Core and ASP.NET Core 3.0 in the dropdowns, and then select Web Application.

设置网站样式Set up the site style

更新 Pages/Shared/_Layout.cshtml 以设置网站的页眉、页脚和菜单:Set up the site header, footer, and menu by updating Pages/Shared/_Layout.cshtml:

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

  • 删除“主页”和“隐私”菜单项,然后添加“关于”、“学生”、“课程”、“讲师”和“院系”的菜单项 。Delete the Home and Privacy menu entries, and add entries for About, Students, Courses, Instructors, and Departments.

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - Contoso University</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
    <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-page="/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-page="/About">About</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Students/Index">Students</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Courses/Index">Courses</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Instructors/Index">Instructors</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Departments/Index">Departments</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
    <div class="container">
        <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-page="/Privacy">Privacy</a>
        </div>
    </footer>

    <script src="~/lib/jquery/dist/jquery.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>

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

在 Pages/Index.cshtml 中,将文件内容替换为以下代码,以将有关 ASP.NET Core 的文本替换为有关本应用的文本:In Pages/Index.cshtml, replace the contents of the file with the following code to replace the text about ASP.NET Core with text about this app:

@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="row mb-auto">
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 mb-4 ">
                <p class="card-text">
                    Contoso University is a sample application that
                    demonstrates how to use Entity Framework Core in an
                    ASP.NET Core Razor Pages web app.
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column position-static">
                <p class="card-text mb-auto">
                    You can build the application by following the steps in a series of tutorials.
                </p>
                <p>
                    <a href="https://docs.microsoft.com/aspnet/core/data/ef-rp/intro" class="stretched-link">See the tutorial</a>
                </p>
            </div>
        </div>
    </div>
    <div class="col-md-4">
        <div class="row no-gutters border mb-4">
            <div class="col p-4 d-flex flex-column">
                <p class="card-text mb-auto">
                    You can download the completed project from GitHub.
                </p>
                <p>
                    <a href="https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/data/ef-rp/intro/samples" class="stretched-link">See project source code</a>
                </p>
            </div>
        </div>
    </div>
</div>

运行应用以验证主页是否显示。Run the app to verify that the home page appears.

数据模型The data model

以下部分用于创建数据模型:The following sections create a data model:

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

一名学生可以修读任意数量的课程,并且某一课程可以有任意数量的学生修读。A student can enroll in any number of courses, and a course can have any number of students enrolled in it.

Student 实体The Student entity

Student 实体关系图

  • 在项目文件夹中创建“Models”文件夹。Create a Models folder in the project folder.

  • 使用以下代码创建 Models/Student.cs:Create Models/Student.cs 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 becomes the primary key column of the database table that corresponds to this class. 默认情况下,EF Core 将名为 IDclassnameID 的属性视为主键。By default, EF Core interprets a property that's named ID or classnameID as the primary key. 因此,Student 类主键的另一种自动识别的名称是 StudentIDSo the alternative automatically recognized name for the Student class primary key is StudentID. 有关详细信息,请参阅 F Core - 密钥For more information, see EF Core - Keys.

Enrollments 属性是导航属性The Enrollments property is a navigation property. 导航属性中包含与此实体相关的其他实体。Navigation properties hold other entities that are related to this entity. 在本例中,Student 实体的 Enrollments 属性包含与该 Student 相关的所有 Enrollment 实体。In this case, the Enrollments property of a Student entity holds all of the Enrollment entities that are related to that Student. 例如,如果数据库中的 Student 行有两个相关的 Enrollment 行,则 Enrollments 导航属性包含这两个 Enrollment 实体。For example, if a Student row in the database has two related Enrollment rows, the Enrollments navigation property contains those two Enrollment entities.

在数据库中,如果 StudentID 列包含学生的 ID 值,则 Enrollment 行与 Student 行相关。In the database, an Enrollment row is related to a Student row if its StudentID column contains the student's ID value. 例如,假设某个 Student 行的 ID=1。For example, suppose a Student row has ID=1. 则相关 Enrollment 行的 StudentID = 1。Related Enrollment rows will have StudentID = 1. StudentID 是 Enrollment 表中的外键。StudentID is a foreign key in the Enrollment table.

Enrollments 属性定义为 ICollection<Enrollment>,因为可能有多个相关的 Enrollment 实体。The Enrollments property is defined as ICollection<Enrollment> because there may be multiple related Enrollment entities. 可以使用 List<Enrollment>HashSet<Enrollment> 等其他集合类型。You can use other collection types, such as List<Enrollment> or HashSet<Enrollment>. 使用 ICollection<Enrollment> 时,EF Core 会默认创建 HashSet<Enrollment> 集合。When ICollection<Enrollment> is used, EF Core creates a HashSet<Enrollment> collection by default.

Enrollment 实体The Enrollment entity

Enrollment 实体关系图

使用以下代码创建 Models/Enrollment.cs:Create Models/Enrollment.cs 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 模式而不是直接使用 IDThe EnrollmentID property is the primary key; this entity uses the classnameID pattern instead of ID by itself. 对于生产数据模型,请选择一个模式并一直使用。For a production data model, choose one pattern and use it consistently. 本教程两个都使用,只是为了说明这两个模式都能使用。This tutorial uses both just to illustrate that both work. 使用不具有 classnameID 可以更轻松地实现某些类型的数据模型更改。Using ID without classname makes it easier to implement some kinds of data model changes.

Grade 属性为 enumThe Grade property is an enum. Grade 声明类型后的?表示 Grade 属性可以为 nullThe 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 属性是外键,其对应的导航属性为 StudentThe StudentID property is a foreign key, and the corresponding navigation property is Student. Enrollment 实体与一个 Student 实体相关联,因此该属性只包含一个 Student 实体。An Enrollment entity is associated with one Student entity, so the property contains a single Student entity.

CourseID 属性是外键,其对应的导航属性为 CourseThe 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>,EF Core 会将其视为外键。EF Core interprets a property as a foreign key if it's named <navigation property name><primary key property name>. 例如,StudentIDStudent 导航属性的外键,因为 Student 实体的主键为 IDFor example,StudentID is the foreign key for the Student navigation property, since the Student entity's primary key is ID. 还可以将外键属性命名为 <primary key property name>Foreign key properties can also be named <primary key property name>. 例如 CourseID,因为 Course 实体的主键为 CourseIDFor example, CourseID since the Course entity's primary key is CourseID.

Course 实体The Course entity

Course 实体关系图

使用以下代码创建 Models/Course.cs:Create Models/Course.cs 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 特性指定主键,而无需靠数据库生成。The DatabaseGenerated attribute allows the app to specify the primary key rather than having the database generate it.

生成项目以验证没有任何编译器错误。Build the project to validate that there are no compiler errors.

搭建“学生”页的基架Scaffold Student pages

本部分使用 ASP.NET Core 基架工具生成以下内容:In this section, you use the ASP.NET Core scaffolding tool to generate:

  • EF Core 上下文类。An EF Core context class. 上下文是为给定数据模型协调实体框架功能的主类。The context is the main class that coordinates Entity Framework functionality for a given data model. 它派生自 Microsoft.EntityFrameworkCore.DbContext 类。It derives from the Microsoft.EntityFrameworkCore.DbContext class.
  • Razor 页面,可处理 Student 实体的创建、读取、更新和删除 (CRUD) 操作。 pages that handle Create, Read, Update, and Delete (CRUD) operations for the Student entity.
  • 在“Pages”文件夹中创建“Students”文件夹 。Create a Students folder in the Pages folder.
  • 在“解决方案资源管理器”中,右键单击“Pages/Students”文件夹,然后选择“添加”>“新搭建基架的项目” 。In Solution Explorer, right-click the Pages/Students folder and select Add > New Scaffolded Item.
  • 在“添加基架”对话框中,依次选择“使用实体框架的 Razor Pages (CRUD)”>“添加”。In the Add Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > ADD.
  • 在“添加使用实体框架的 Razor Pages (CRUD)”对话框中:In the Add Razor Pages using Entity Framework (CRUD) dialog:
    • 在“模型类”下拉列表中,选择“Student (ContosoUniversity.Models)” 。In the Model class drop-down, select Student (ContosoUniversity.Models).
    • 在“数据上下文类”行中,选择 +(加号) 。In the Data context class row, select the + (plus) sign.
    • 将数据上下文名称从 ContosoUniversity.Models.ContosoUniversityContext 更改为 ContosoUniversity.Data.SchoolContext 。Change the data context name from ContosoUniversity.Models.ContosoUniversityContext to ContosoUniversity.Data.SchoolContext.
    • 选择“添加”。Select Add.

自动安装以下包:The following packages are automatically installed:

  • Microsoft.VisualStudio.Web.CodeGeneration.Design
  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.EntityFrameworkCore.Tools

如果对上述步骤有疑问,请生成项目并重试基架搭建步骤。If you have a problem with the preceding step, build the project and retry the scaffold step.

基架流程:The scaffolding process:

  • 在“Pages/Students”文件夹中创建 Razor 页面:Creates Razor pages in the Pages/Students folder:
    • Create.cshtml 和 Create.cshtml.cs Create.cshtml and Create.cshtml.cs
    • Delete.cshtml 和 Delete.cshtml.cs Delete.cshtml and Delete.cshtml.cs
    • Details.cshtml 和 Details.cshtml.cs Details.cshtml and Details.cshtml.cs
    • Edit.cshtml 和 Edit.cshtml.cs Edit.cshtml and Edit.cshtml.cs
    • Index.cshtml 和 Index.cshtml.cs Index.cshtml and Index.cshtml.cs
  • 创建 Data/SchoolContext.cs。Creates Data/SchoolContext.cs.
  • 将上下文添加到 Startup.cs 中的依赖项注入。Adds the context to dependency injection in Startup.cs.
  • 将数据库连接字符串添加到 appsettings.json。Adds a database connection string to appsettings.json.

数据库连接字符串Database connection string

连接字符串指定 SQL Server LocalDBThe connection string specifies SQL Server LocalDB.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext6;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

LocalDB 是轻型版本 SQL Server Express 数据库引擎,专门针对应用开发,而非生产使用。LocalDB is a lightweight version of the SQL Server Express Database Engine and is intended for app development, not production use. 默认情况下,LocalDB 会在 C:/Users/<user> 目录中创建 .mdf 文件。By default, LocalDB creates .mdf files in the C:/Users/<user> directory.

更新数据库上下文类Update the database context class

数据库上下文类是为给定数据模型协调 EF Core 功能的主类。The main class that coordinates EF Core functionality for a given data model is the database context class. 上下文派生自 Microsoft.EntityFrameworkCore.DbContextThe context is derived from Microsoft.EntityFrameworkCore.DbContext. 上下文指定数据模型中包含哪些实体。The context specifies which entities are included in the data model. 在此项目中将数据库上下文类命名为 SchoolContextIn this project, the class is named SchoolContext.

使用以下代码更新 SchoolContext.cs:Update SchoolContext.cs with the following code:

using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;

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

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

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

突出显示的代码为每个实体集创建 DbSet<TEntity> 属性。The highlighted code creates a DbSet<TEntity> property for each entity set. 在 EF Core 术语中:In EF Core terminology:

  • 实体集通常对应数据库表。An entity set typically corresponds to a database table.
  • 实体对应表中的行。An entity corresponds to a row in the table.

由于实体集包含多个实体,因此 DBSet 属性应为复数名称。Since an entity set contains multiple entities, the DBSet properties should be plural names. 由于基架工具创建了 Student DBSet,因此此步骤将其更改为复数 StudentsSince the scaffolding tool created aStudent DBSet, this step changes it to plural Students.

为了使 Razor Pages 代码与新的 DBSet 名称相匹配,请在整个项目中进行全局更改,将 _context.Student 更改为 _context.StudentsTo make the Razor Pages code match the new DBSet name, make a global change across the whole project of _context.Student to _context.Students. 更改发生 8 次。There are 8 occurrences.

生成项目以验证没有任何编译器错误。Build the project to verify there are no compiler errors.

Startup.csStartup.cs

ASP.NET Core 通过依赖关系注入进行生成。ASP.NET Core is built with dependency injection. 在应用程序启动过程通过依赖注入注册相关服务(例如 EF Core 数据库上下文)。Services (such as the EF Core database context) are registered with dependency injection during application startup. 需要这些服务(如 Razor 页面)的组件通过构造函数参数提供相应服务。Components that require these services (such as Razor Pages) are provided these services via constructor parameters. 本教程的后续部分介绍了用于获取数据库上下文实例的构造函数代码。The constructor code that gets a database context instance is shown later in the tutorial.

基架工具自动将上下文类注册到了依赖项注入容器。The scaffolding tool automatically registered the context class with the dependency injection container.

  • ConfigureServices 中,基架添加了突出显示的行:In ConfigureServices, the highlighted lines were added by the scaffolder:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    
        services.AddDbContext<SchoolContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("SchoolContext")));
    }
    

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

创建数据库Create the database

如果没有数据库,请更新 Program.cs 以创建数据库:Update Program.cs to create the database if it doesn't exist:

using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();

            CreateDbIfNotExists(host);

            host.Run();
        }

        private static void CreateDbIfNotExists(IHost host)
        {
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;

                try
                {
                    var context = services.GetRequiredService<SchoolContext>();
                    context.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

如果有上下文的数据库,则 EnsureCreated 方法不执行任何操作。The EnsureCreated method takes no action if a database for the context exists. 如果没有数据库,则它将创建数据库和架构。If no database exists, it creates the database and schema. EnsureCreated 启用以下工作流来处理数据模型更改:EnsureCreated enables the following workflow for handling data model changes:

  • 删除数据库。Delete the database. 任何现有数据丢失。Any existing data is lost.
  • 更改数据模型。Change the data model. 例如,添加 EmailAddress 字段。For example, add an EmailAddress field.
  • 运行应用。Run the app.
  • EnsureCreated 创建具有新架构的数据库。EnsureCreated creates a database with the new schema.

在无需保存数据的情况下,当架构快速发展时,此工作流在早期开发过程中表现良好。This workflow works well early in development when the schema is rapidly evolving, as long as you don't need to preserve data. 如果需要保存已输入数据库的数据,情况就有所不同了。The situation is different when data that has been entered into the database needs to be preserved. 如果是这种情况,请使用迁移。When that is the case, use migrations.

本系列教程的后续部分将删除 EnsureCreated 创建的数据库,转而使用迁移。Later in the tutorial series, you delete the database that was created by EnsureCreated and use migrations instead. 无法使用迁移更新 EnsureCreated 创建的数据库。A database that is created by EnsureCreated can't be updated by using migrations.

测试应用Test the app

  • 运行应用。Run the app.
  • 依次选择“学生”链接、“新建” 。Select the Students link and then Create New.
  • 测试“编辑”、“详细信息”和“删除”链接。Test the Edit, Details, and Delete links.

设定数据库种子Seed the database

EnsureCreated 方法将创建空数据库。The EnsureCreated method creates an empty database. 本节添加用测试数据填充数据库的代码。This section adds code that populates the database with test data.

使用以下代码创建 Data/DbInitializer.cs:Create Data/DbInitializer.cs with the following code:

using ContosoUniversity.Data;
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("2019-09-01")},
                new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
                new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
                new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
                new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
            };

            context.Students.AddRange(students);
            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}
            };

            context.Courses.AddRange(courses);
            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},
            };

            context.Enrollments.AddRange(enrollments);
            context.SaveChanges();
        }
    }
}

该代码会检查数据库中是否存在任何学生。The code checks if there are any students in the database. 如果不存在学生,它将向数据库添加测试数据。If there are no students, it adds test data to the database. 该代码使用数组创建测试数据而不是使用 List<T> 集合是为了优化性能。It creates the test data in arrays rather than List<T> collections to optimize performance.

  • 在 Program.cs 中,将 EnsureCreated 调用替换为 DbInitializer.Initialize 调用:In Program.cs, replace the EnsureCreated call with a DbInitializer.Initialize call:

    // context.Database.EnsureCreated();
    DbInitializer.Initialize(context);
    

如果应用正在运行,则停止应用,然后在包管理器控制台 (PMC) 中运行以下命令:Stop the app if it's running, and run the following command in the Package Manager Console (PMC):

Drop-Database
  • 重新启动应用。Restart the app.

  • 选择“学生”页查看已设定种子的数据。Select the Students page to see the seeded data.

查看数据库View the database

  • 从 Visual Studio 中的“视图”菜单打开 SQL Server 对象资源管理器 (SSOX) 。Open SQL Server Object Explorer (SSOX) from the View menu in Visual Studio.
  • 在 SSOX 中,依次选择“(localdb)\MSSQLLocalDB”>“数据库”>“SchoolContext-{GUID}”。In SSOX, select (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}. 数据库名称由之前提供的上下文名称以及短划线和 GUID 组成。The database name is generated from the context name you provided earlier plus a dash and a GUID.
  • 展开“表”节点。Expand the Tables node.
  • 右键单击 Student 表,然后单击“查看数据”,以查看创建的列和插入到表中的行 。Right-click the Student table and click View Data to see the columns created and the rows inserted into the table.
  • 右键单击 Student 表,然后单击“查看代码”查看 Student 模型如何映射到 Student 表架构 。Right-click the Student table and click View Code to see how the Student model maps to the Student table schema.

异步代码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 can handle more traffic without delays.

异步代码会在运行时引入少量开销。Asynchronous code does introduce a small amount of overhead at run time. 流量较低时,对性能的影响可以忽略不计,但流量较高时,潜在的性能改善非常显著。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 OnGetAsync()
{
    Students = await _context.Students.ToListAsync();
}
  • async 关键字让编译器执行以下操作:The async keyword tells the compiler to:
    • 为方法主体的各部分生成回调。Generate callbacks for parts of the method body.
    • 创建返回的 Task 对象。Create the Task object that's returned.
  • 返回类型 Task<T> 表示正在进行的工作。The Task<T> return type represents ongoing work.
  • 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.

编写使用 EF Core 的异步代码时需要注意的一些事项:Some things to be aware of when writing asynchronous code that uses EF Core:

  • 只有导致查询或发送数据库命令的语句才能以异步方式执行。Only statements that cause queries or commands to be sent to the database are executed asynchronously. 这包括 ToListAsyncSingleOrDefaultAsyncFirstOrDefaultAsyncSaveChangesAsyncThat includes ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync, and SaveChangesAsync. 不包括只会更改 IQueryable 的语句,例如 var students = context.Students.Where(s => s.LastName == "Davolio")It doesn't include statements that just change an IQueryable, such as var students = context.Students.Where(s => s.LastName == "Davolio").
  • EF Core 上下文并非线程安全:请勿尝试并行执行多个操作。An EF Core context isn't thread safe: don't try to do multiple operations in parallel.
  • 若要利用异步代码的性能优势,请验证在调用向数据库发送查询的 EF Core 方法时,库程序包(如用于分页)是否使用异步。To take advantage of the performance benefits of async code, verify that library packages (such as for paging) use async if they call EF Core methods that send queries to the database.

有关 .NET 中异步编程的详细信息,请参阅异步概述使用 Async 和 Await 的异步编程For more information about asynchronous programming in .NET, see Async Overview and Asynchronous programming with async and await.

后续步骤Next steps

Contoso University 示例 Web 应用演示了如何使用 Entity Framework (EF) Core 创建 ASP.NET Core Razor Pages 应用。The Contoso University sample web app demonstrates how to create an ASP.NET Core Razor Pages app using Entity Framework (EF) Core.

该示例应用是一个虚构的 Contoso University 的网站。The sample app is a web site for a fictional Contoso University. 其中包括学生录取、课程创建和讲师分配等功能。It includes functionality such as student admission, course creation, and instructor assignments. 本页是介绍如何构建 Contoso University 示例应用系列教程中的第一部分。This page is the first in a series of tutorials that explain how to build the Contoso University sample app.

下载或查看已完成的应用。Download or view the completed app. 下载说明Download instructions.

先决条件Prerequisites

Visual Studio 2019 包含以下工作负荷:Visual Studio 2019 with the following workloads:

  • ASP.NET 和 Web 开发ASP.NET and web development
  • .NET Core 跨平台开发.NET Core cross-platform development

熟悉 Razor PagesFamiliarity with Razor Pages. 新程序员在开始学习本系列之前,应先完成 Razor Pages 入门New programmers should complete Get started with Razor Pages before starting this series.

疑难解答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. 获取帮助的一个好方法是将问题发布到适用于 ASP.NET CoreEF CoreStackOverflow.comA good way to get help is by posting a question to StackOverflow.com for ASP.NET Core or EF Core.

Contoso University Web 应用The Contoso University web app

这些教程中所构建的应用是一个基本的大学网站。The app built in these tutorials is a basic university web site.

用户可以查看和更新学生、课程和讲师信息。Users can view and update student, course, and instructor information. 以下是在本教程中创建的几个屏幕。Here are a few of the screens created in the tutorial.

“学生索引”页

学生编辑页

此网站的 UI 样式与内置模板生成的 UI 样式类似。The UI style of this site is close to what's generated by the built-in templates. 教程的重点是 EF Core 和 Razor Pages,而非 UI。The tutorial focus is on EF Core with Razor Pages, not the UI.

创建 ContosoUniversity Razor Pages Web 应用Create the ContosoUniversity Razor Pages web app

  • 从 Visual Studio“文件”菜单中选择“新建”>“项目”。From the Visual Studio File menu, select New > Project.
  • 创建新的 ASP.NET Core Web 应用程序。Create a new ASP.NET Core Web Application. 将该项目命名为 ContosoUniversity 。Name the project ContosoUniversity. 务必将该项目命名为 ContosoUniversity,以便复制/粘贴代码时命名空间相匹配。It's important to name the project ContosoUniversity so the namespaces match when code is copy/pasted.
  • 在下拉列表中选择“ASP.NET Core 2.1”,然后选择“Web 应用程序” 。Select ASP.NET Core 2.1 in the dropdown, and then select Web Application.

有关上述步骤的图像,请参阅创建 Razor Web 应用For images of the preceding steps, see Create a Razor web app. 运行应用。Run the app.

设置网站样式Set up the site style

设置网站菜单、布局和主页时需作少量更改。A few changes set up the site menu, layout, and home page. 进行以下更改以更新 Pages/Shared/_Layout.cshtml:Update Pages/Shared/_Layout.cshtml with the following changes:

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

  • 添加菜单项 StudentsCoursesInstructors,和 Department,并删除 Contact菜单项。Add menu entries for Students, Courses, Instructors, and Departments, and delete the Contact menu entry.

突出显示所作更改。The changes are highlighted. (没有显示全部标记。)(All the markup is not displayed.)

<!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" />
        <link rel="stylesheet" href="~/css/site.css" />
    </environment>
    <environment exclude="Development">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/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" />
        <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
    </environment>
</head>
<body>
    <nav 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="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a asp-page="/Index" class="navbar-brand">Contoso University</a>
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li><a asp-page="/Index">Home</a></li>
                    <li><a asp-page="/About">About</a></li>
                    <li><a asp-page="/Students/Index">Students</a></li>
                    <li><a asp-page="/Courses/Index">Courses</a></li>
                    <li><a asp-page="/Instructors/Index">Instructors</a></li>
                    <li><a asp-page="/Departments/Index">Departments</a></li>
                </ul>
            </div>
        </div>
    </nav>

    <partial name="_CookieConsentPartial" />

    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; 2018 : Contoso University</p>
        </footer>
    </div>

    @*Remaining markup not shown for brevity.*@

在 Pages/Index.cshtml 中,将文件内容替换为以下代码,以将有关 ASP.NET 和 MVC 的文本替换为有关本应用的文本:In Pages/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 app:

@page
@model IndexModel
@{
    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 Razor Pages web app.
        </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.microsoft.com/aspnet/core/data/ef-rp/intro">
                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/dotnet/AspNetCore.Docs/tree/master/aspnetcore/data/ef-rp/intro/samples/">
                See project source code &raquo;
            </a>
        </p>
    </div>
</div>

创建数据模型Create the data model

创建 Contoso University 应用的实体类。Create entity classes for the Contoso University app. 从以下三个实体开始:Start with the following three entities:

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

StudentEnrollment 实体之间存在一对多关系。There's a one-to-many relationship between Student and Enrollment entities. CourseEnrollment 实体之间存在一对多关系。There's a one-to-many relationship between Course and Enrollment entities. 一名学生可以报名参加任意数量的课程。A student can enroll in any number of courses. 一门课程中可以包含任意数量的学生。A course can have any number of students enrolled in it.

以下部分将为这几个实体中的每一个实体创建一个类。In the following sections, a class for each one of these entities is created.

Student 实体The Student entity

Student 实体关系图

创建 Models 文件夹。Create a Models folder. 在 Models 文件夹中,使用以下代码创建一个名为 Student.cs 的类文件 :In the Models folder, create a class file named Student.cs 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 属性成为此类对应的数据库 (DB) 表的主键列。The ID property becomes the primary key column of the database (DB) table that corresponds to this class. 默认情况下,EF Core 将名为 IDclassnameID 的属性视为主键。By default, EF Core interprets a property that's named ID or classnameID as the primary key. classnameID 中,classname 为类名称。In classnameID, classname is the name of the class. 另一种自动识别的主键是上例中的 StudentIDThe alternative automatically recognized primary key is StudentID in the preceding example.

Enrollments 属性是导航属性The Enrollments property is a navigation property. 导航属性链接到与此实体相关的其他实体。Navigation properties link to other entities that are related to this entity. 在这种情况下,Student entityEnrollments 属性包含与该 Student 相关的所有 Enrollment 实体。In this case, the Enrollments property of a Student entity holds all of the Enrollment entities that are related to that Student. 例如,如果数据库中的 Student 行有两个相关的 Enrollment 行,则 Enrollments 导航属性包含这两个 Enrollment 实体。For example, if a Student row in the DB has two related Enrollment rows, the Enrollments navigation property contains those two Enrollment entities. 相关的 Enrollment 行是 StudentID 列中包含该学生的主键值的行。A related Enrollment row is a row that contains that student's primary key value in the StudentID column. 例如,假设 ID=1 的学生在 Enrollment 表中有两行。For example, suppose the student with ID=1 has two rows in the Enrollment table. Enrollment 表中有两行的 StudentID = 1。The Enrollment table has two rows with StudentID = 1. StudentIDEnrollment 表中的外键,用于指定 Student 表中的学生。StudentID is a foreign key in the Enrollment table that specifies the student in the Student table.

如果导航属性包含多个实体,则导航属性必须是列表类型,例如 ICollection<T>If a navigation property can hold multiple entities, the navigation property must be a list type, such as ICollection<T>. 可以指定 ICollection<T> 或诸如 List<T>HashSet<T> 的类型。ICollection<T> can be specified, or a type such as List<T> or HashSet<T>. 使用 ICollection<T> 时,EF Core 会默认创建 HashSet<T> 集合。When ICollection<T> is used, EF Core creates a HashSet<T> collection by default. 包含多个实体的导航属性来自于多对多和一对多关系。Navigation properties that hold multiple entities come from many-to-many and one-to-many relationships.

Enrollment 实体The Enrollment entity

Enrollment 实体关系图

在 Models 文件夹中,使用以下代码创建 Enrollment.cs :In the Models folder, create Enrollment.cs 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 属性为主键。The EnrollmentID property is the primary key. Student 实体使用的是 ID 模式,而本实体使用的是 classnameID 模式。This entity uses the classnameID pattern instead of ID like the Student entity. 通常情况下,开发者会选择一种模式并在整个数据模型中都使用该模式。Typically developers choose one pattern and use it throughout the data model. 下一个教程将介绍如何使用不带类名的 ID,以便更轻松地在数据模型中实现集成。In a later tutorial, using ID without classname is shown to make 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 属性是外键,其对应的导航属性为 StudentThe StudentID property is a foreign key, and the corresponding navigation property is Student. Enrollment 实体与一个 Student 实体相关联,因此该属性只包含一个 Student 实体。An Enrollment entity is associated with one Student entity, so the property contains a single Student entity. Student 实体与 Student.Enrollments 导航属性不同,后者包含多个 Enrollment 实体。The Student entity differs from the Student.Enrollments navigation property, which contains multiple Enrollment entities.

CourseID 属性是外键,其对应的导航属性为 CourseThe 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>,EF Core 会将其视为外键。EF Core interprets a property as a foreign key if it's named <navigation property name><primary key property name>. 例如 Student 导航属性的 StudentID,因为 Student 实体的主键为 IDFor example,StudentID for the Student navigation property, since the Student entity's primary key is ID. 还可以将外键属性命名为 <primary key property name>Foreign key properties can also be named <primary key property name>. 例如 CourseID,因为 Course 实体的主键为 CourseIDFor 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 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 特性指定主键,而无需靠数据库生成。The DatabaseGenerated attribute allows the app to specify the primary key rather than having the DB generate it.

为“学生”模型搭建基架Scaffold the student model

本部分将为“学生”模型搭建基架。In this section, the student model is scaffolded. 确切地说,基架工具将生成页面,用于对“学生”模型执行创建、读取、更新和删除 (CRUD) 操作。That is, the scaffolding tool produces pages for Create, Read, Update, and Delete (CRUD) operations for the student model.

  • 生成项目。Build the project.
  • 创建 Pages/Students 文件夹。Create the Pages/Students folder.
  • 在“解决方案资源管理器”中,右键单击“Pages/Students”文件夹 >“添加”>“新搭建基架的项目”。In Solution Explorer, right click on the Pages/Students folder > Add > New Scaffolded Item.
  • 在“添加基架”对话框中,依次选择“使用实体框架的 Razor Pages (CRUD)”>“添加”。In the Add Scaffold dialog, select Razor Pages using Entity Framework (CRUD) > ADD.

完成“添加使用实体框架的 Razor 页面 (CRUD)”对话框:Complete the Add Razor Pages using Entity Framework (CRUD) dialog:

  • 在“模型类”下拉列表中,选择“Student (ContosoUniversity.Models)” 。In the Model class drop-down, select Student (ContosoUniversity.Models).
  • 在“数据上下文类”行中,选择加号 (+) 并将生成的名称更改为 ContosoUniversity.Models.SchoolContext 。In the Data context class row, select the + (plus) sign and change the generated name to ContosoUniversity.Models.SchoolContext.
  • 在“数据上下文类”下拉列表中,选择“ContosoUniversity.Models.SchoolContext” In the Data context class drop-down, select ContosoUniversity.Models.SchoolContext
  • 选择“添加”。Select Add.

CRUD 对话框

如果对前面的步骤有疑问,请参阅搭建“电影”模型的基架See Scaffold the movie model if you have a problem with the preceding step.

搭建基架的过程会创建并更改以下文件:The scaffold process created and changed the following files:

创建的文件Files created

  • Pages/Students:“创建”、“删除”、“详细信息”、“编辑”、“索引”。Pages/Students Create, Delete, Details, Edit, Index.
  • Data/SchoolContext.csData/SchoolContext.cs

更新的文件File updates

  • Startup.cs:下一部分详细介绍对此文件所作的更改。Startup.cs : Changes to this file are detailed in the next section.
  • appsettings.json:添加用于连接到本地数据库的连接字符串。appsettings.json : The connection string used to connect to a local database is added.

检查通过依赖关系注入注册的上下文Examine the context registered with dependency injection

ASP.NET Core 通过依赖关系注入进行生成。ASP.NET Core is built with dependency injection. 服务(例如 EF Core 数据库上下文)在应用程序启动期间通过依赖关系注入进行注册。Services (such as the EF Core DB context) are registered with dependency injection during application startup. 需要这些服务(如 Razor 页面)的组件通过构造函数参数提供相应服务。Components that require these services (such as Razor Pages) are provided these services via constructor parameters. 本教程的后续部分介绍了用于获取数据库上下文实例的构造函数代码。The constructor code that gets a db context instance is shown later in the tutorial.

基架工具自动创建 DB 上下文并将其注册到依赖关系注入容器。The scaffolding tool automatically created a DB Context and registered it with the dependency injection container.

在 Startup.cs 中检查 ConfigureServices 方法。Examine the ConfigureServices method in Startup.cs. 基架添加了突出显示的行:The highlighted line was added by the scaffolder:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for 
        //non -essential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

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

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

更新 mainUpdate main

在 Program.cs 中,修改 Main 方法以执行以下操作:In Program.cs, modify the Main method to do the following:

  • 从依赖关系注入容器获取数据库上下文实例。Get a DB context instance from the dependency injection container.
  • 调用 EnsureCreatedCall the EnsureCreated.
  • EnsureCreated 方法完成时释放上下文。Dispose the context when the EnsureCreated method completes.

下面的代码显示更新后的 Program.cs 文件。The following code shows the updated Program.cs file.

using ContosoUniversity.Models;                   // SchoolContext
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;   // CreateScope
using Microsoft.Extensions.Logging;
using System;

namespace ContosoUniversity
{
    public class Program
    {
        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>();
                    context.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "An error occurred creating the DB.");
                }
            }

            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

EnsureCreated 确保存在上下文数据库。EnsureCreated ensures that the database for the context exists. 如果存在,则不需要任何操作。If it exists, no action is taken. 如果不存在,则会创建数据库及其所有架构。If it does not exist, then the database and all its schema are created. EnsureCreated 不使用迁移创建数据库。EnsureCreated does not use migrations to create the database. 使用 EnsureCreated 创建的数据库稍后无法使用迁移更新。A database that is created with EnsureCreated cannot be later updated using migrations.

启动应用时会调用 EnsureCreated,以进行以下工作流:EnsureCreated is called on app start, which allows the following work flow:

  • 删除数据库。Delete the DB.
  • 更改数据库架构(例如添加一个 EmailAddress 字段)。Change the DB schema (for example, add an EmailAddress field).
  • 运行应用。Run the app.
  • EnsureCreated 创建一个带有 EmailAddress 列的数据库。EnsureCreated creates a DB with theEmailAddress column.

架构快速演变时,在开发初期使用 EnsureCreated 很方便。EnsureCreated is convenient early in development when the schema is rapidly evolving. 本教程后面将删除 DB 并使用迁移。Later in the tutorial the DB is deleted and migrations are used.

测试应用Test the app

运行应用并接受 cookie 策略。Run the app and accept the cookie policy. 此应用不保留个人信息。This app doesn't keep personal information. 有关 cookie 策略的信息,请参阅欧盟一般数据保护条例 (GDPR) 支持You can read about the cookie policy at EU General Data Protection Regulation (GDPR) support.

  • 依次选择“学生”链接、“新建” 。Select the Students link and then Create New.
  • 测试“编辑”、“详细信息”和“删除”链接。Test the Edit, Details, and Delete links.

检查 SchoolContext DB 上下文Examine the SchoolContext DB context

数据库上下文类是为给定数据模型协调 EF Core 功能的主类。The main class that coordinates EF Core functionality for a given data model is the DB context class. 数据上下文派生自 Microsoft.EntityFrameworkCore.DbContextThe data context is derived from Microsoft.EntityFrameworkCore.DbContext. 数据上下文指定数据模型中包含哪些实体。The data context specifies which entities are included in the data model. 在此项目中将数据库上下文类命名为 SchoolContextIn this project, the class is named SchoolContext.

使用以下代码更新 SchoolContext.cs:Update SchoolContext.cs with the following code:

using Microsoft.EntityFrameworkCore;

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

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

突出显示的代码为每个实体集创建 DbSet<TEntity> 属性。The highlighted code creates a DbSet<TEntity> property for each entity set. 在 EF Core 术语中:In EF Core terminology:

  • 实体集通常对应一个数据库表。An entity set typically corresponds to a DB table.
  • 实体对应表中的行。An entity corresponds to a row in the table.

可以省略 DbSet<Enrollment>DbSet<Course>DbSet<Enrollment> and DbSet<Course> could be omitted. EF Core 隐式包含了它们,因为 Student 实体引用 Enrollment 实体,而 Enrollment 实体引用 Course 实体。EF Core includes them implicitly because the Student entity references the Enrollment entity, and the Enrollment entity references the Course entity. 在本教程中,将 DbSet<Enrollment>DbSet<Course> 保留在 SchoolContext 中。For this tutorial, keep DbSet<Enrollment> and DbSet<Course> in the SchoolContext.

SQL Server Express LocalDBSQL Server Express LocalDB

连接字符串指定 SQL Server LocalDBThe connection string specifies SQL Server LocalDB. LocalDB 是轻型版本 SQL Server Express 数据库引擎,专门针对应用开发,而非生产使用。LocalDB is a lightweight version of the SQL Server Express Database Engine and is intended for app 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 DB files in the C:/Users/<user> directory.

添加代码,以使用测试数据初始化该数据库Add code to initialize the DB with test data

EF Core 会创建一个空的数据库。EF Core creates an empty DB. 本部分中编写了 Initialize 方法来使用测试数据填充该数据库。In this section, an Initialize method is written to populate it with test data.

在 Data 文件夹中,新建一个名为 DbInitializer.cs 的类文件,并添加以下代码 :In the Data folder, create a new class file named DbInitializer.cs and add the following code:

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

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

            // Look for any students.
            if (context.Student.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.Student.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.Course.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.Enrollment.Add(e);
            }
            context.SaveChanges();
        }
    }
}

注意:上面的代码对命名空间使用 Models (namespace ContosoUniversity.Models),而不是 DataNote: The preceding code uses Models for the namespace (namespace ContosoUniversity.Models) rather than Data. Models 与基架生成的代码一致。Models is consistent with the scaffolder-generated code. 有关详细信息,请参阅此 GitHub 基架问题For more information, see this GitHub scaffolding issue.

该代码会检查数据库中是否存在任何学生。The code checks if there are any students in the DB. 如果 DB 中没有任何学生,则会使用测试数据初始化该 DB。If there are no students in the DB, the DB is initialized with test data. 代码中使用数组存放测试数据而不是使用 List<T> 集合是为了优化性能。It loads test data into arrays rather than List<T> collections to optimize performance.

EnsureCreated 方法自动为数据库上下文创建数据库。The EnsureCreated method automatically creates the DB for the DB context. 如果数据库已存在,则返回 EnsureCreated,并且不修改数据库。If the DB exists, EnsureCreated returns without modifying the DB.

在 Program.cs 中,将 Main 方法修改为调用 InitializeIn Program.cs, modify the Main method to call Initialize:

public class Program
{
    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>();
                // using ContosoUniversity.Data; 
                DbInitializer.Initialize(context);
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred creating the DB.");
            }
        }

        host.Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

如果应用正在运行,则停止应用,然后在包管理器控制台 (PMC) 中运行以下命令:Stop the app if it's running, and run the following command in the Package Manager Console (PMC):

Drop-Database

查看数据库View the DB

数据库名称由之前提供的上下文名称以及短划线和 GUID 组成。The database name is generated from the context name you provided earlier plus a dash and a GUID. 因此,数据库名称为“SchoolContext-{GUID}”。Thus, the database name will be "SchoolContext-{GUID}". GUID 因用户而异。The GUID will be different for each user. 从 Visual Studio 中的“视图”菜单打开 SQL Server 对象资源管理器 (SSOX) 。Open SQL Server Object Explorer (SSOX) from the View menu in Visual Studio. 在 SSOX 中,依次单击“(localdb)\MSSQLLocalDB”>“数据库”>“SchoolContext-{GUID}”。In SSOX, click (localdb)\MSSQLLocalDB > Databases > SchoolContext-{GUID}.

展开“表”节点。Expand the Tables node.

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

异步代码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. 流量较低时,对性能的影响可以忽略不计,但流量较高时,潜在的性能改善非常显著。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 OnGetAsync()
{
    Student = await _context.Student.ToListAsync();
}
  • async 关键字让编译器执行以下操作:The async keyword tells the compiler to:

    • 为方法主体的各部分生成回调。Generate callbacks for parts of the method body.
    • 自动创建返回的 Task 对象。Automatically create the Task object that's returned. 有关详细信息,请参阅任务返回类型For more information, see Task Return Type.
  • 隐式返回类型 Task 表示正在进行的工作。The implicit return type Task represents ongoing work.

  • 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.

编写使用 EF Core 的异步代码时需要注意的一些事项:Some things to be aware of when writing asynchronous code that uses EF Core:

  • 只会异步执行导致查询或命令被发送到数据库的语句。Only statements that cause queries or commands to be sent to the DB are executed asynchronously. 这包括 ToListAsyncSingleOrDefaultAsyncFirstOrDefaultAsyncSaveChangesAsyncThat includes, ToListAsync, SingleOrDefaultAsync, FirstOrDefaultAsync, and SaveChangesAsync. 不包括只会更改 IQueryable 的语句,例如 var students = context.Students.Where(s => s.LastName == "Davolio")It doesn't include statements that just change an IQueryable, such as var students = context.Students.Where(s => s.LastName == "Davolio").
  • EF Core 上下文并非线程安全:请勿尝试并行执行多个操作。An EF Core context isn't thread safe: don't try to do multiple operations in parallel.
  • 若要利用异步代码的性能优势,请验证在调用向数据库发送查询的 EF Core 方法时,库程序包(如用于分页)是否使用异步。To take advantage of the performance benefits of async code, verify that library packages (such as for paging) use async if they call EF Core methods that send queries to the DB.

有关 .NET 中异步编程的详细信息,请参阅异步概述使用 Async 和 Await 的异步编程For more information about asynchronous programming in .NET, see Async Overview and Asynchronous programming with async and await.

下一个教程将介绍基本的 CRUD(创建、读取、更新、删除)操作。In the next tutorial, basic CRUD (create, read, update, delete) operations are examined.

其他资源Additional resources