入门

教程:在 ASP.NET MVC Web 应用中使用 EF Core 入门

本教程没有升级至 ASP.NET Core 3.0 。 Razor Pages 版本已更新。 针对本教程的 ASP.NET Core 3.0 及更高版本的大多数代码更改:

有关此版本的更新时间的详细信息,请参阅此 GitHub 问题

本教程介绍具有控制器和视图的 ASP.NET Core MVC 和 Entity Framework Core。 Razor Pages 是 ASP.NET Core 2.0 中引入的替代编程模型。 对于新的开发,我们建议在具有控制器和视图的 MVC 上使用 Razor Pages。 提供了本教程的 Razor Pages 版本。 每个教程涵盖其他教程没有的一些资料:

此 MVC 教程中包含但 Razor Pages 教程中没有的某些内容:

  • 在数据模型中实现继承
  • 执行原始 SQL 查询
  • 使用动态 LINQ 简化代码

Razor Pages 教程中包含但此 MVC 教程中没有的某些内容:

  • 使用 Select 方法加载相关数据
  • 可用于 ASP.NET Core 3.0 的版本

Contoso University 示例 Web 应用程序演示如何使用 Entity Framework (EF) Core 2.2 和 Visual Studio 2017 或 2019 创建 ASP.NET Core 2.2 MVC Web 应用程序。

示例应用程序供一个虚构的 Contoso 大学网站使用。 它包括诸如学生入学、 课程创建和导师分配等功能。 这是一系列教程中的第一个,这一系列教程主要展示了如何从零开始构建 Contoso 大学示例应用程序。

在本教程中,你将了解:

  • 创建 ASP.NET Core MVC Web 应用
  • 设置网站样式
  • 了解 EF Core NuGet 包
  • 创建数据模型
  • 创建数据库上下文
  • 为依赖关系注入注册上下文
  • 使用测试数据初始化数据库
  • 创建控制器和视图
  • 查看数据库

先决条件

疑难解答

如果遇到无法解决的问题,可以通过与 已完成的项目对比代码来查找解决方案。 常见错误以及对应的解决方案,请参阅 最新教程中的故障排除 如果没有找到遇到的问题的解决方案,可以将问题发布到StackOverflow.com 的 ASP.NET CoreEF Core 版块。

提示

这是一系列一共有十个教程,其中每个都是在前面教程已完成的基础上继续。 请考虑在完成每一个教程后保存项目的副本。 之后如果遇到问题,你可以从保存的副本中开始寻找问题,而不是从头开始。

Contoso University Web 应用

你将在这些教程中学习构建一个简单的大学网站的应用程序。

用户可以查看和更新学生、 课程和教师信息。 以下是一些你即将创建的页面。

“学生索引”页

学生编辑页

创建 Web 应用

  • 打开 Visual Studio。

  • 从“文件”菜单中选择“新建”>“项目” 。

  • 从左窗格中依次选择“已安装”>“Visual C#”>“Web” 。

  • 选择“ASP.NET Core Web 应用程序”项目模板 。

  • 输入“ContosoUniversity”作为名称,然后单击“确定” 。

    “新建项目”对话框

  • 等待“新建 ASP.NET Core Web 应用程序”对话框显示出来 。

  • 选择“.NET Core”、“ASP.NET Core 2.2”和“Web 应用程序(模型-视图-控制器)”模板 。

  • 请确保 身份验证 设置为 不进行身份验证

  • 选择“确定”

    新的 ASP.NET Core 项目对话框

设置网站样式

通过几个简单的更改设置站点菜单、 布局和主页。

打开 Views/Shared/_Layout.cshtml 并进行以下更改 :

  • 将文件中的"ContosoUniversity"更改为"Contoso University"。 需要更改三个地方。

  • 添加“关于” 、“学生” 、“课程” “讲师” 和“院系” 的菜单项,并删除“隐私” 菜单项。

突出显示所作更改。

<!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 的内容替换为有关此应用程序的内容:

@{
    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/dotnet/AspNetCore.Docs/tree/master/aspnetcore/data/ef-mvc/intro/samples/cu-final">See project source code &raquo;</a></p>
    </div>
</div>

按 CTRL + F5 来运行该项目或从菜单选择 调试 > 开始执行(不调试) 你会看到首页,以及通过这个教程创建的页对应的选项卡。

Contoso University 主页

关于 EF Core NuGet 包

若要为项目添加 EF Core 支持,需要安装相应的数据库驱动包。 本教程使用 SQL Server,相关驱动包Microsoft.EntityFrameworkCore.SqlServer 此包包含在 Microsoft.AspNetCore.App 元包中,因此无需引用该包。

EF SQL Server 包和其依赖项(Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Relational)一起提供 EF 的运行时支持。 你将在之后的 迁移 教程中学习添加工具包。

有关其他可用于 EF Core 的数据库驱动的信息,请参阅 数据库驱动

创建数据模型

接下来你将创建 Contoso 大学应用程序的实体类。 你将从以下三个实体类开始。

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

StudentEnrollment实体之间是一对多的关系,CourseEnrollment 实体之间也是一个对多的关系。 换而言之,一名学生可以修读任意数量的课程, 并且某一课程可以被任意数量的学生修读。

接下来,你将创建与这些实体对应的类。

Student 实体

Student 实体关系图

Models 文件夹中,创建一个名为 Student.cs 的类文件并且将模板代码替换为以下代码。

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 属性将成为对应于此类的数据库表中的主键。 默认情况下,EF 将会将名为 IDclassnameID 的属性解析为主键。

Enrollments 属性是导航属性 导航属性中包含与此实体相关的其他实体。 在这个案例下,Student entity 中的 Enrollments 属性会保留所有与 Student 实体相关的 Enrollment 换而言之,如果在数据库中有两行描述同一个学生的修读情况 (两行的 StudentID 值相同,而且 StudentID 作为外键和某位学生的主键值相同), Student 实体的 Enrollments 导航属性将包含那两个 Enrollment 实体。

如果导航属性可以具有多个实体 (如多对多或一对多关系),那么导航属性的类型必须是可以添加、 删除和更新条目的容器,如 ICollection<T> 你可以指定 ICollection<T> 或实现该接口类型,如 List<T>HashSet<T> 如果指定 ICollection<T>,EF在默认情况下创建 HashSet<T> 集合。

Enrollment 实体

修读实体关系图

Models 文件夹中,创建 Enrollment.cs 并且用以下代码替换现有代码:

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 实体那样直接使用 ID 通常情况下,你选择一个主键模式,并在你的数据模型自始至终使用这种模式。 在这里,使用了两种不同的模式只是为了说明你可以使用任一模式来指定主键。 后面的教程,你将了解到使用ID这种模式可以更轻松地在数据模型之间实现继承。

Grade 属性是 enum Grade 声明类型后的?表示 Grade 属性可以为 null。 评级为 null 和评级为零是有区别的 --null 意味着评级未知或者尚未分配。

StudentID 属性是一个外键,Student 是与其且对应的导航属性。 Enrollment 实体与一个 Student 实体相关联,因此该属性只包含单个 Student 实体 (与前面所看到的 Student.Enrollments 导航属性不同后,Student中可以容纳多个 Enrollment 实体)。

CourseID 属性是一个外键, Course 是与其对应的导航属性。 Enrollment 实体与一个 Course 实体相关联。

如果一个属性名为 <navigation property name><primary key property name>,Entity Framework 就会将这个属性解析为外键属性(例如, Student 实体的主键是IDStudentEnrollment的导航属性所以Enrollment实体中 StudentID 会被解析为外键)。 此外还可以将需要解析为外键的属性命名为 <primary key property name> (例如,CourseID 由于 Course 实体的主键所以 CourseID 也被解析为外键)。

Course 实体

Course 实体关系图

Models 文件夹中,创建 Course.cs 并且用以下代码替换现有代码:

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 属性是导航属性。 一个 Course 体可以与任意数量的 Enrollment 实体相关。

我们在本系列 后面的教程 中会有更多有关 DatabaseGenerated 特性的例子。 简单来说,此特性让你能自行指定主键,而不是让数据库自动指定主键。

创建数据库上下文

使得给定的数据模型与 Entity Framework 功能相协调的主类是数据库上下文类。 可以通过继承 Microsoft.EntityFrameworkCore.DbContext 类的方式创建此类。 在该类中你可以指定数据模型中包含哪些实体。 你还可以定义某些 Entity Framework 行为。 在此项目中将数据库上下文类命名为 SchoolContext

在项目文件夹中,创建名为的文件夹 Data

Data 文件夹创建名为 SchoolContext.cs 的类文件,并将模板代码替换为以下代码:

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 属性。 在 Entity Framework 中,实体集通常与数据表相对应,具体实体与表中的行相对应。

在这里可以省略 DbSet<Enrollment>DbSet<Course> 语句,实现的功能没有任何改变。 Entity Framework 会隐式包含这两个实体因为 Student 实体引用了 Enrollment 实体、Enrollment 实体引用了 Course 实体。

当数据库创建完成后, EF 创建一系列数据表,表名默认和 DbSet 属性名相同。 集合属性的名称一般使用复数形式,但不同的开发人员的命名习惯可能不一样,开发人员根据自己的情况确定是否使用复数形式。 在定义 DbSet 属性的代码之后,添加下面高亮代码,对 DbContext 指定单数的表名来覆盖默认的表名。 此教程在最后一个 DbSet 属性之后,添加以下高亮显示的代码。

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");
        }
    }
}

注册 SchoolContext

ASP.NET Core 默认实现 依赖注入 在应用程序启动过程通过依赖注入注册相关服务 (例如 EF 数据库上下文)。 需要这些服务的组件 (如 MVC 控制器) 可以通过向构造函数添加相关参数来获得对应服务。 在本教程后面你将看到控制器构造函数的代码,就是通过上述方式获得上下文实例。

若要将 SchoolContext 注册为一种服务,打开 Startup.cs ,并将高亮代码添加到 ConfigureServices 方法中。

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 中的一个方法将数据库连接字符串在配置文件中的名称传递给上下文对象。 进行本地开发时, ASP.NET Core 配置系统appsettings.json 文件中读取数据库连接字符串。

添加 using 语句引用 ContosoUniversity.DataMicrosoft.EntityFrameworkCore 命名空间,然后生成项目。

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

打开 appsettings.json 文件,并如以下示例所示添加连接字符串 。

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

SQL Server Express LocalDB

数据库连接字符串指定使用 SQL Server LocalDB 数据库。 LocalDB 是 SQL Server Express 数据库引擎的轻量级版本,用于应用程序开发,不在生产环境中使用。 LocalDB 作为按需启动并在用户模式下运行的轻量级数据库没有复杂的配置。 默认情况下, LocalDB 在 C:/Users/<user> 目录下创建 .mdf 数据库文件。

使用测试数据初始化数据库

Entity Framework 已经为你创建了一个空数据库。 在本部分中,你将编写一个方法用于向数据库填充测试数据,该方法会在数据库创建完成之后执行。

此处将使用 EnsureCreated 方法来自动创建数据库。 后面的教程 你将了解如何通过使用 Code First Migration 来更改而不是删除并重新创建数据库来处理模型更改。

Data 文件夹中,创建名为的新类文件 DbInitializer.cs 并且将模板代码替换为以下代码,使得在需要时能创建数据库并向其填充测试数据。

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();
        }
    }
}

这段代码首先检查是否有学生数据在数据库中,如果没有的话,就可以假定数据库是新建的,然后使用测试数据进行填充。 代码中使用数组存放测试数据而不是使用 List<T> 集合是为了优化性能。

Program.cs,修改 Main 方法,使得在应用程序启动时能执行以下操作:

  • 从依赖注入容器中获取数据库上下文实例。
  • 调用 seed 方法,将上下文传递给它。
  • Seed 方法完成此操作时释放上下文。
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 语句:

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

在旧版教程中,你可能会在 Startup.cs 中的 Configure 方法看到类似的代码。 我们建议你只在为了设置请求管道时使用 Configure 方法。 将应用程序启动代码放入 Main 方法。

现在首次运行该应用程序,创建数据库并使用测试数据作为种子数据。 每当你更改数据模型时,可以删除数据库、 更新你的 Initialize 方法,然后使用上述方式更新新数据库。 在之后的教程中,你将了解如何在数据模型更改时,只需修改数据库而无需删除重建数据库。

创建控制器和视图

接下来,将使用 Visual Studio 中的基架引擎添加一个 MVC 控制器,以及使用 EF 来查询和保存数据的视图。

CRUD 操作方法和视图的自动创建被称为基架。 基架与代码生成不同,基架的代码是一个起点,可以修改基架以满足自己需求,而你通常无需修改生成的代码。 当你需要自定义生成代码时,可使用一部分类或需求发生变化时重新生成代码。

  • 右键单击 解决方案资源管理器 中的 Controllers 文件夹选择 添加 > 新搭建基架的项目

  • 在“添加基架”对话框中 :

    • 选择 视图使用 Entity Framework 的 MVC 控制器

    • 单击 添加 随即将显示“使用 Entity Framework 添加包含视图的 MVC 控制器” 对话框。

      构架 Student

    • 模型类 选择 Student

    • 在“数据上下文类”中选择 SchoolContext 。

    • 使用 StudentsController 作为默认名称。

    • 单击 添加

    当你单击 添加 后,Visual Studio 基架引擎创建 StudentsController.cs 文件和一组对应于控制器的视图 ( .cshtml 文件) 。

(如果你之前手动创建数据库上下文,基架引擎还可以自动创建。 你可以在 添加控制器 对话框中单击右侧的加号框 数据上下文类 来指定在一个新上下文类。 然后,Visual Studio 将创建你的 DbContext,控制器和视图类。)

注意控制器将 SchoolContext 作为构造函数参数。

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

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

ASP.NET Core 依赖关系注入负责将 SchoolContext 实例传递到控制器。 在前面的教程中已经通过修改 Startup.cs 文件来配置注入规则。

控制器包含 Index 操作方法,用于显示数据库中的所有学生。 该方法从学生实体集中获取学生列表,学生实体集则是通过读取数据库上下文实例中的 Students 属性获得:

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

本教程后面部分将介绍此代码中的异步编程元素。

Views/Students/Index.cshtml 视图使用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 来运行该项目,或从菜单选择 调试 > 开始执行(不调试)

单击学生选项卡以查看 DbInitializer.Initialize 插入的测试的数据。 你将看到 Students 选项卡链接在页的顶部或在单击右上角后的导航图标中,具体显示在哪里取决于浏览器窗口宽度。

Contoso University 主页宽窄

“学生索引”页

查看数据库

当你启动了应用程序,DbInitializer.Initialize 方法调用 EnsureCreated EF 没有检测到相关数据库,因此自己创建了一个,接着 Initialize 方法的其余代码向数据库中填充数据。 你可以使用 Visual Studio 中的 SQL Server 对象资源管理器 (SSOX) 查看数据库。

关闭浏览器。

如果 SSOX 窗口尚未打开,请从Visual Studio 中的视图 菜单中选择。

在 SSOX 中,单击 (localdb) \MSSQLLocalDB > 数据库,然后单击和 appsettings.json 文件中的连接字符串对应的数据库。

展开“表”节点,查看数据库中的表 。

SSOX 中的表

右键单击 Student 表,然后单击 查看数据,即可查看已创建的列和已插入到表的行。

SSOX 中的 Student 表

.mdf 和 .ldf 数据库文件位于 C:\Users\<yourusername> 文件夹中 。

因为调用 EnsureCreated 的初始化方法在启动应用程序时才运行,所以在这之前你可以更改 Student 类、 删除数据库、 再运行一次应用程序,这时候数据库将自动重新创建,以匹配所做的更改。 例如,如果向 Student 类添加 EmailAddress 属性,重新的创建表中会有 EmailAddress 列。

约定

由于 Entity Framwork 有一定的约束条件,你只需要按规则编写很少的代码就能够创建一个完整的数据库。

  • DbSet 类型的属性用作表名。 如果实体未被 DbSet 属性引用,实体类名称用作表名称。

  • 使用实体属性名作为列名。

  • 以 ID 或 classnameID 命名的实体属性被视为主键属性。

  • 如果属性名为 <导航属性名><主键属性名>(例如,StudentID 对应 Student 导航属性,因为 Student 实体的主键是 ID),其将被解释为外键属性。 此外还可以将外键属性仅命名为 <主键属性名>(例如 EnrollmentID,因为 Enrollment 实体的主键为 EnrollmentID) 。

约定行为可以重写。 例如,本教程前半部分显式指定表名称。 本系列 后面教程 则设置列名称并将任何属性设置为主键或外键。

异步代码

异步编程是 ASP.NET Core 和 EF Core 的默认模式。

Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。 使用同步代码时,可能会出现多个线程被占用但不能执行任何操作的情况,因为它们正在等待 I/O 完成。 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。 因此,异步代码使得服务器更有效地使用资源,并且该服务器可以无延迟地处理更多流量。

异步代码在运行时,会引入的少量开销,在低流量时对性能的影响可以忽略不计,但在针对高流量情况下潜在的性能提升是可观的。

在以下代码中,async 关键字、Task<T> 返回值、await 关键字和 ToListAsync 方法让代码异步执行。

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • async 关键字用于告知编译器该方法主体将生成回调并自动创建 Task<IActionResult> 返回对象。

  • 返回类型 Task<IActionResult> 表示正在进行的工作返回的结果为 IActionResult 类型。

  • await 关键字会使得编译器将方法拆分为两个部分。 第一部分是以异步方式结束已启动的操作。 第二部分是当操作完成时注入调用回调方法的地方。

  • ToListAsyncToList 方法的的异步扩展版本。

使用 Entity Framework 编写异步代码时的一些注意事项:

  • 只有导致查询或发送数据库命令的语句才能以异步方式执行。 包括 ToListAsyncSingleOrDefaultAsync,和 SaveChangesAsync 不包括只需更改 IQueryable 的语句,如 var students = context.Students.Where(s => s.LastName == "Davolio")

  • EF 上下文是线程不安全的: 请勿尝试并行执行多个操作。 当调用异步 EF 方法时,始终使用 await 关键字。

  • 如果你想要利用异步代码的性能优势,请确保你所使用的任何库和包在它们调用导致 Entity Framework 数据库查询方法时也使用异步。

有关在 .NET 异步编程的详细信息,请参阅 异步概述

获取代码

下载或查看已完成的应用程序。

后续步骤

在本教程中,你将了解:

  • 已创建 ASP.NET Core MVC Web 应用
  • 设置网站样式
  • 已了解 EF Core NuGet 包
  • 已创建数据模型
  • 已创建数据库上下文
  • 已注册 SchoolContext
  • 已使用测试数据初始化数据库
  • 已创建控制器和视图
  • 已查看数据库

在下一个教程中,你将学习如何执行基本的 CRUD (创建、 读取、 更新、 删除) 操作。

请继续阅读下一篇教程,了解如何执行基本的 CRUD(创建、读取、更新、删除)操作。