作者:Tom Dykstra和Rick Anderson
此处提供了本教程的 Razor 页版本。 Razor 页版本更容易体现和覆盖 EF 的功能。 我们建议你学习本教程的 Razor 页版本。
Contoso 大学示例 web 应用程序演示如何使用 Entity Framework (EF) Core 2.0 和 Visual Studio 2017 创建 ASP.NET Core 2.0 MVC web 应用程序。
示例应用程序供一个虚构的 Contoso 大学网站使用。 它包括诸如学生入学、 课程创建和导师分配等功能。 这是一系列教程中的第一个,这一系列教程主要展示了如何从零开始构建 Contoso 大学示例应用程序。
下载或查看已完成的应用程序。
EF Core 2.0 是 EF 的最新版本,但还没有包括 EF 6.x 的所有功能 。 有关如何在 EF 6.x 和 EF Core 之间选择,请参阅EF Core vs. EF6.x。 如果你选择使用 EF 6.x,请参阅本系列教程的上一个版本。
- 本教程的 ASP.NET Core 1.1 版本,请参阅本教程中 VS 2017 Update 2 版本的 PDF 文档。
- 有关本教程的 Visual Studio 2015 版本,请参阅 ASP.NET Core 文档 VS 2015 版本的 PDF 文档。
准备
安装以下组件:
- .NET Core 2.0.0 SDK 或更高版本。
- 已安装 ASP.NET 和 Web 开发工作负载的 Visual Studio 2017 15.3 版或更高版本。
疑难解答
如果你遇到无法解决的问题,可以通过比较已完成的项目查找解决方案。常见错误以及对应的解决方案,请参阅最新教程中的故障排除。 如果没有找到遇到的问题的解决方案你可以将问题发布到StackOverflow.com 的 ASP.NET Core或EF Core版块。
这是一系列一共有十个教程,其中每个都是在前面教程已完成的基础上继续。请考虑在完成每一个教程后保存项目的副本。之后如果遇到问题,你可以从保存的副本中开始寻找问题,而不是从头开始。
Contoso 大学 web 应用程序
你将在这些教程中学习构建一个简单的大学网站的应用程序。
用户可以查看和更新学生、 课程和教师信息。 以下是一些你即将创建的页面。
本教程主要关注于如何使用 Entity Framework , 所以此站点的UI样式都是直接套用内置的模板。
创建 ASP.NET Core MVC web 应用程序
打开 Visual Studio 并创建一个新 ASP.NET Core C# web 项目名为”ContosoUniversity”。
从文件菜单上,选择新建 > 项目。
从左窗格中,选择已安装 > Visual C# > Web。
选择“ASP.NET Core Web 应用程序”项目模板。
-
输入ContosoUniversity作为名称,然后单击确定。
等待新 ASP.NET Core Web 应用程序 (.NET Core)显示对话框
-
选择ASP.NET Core 2.0和Web 应用程序 (模型-视图-控制器)模板。
注意:本教程需要安装 ASP.NET Core 2.0 和 EF Core 2.0 或更高版本-请确保ASP.NET Core 1.1未选中。
请确保身份验证设置为不进行身份验证。
-
单击“确定”
设置站点样式
通过几个简单的更改设置站点菜单、 布局和主页。
打开Views/Shared/_Layout.cshtml并进行以下更改:
将文件中的”ContosoUniversity”更改为”Contoso University”。 需要更改三个地方。
添加菜单项Students,Courses,Instructors,和Department,并删除Contact菜单项。
高亮代码显示所作的变化
@ViewData["Title"] - Contoso University
@RenderBody()
@RenderSection("Scripts", required: false)
在Views/Home/Index.cshtml,将文件的内容替换为以下代码以将有关 ASP.NET 和 MVC 的内容替换为有关此应用程序的内容:
@{
ViewData["Title"] = "Home Page";
}
Contoso University
Welcome to Contoso University
Contoso University is a sample application that
demonstrates how to use Entity Framework Core in an
ASP.NET Core MVC web application.
Build it from scratch
You can build the application by following the steps in a series of tutorials.
按 CTRL + F5 来运行该项目或从菜单选择调试 > 开始执行不调试。 你会看到首页和将通过这个教程创建的页对应的选项卡。
Entity Framework Core NuGet 包
若要为项目添加 EF Core 支持,需要安装相应的数据库驱动包。 本教程使用 SQL Server,相关驱动包Microsoft.EntityFrameworkCore.SqlServer。 该包包含在Microsoft.AspNetCore.All 包中,因此不需要手动安装。
此包和其依赖项 (Microsoft.EntityFrameworkCore
和Microsoft.EntityFrameworkCore.Relational
) 一起提供 EF 的运行时支持。 你将在之后的迁移教程中学习添加工具包。
有关其他可用于 EF Core 的数据库驱动的信息,请参阅数据库驱动。
创建数据模型
接下来你将创建 Contoso 大学应用程序的实体类。 你将从以下三个实体类开始。
Student
和Enrollment
实体之间是一对多的关系,Course
和Enrollment
实体之间也是一个对多的关系。 换而言之,一名学生可以修读任意数量的课程, 并且某一课程可以被任意数量的学生修读。
接下来,你将创建与这些实体对应的类。
学生实体
在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 Enrollments { get; set; }
}
}
ID
属性将成为对应于此类的数据库表中的主键。 默认情况下,EF 将会将名为ID
或classnameID
的属性解析为主键。
Enrollments
属性是导航属性。 导航属性中包含与此实体相关的其他实体。 在这个案例下,Student entity
中的Enrollments
属性会保留所有与Student
实体相关的Enrollment
。 换而言之,如果在数据库中有两行描述同一个学生的修读情况 (两行的 StudentID 值相同,而且 StudentID 作为外键和某位学生的主键值相同),Student
实体的Enrollments
导航属性将包含那两个Enrollment
实体。
如果导航属性可以具有多个实体 (如多对多或一对多关系),那么导航属性的类型必须是可以添加、 删除和更新条目的容器,如ICollection
。 你可以指定ICollection
或实现该接口类型,如List
或HashSet
。 如果指定ICollection
,EF在默认情况下创建HashSet
集合。
修读实体
在Models文件夹中,创建Enrollment.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 Enrollments { 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
实体相关联。
如果一个属性名为<导航属性名><主键属性名>
,Entity Framework 就会将这个属性解析为外键属性(例如,Student
实体的主键是ID
,Student
是Enrollment
的导航属性所以Enrollment
实体中StudentID
会被解析为外键)。 此外还可以将需要解析为外键的属性命名为<主键属性名>
(例如,CourseID
由于Course
实体的主键所以CourseID
也被解析为外键)。
课程实体
在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 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 options) : base(options)
{
}
public DbSet Courses { get; set; }
public DbSet Enrollments { get; set; }
public DbSet Students { get; set; }
}
}
此代码将为每个实体集创建DbSet
属性。 在 Entity Framework 中,实体集通常与数据表相对应,具体实体与表中的行相对应。
在这里可以省略DbSet
和DbSet
语句,实现的功能没有任何改变。 Entity Framework 会隐式包含这两个实体因为Student
实体引用了Enrollment
实体、Enrollment
实体引用了Course
实体。
当数据库创建完成后, EF 创建一系列数据表,表名默认和DbSet
属性名相同。 集合属性的名称一般使用复数形式,但不同的开发人员的命名习惯可能不一样,开发人员根据自己的情况确定是否使用复数形式。在最后一个 DbSet 属性之后添加以下高亮显示的代码在定义 DbSet 属性的代码之后添加下面高亮代码,对 DbContext 指定单数的表明来覆盖默认的表名。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions options) : base(options)
{
}
public DbSet Courses { get; set; }
public DbSet Enrollments { get; set; }
public DbSet Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().ToTable("Course");
modelBuilder.Entity().ToTable("Enrollment");
modelBuilder.Entity().ToTable("Student");
}
}
}
使用依赖注入注册上下文
ASP.NET Core 默认实现依赖注入。 在应用程序启动过程通过依赖注入注册相关服务 (例如 EF 数据库上下文)。 需要这些服务的组件 (如 MVC 控制器) 可以通过向构造函数添加相关参数来获得对应服务。 在本教程后面你将看到控制器构造函数的代码,就是通过上述方式获得上下文实例。
若要将SchoolContext
注册为一种服务,打开Startup.cs,并将添加高亮代码添加到ConfigureServices
方法中。
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
通过调用DbContextOptionsBuilder
中的一个方法将数据库连接字符串在配置文件中的名称传递给上下文对象。 进行本地开发时, ASP.NET Core 配置系统在appsettings.json文件中读取数据库连接字符串。
添加using
语句引用ContosoUniversity.Data
和Microsoft.EntityFrameworkCore
命名空间,然后生成项目。
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
打开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/
目录下创建.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
集合是为了优化性能。
在Program.cs,修改Main
方法,使得在应用程序启动时能执行以下操作:
- 从依赖注入容器中获取数据库上下文实例。
- 调用 seed 方法,将上下文传递给它。
- Seed 方法完成此操作时释放上下文。
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions options) : base(options)
{
}
public DbSet Courses { get; set; }
public DbSet Enrollments { get; set; }
public DbSet Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity().ToTable("Course");
modelBuilder.Entity().ToTable("Enrollment");
modelBuilder.Entity().ToTable("Student");
}
}
}
添加using
语句:
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Data;
在旧版教程中,你可能会在Startup.cs中的Configure
方法看到类似的代码。 我们建议你只在为了设置请求管道时使用Configure
方法。 将应用程序启动代码放入Main
方法。
现在首次运行该应用程序,创建数据库并使用测试数据作为种子数据。 每当你更改你的数据模型,你可以删除数据库、 更新你的 Initialize 方法,然后使用上述方式更新新数据库。 在之后的教程中,你将了解如何在数据模型更改时,只需修改数据库而无需删除重建数据库。
创建控制器和视图
接下来,将使用 Visual Studio 中的基架引擎添加一个 MVC 控制器和使用 EF 来查询和保存数据的视图。
CRUD 操作方法和视图的自动创建被称为基架。 基架与代码生成不同,基架的代码是一个起点,您可以修改基架以满足自己需求,而你通常无需修改生成的代码。 当你需要自定义生成代码时,你使用一部分类或需求发生变化时重新生成代码。
- 右键单击解决方案资源管理器中的Controllers文件夹选择添加 > 新搭建的基架项目。
如果显示“添加 MVC 依赖项”对话框:
更新 Visual Studio 至最新版本。 当 Visual Studio 版本小于 15.5 时会显示这个对话框
如果更新失败,选择添加, 然后执行添加控制器的步骤。
-
在添加基架的对话框中:
选择视图使用 Entity Framework 的 MVC 控制器
单击添加
-
在添加控制器对话框中:
在模型类选择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 依赖注入机制将会处理传递一个SchoolContext
实例到控制器。 在前面的教程中已经通过修改Startup.cs文件来配置注入规则。
控制器包含Index
操作方法用于显示数据库中的所有学生。 该方法从学生实体集中获取学生列表,学生实体集则是通过读取数据库上下文实例中的Students
属性获得:
public async Task Index()
{
return View(await _context.Students.ToListAsync());
}
你将在本教程后面了解此代码中的异步编程元素。
Views/Students/Index.cshtml视图使用table标签显示此列表:
@model IEnumerable
@{
ViewData["Title"] = "Index";
}
Index
@Html.DisplayNameFor(model => model.LastName)
@Html.DisplayNameFor(model => model.FirstMidName)
@Html.DisplayNameFor(model => model.EnrollmentDate)
@foreach (var item in Model) {
@Html.DisplayFor(modelItem => item.LastName)
@Html.DisplayFor(modelItem => item.FirstMidName)
@Html.DisplayFor(modelItem => item.EnrollmentDate)
Edit |
Details |
Delete
}
按 CTRL + F5 来运行该项目或从菜单选择调试 > 开始执行(不调试)。
单击学生选项卡以查看DbInitializer.Initialize
插入的测试的数据。 你将看到Student
选项卡链接在页的顶部或在单击右上角后的导航图标中,具体显示在哪里取决于浏览器窗口宽度。
查看数据库
当你启动了应用程序,DbInitializer.Initialize
方法调用EnsureCreated
。 EF 没有检测到相关数据库然后自己创建了一个,接着Initialize
方法的其余代码向数据库中填充数据。 你可以使用 Visual Studio 中的SQL Server 对象资源管理器(SSOX) 查看数据库。
关闭浏览器。
如果 SSOX 窗口尚未打开,请从Visual Studio 中的视图菜单中选择。
在 SSOX 中,单击(localdb) \MSSQLLocalDB > 数据库,然后单击和appsettings.json文件中的连接字符串对应的数据库。
展开表节点以查看你的数据库中的表。
右键单击Student表,然后单击查看数据若要查看已创建的列和已插入到表的行。
.Mdf和.ldf数据库文件位于*C:\Users*文件夹。
因为调用EnsureCreated
的初始化方法在启动应用程序时才运行,所以在这之前你可以更改Student
类、 删除数据库、 再运行一次应用程序,这时候数据库将自动重新创建,以匹配所做的更改。 例如,如果向Student
类添加EmailAddress
属性,重新的创建表中会有EmailAddress
列。
约定
由于 Entity Framwork 有一定的约束条件,你只需要按规则编写很少的代码就能够创建一个完整的数据库,
DbSet
类型的属性用作表名。 实体未被DbSet
属性引用,实体类名称用作表名称。实体属性名称用于列名称。
ID 或 classnameID 命名的实体属性被识别为主键属性。
如果属性名为 <导航属性名> <主键名>将被解释为外键属性 (例如,
StudentID
对应Student
导航属性,Student
实体的主键是ID
,所以StudentID
被解释为外键属性). 此外也可以将外键属性命名为 <主键属性名> (例如,EnrollmentID
,由于Enrollment
实体的主键是EnrollmentID
,因此被解释为外键)。
约定行为可以被重写。 例如,在本教程前半部分的显式指定表名称, 在本系列后面教程的设置列名称和将任何属性设置为主键或外键。
异步代码
异步编程是 ASP.NET Core 和 EF Core 的默认模式。
Web 服务器的可用线程是有限的,而在高负载情况下的可能所有线程都被占用。 当发生这种情况的时候,服务器就无法处理新请求,直到线程被释放。 使用同步代码时,可能会出现多个线程被占用但不能执行任何操作的情况,因为它们正在等待 I/O 完成。 使用异步代码时,当进程正在等待 I/O 完成,服务器可以将其线程释放用于处理其他请求。 因此,异步代码使得服务器更有效地使用资源,并且该服务器可以无延迟地处理更多流量。
异步代码在运行时,会引入的少量开销,在低流量时对性能的影响可以忽略不计,但在针对高流量情况下潜在的性能提升是可观的。
在下面的代码中,async
关键字,Task
返回值,await
关键字,和ToListAsync
方法使代码异步执行。
public async Task Index()
{
return View(await _context.Students.ToListAsync());
}
async
关键字用于告知编译器该方法主体将生成回调并自动创建Task
返回对象。返回类型
Task
表示正在进行的工作返回的结果为IActionResult
类型。await
关键字会使得编译器将方法拆分为两个部分。 第一部分是以异步方式结束已启动的操作。 第二部分是当操作完成时注入调用回调方法的地方。ToListAsync
是由ToList
方法的的异步扩展版本。
你使用 Entity Framework 编写异步代码时的一些注意事项:
只有导致查询或发送数据库命令的语句才能以异步方式执行。 包括
ToListAsync
,SingleOrDefaultAsync
,和SaveChangesAsync
。 不包括,操作IQueryable
的语句,如var students = context.Students.Where(s => s.LastName == "Davolio")
。EF 上下文是线程不安全的: 请勿尝试并行执行多个操作。 当调用异步 EF 方法时,始终使用
await
关键字。如果你想要利用异步代码的性能优势,请确保你所使用的任何库和包在它们调用导致 Entity Framework 数据库查询方法时也使用异步。
有关在 .NET 异步编程的详细信息,请参阅异步概述。
总结
你现在已创建了一个使用 Entity Framework Core 和 SQL Server Express LocalDB 来存储和显示数据的简单应用程序。 在下一个教程中,你将学习如何执行基本的 CRUD (创建、 读取、 更新、 删除) 操作。