五、设置DbContext和控制器

路由和控制器负责接收、验证和处理传入的 HTTP 请求。 在处理过程中,控制器可能通过DbContext持久化或不持久化并读取数据库中的记录。 了解控制器和DbContext如何一起工作对于构建预期工作的 web 应用项目至关重要。

本章结束后,您将能够理解以下主题:

  • 编写实体和枚举
  • 建立数据库 EF Core 和DbContext
  • 写入控制器和路由
  • 使用 Swagger UI 测试控制器

技术要求

以下是你完成本章所需要的东西:

  • Visual Studio 2019, Visual Studio for Mac,或 Rider
  • dotnet-ef工具
  • SQLite 浏览器或 SQLiteStudio

下面是本章的入门库,也是第 4 章application Clean Architecture ASP. php 的完整源代码.NET Core Solution:https://github.com/PacktPublishing/ASP.NET-Core-and-Vue.js/tree/master/Chapter05

写入实体和枚举

在我们开始在域项目中编写实体和枚举之前,下面是它们的快速定义。 什么是实体? 实体是应用中领域对象模型的表示。 数据库将实体转换为数据库表中的一行。 类似地,实体的属性是数据库表中的列。

另一方面,枚举**是类的一种类型,表示一组常量或只读变量。

我们已经快速地了解了实体和 enum 的定义; 现在,让我们看看他们的行动。

为 Travel Tour 应用创建实体和枚举

在您的解决方案应用中,转到Travel.Domain项目并创建两个目录:

  • Entities
  • Enums

Entities目录将用于所有 Travel Tour 应用实体,而Enums目录将用于所有 Travel Tour 应用枚举:

  1. In the Entities folder, create a C# file, TourPackage.cs, and write this code:

    namespace Travel.Domain.Entities { public class TourPackage { public int Id { get; set; } public int ListId { get; set; } public string Name { get; set; } public string WhatToExpect { get; set; } public string MapLocation { get; set; } public float Price { get; set; } public int Duration { get; set; } public bool InstantConfirmation { get; set; } } }

    类是带有Travel.Domain.EntitiesnamespaceTourPackage实体。 TourPackage实体代表包访问数据库中的数据,Id,ListId,Name,WhatToExpect、【显示】,Price,DurationInstantConfirmation。 它还需要一个 enum 用于Currency,一个对象关系用于List。 现在让我们创建这两个类。

  2. In your Enums folder, create Currency.cs and write the following code:

    namespace Travel.Domain.Enums { public enum Currency { PHP, USD, JPY, EUR, NOK } }

    Currency枚举文件有Travel.Domain.Enumsnamespace,枚举有PHPUSDJPYEURNOK

  3. Now switch back to the TourPackage class to add the Currency enum like so:

    using Travel.Domain.Enums; namespace Travel.Domain.Entities { public class TourPackage { … public int Duration { get; set; } public bool InstantConfirmation { get; set; } public Currency Currency { get; set; } } }

    引入Travel.Domain.Enums``namespace,这样您就可以使用刚刚使用Travel.Domain.Enums创建的Enum类。 现在在InstantConfirmation属性之后添加Currency属性。

  4. Next, let's create a TourList.cs C# file inside the Entities folder and write the following code:

    using System.Collections.Generic; namespace Travel.Domain.Entities { public class TourList { public TourList() { Tours = new List<TourPackage>(); } public IList<TourPackage> Tours { get; set; } public int Id { get; set; } public string City { get; set; } public string Country { get; set; } public string About { get; set; } } }

    让我们引入并定义TourList实体。 TourList实体有一个构造函数,该构造函数使用新的TourPackage``new List<TourPackage>()类型初始化Tours属性,该类型设置一对多关系。 其余属性为IdCityCountryAbout

  5. Now go back to TourPackage.cs and add a List property of type TourList below the Currency property:

    using Travel.Domain.Enums; namespace Travel.Domain.Entities { public class TourPackage { … public bool InstantConfirmation { get; set; } public Currency Currency { get; set; } public TourList List { get; set; } } }

    List类型TourList表示一对一关系,也称为对象-对象关系。

为域项目编写实体和枚举到此结束。 现在让我们转到下一个部分,设置实体框架核心(EF 核心)、DbContext和我们将在应用中使用的数据库。

建立数据库、EF Core 和 DbContext

您通常需要一个持久化框架,这是一个中间件,如果需要访问应用中的数据库,它可以帮助开发人员将数据存储在数据库中。 使用持久性框架,您可以轻松地使用实体来查询或将对象保存到数据库中。 现在让我们来看看 EF Core 和DbContext是什么。

英孚核心

在 ASP.NET Core,您可以从头构建持久性框架,但您不必这样做,因为这既耗时又昂贵。 为什么? 编写许多存储过程很难维护; 通过 ADO 读取数据.NET 对象将它们映射到应用中的表是很痛苦的。

所以,这里出现实体框架来拯救。 实体框架是一个持久性框架,它为您完成所有的管道工作。 通常,您不需要编写任何存储过程或将表映射到您的实体。

什么是英孚核心? EF Core是一个跨平台对象关系映射器(O/RM)。 EF Core 本身是实体框架的一个版本,它是一个使用 O/RM 在后台存储和检索对象的持久性框架。

这就是 EF Core 的概述; 让我们前往DbContext

DbContext

EF Core 提供了一个名为DbContext的类,它是我们的数据库的接口。 DbContext可以有一个或多个DbSet来表示数据库中的表。 我们使用语言集成查询(LINQ)来查询DbSet实体,EF Core 会在运行时将我们的 LINQ 查询转换为 SQL 查询。

DbContext打开到数据库的连接,读取数据并映射到对象,并将其添加到DbContextDbSet

当我们在DbSet中保存、更新或删除对象时,EF Core 会跟踪这些更改。 当我们要求持久化更改时,EF Core 自动生成 SQL 语句并在数据库中执行它们。

以上就是对DbContext的简要概述; 现在是在应用中写入DbContext和一些DbSet实体的时候了。

设置

这个设置是将是一个简单的 EF Core 和DbContext设置。 然而,我们将在此过程中进行重构和改进:

  1. First things first, install the dotnet-ef tool globally in your Terminal or CMD:

    dotnet tool install --global dotnet-ef

    dotnet-ef工具是。net CLI 的 EF Core 工具。 您可以访问nuget.org/packages/dotnet-ef链接查看dotnet-ef工具的最新版本。

    我们还需要安装两个 NuGet 包。 我们将使用dotnet cli,因为它可以在任何操作系统上运行,但如果您更喜欢使用管理 NuGet 包,即 IDE 中的 NuGet 包安装程序 UI,您也可以这样做。

  2. Now go to the Travel.WebApi project and run this command:

    dotnet add package Microsoft.EntityFrameworkCore.Design

    在运行时使用 EF Core 迁移数据库时需要Microsoft.EntityFrameworkCore.Design

  3. Next, go to the Travel.Data project and run this command:

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite

    Microsoft.EntityFrameworkCore.Sqlite是 EF Core 的 SQLite 数据库提供商。

  4. Now, still inside the Data project, let's write DbContext we will need here. Create a folder named Contexts inside the Travel.Data project. Inside the folder, create a TravelDbContext.cs file and write the following code:

    using Microsoft.EntityFrameworkCore; using Travel.Domain.Entities; namespace Travel.Data.Contexts { public class TravelDbContext : DbContext { public TravelDbContext (DbContextOptions<TravelDbContext> options) : base(options) { } public DbSet<TourList> TourLists { get; set; } public DbSet<TourPackage> TourPackages { get; set; } } }

    前面的代码导入了Microsoft.EntityFrameworkCore包和Travel.Domain.Entities包。 我们将类命名为TravelDbContext,由 EF Core 的DbContext衍生而来。

    TraveDbContext的构造器有一个TravelDbContext类型的DbContextOptions参数选项,该选项指向 base 关键字。

    您将在构造函数下面看到两个DbSet实体——TourListsTourPackages

  5. 现在转到Travel.WebApi项目的Startup.cs文件,并引入这两个名称空间:

    using Microsoft.EntityFrameworkCore; using Travel.Data.Contexts;

  6. Write the following code inside the ConfigureServices method:

    services.AddDbContext<TravelDbContext>(options => options .UseSqlite("Data Source=TravelTourDatabase.sqlite3"));

    代码在IServiceCollection中将TravelDbContext注册为服务,而UseSqlite将上下文配置为连接到 SQLite 数据库。

    ConfigureServices方法应该看起来像这样:

    public void ConfigureServices(IServiceCollection services) { services.AddDbContext<TravelDbContext>(options => options .UseSqlite("Data Source=TravelTourDatabase.sqlite3")); services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Travel.WebApi", Version = "v1" }); }); }

  7. Now let's go back to the Travel.Data project for database migration.

    请注意

    所有命令均为一行命令。 书页的宽度会给任何太长的命令增加一行。

  8. After navigating to the Travel.Data project, you are now ready to run your first migration:

    dotnet ef migrations add InitialCreate --startup-project ../../presentation/Travel.WebApi/

    EF Core 会在你的项目中创建一个名为Migrations的文件夹,并生成 c#文件。 这些文件是名为InitialCreate的迁移构建器和数据库当前模式的快照。

  9. Next, create a database and schema from the migration files:

    dotnet ef database update --startup-project ../../presentation/Travel.WebApi/

    该命令在 Web API 项目中创建一个 SQLite 数据库文件TravelTourDatabase.sqlite3

  10. 要查看您所做的成功迁移,请使用 SQLiteStudio 或 SQLite Browser。

  11. 在 SQLiteStudio 或 SQLite 浏览器中添加TravelTourDatabaseSQLite 数据库文件来查看表:

Figure 5.1 – SQLiteStudio

alicia alicia 工作室

上面 SQLiteStudio 的截图显示TravelTourDatabase,下面 SQLite Browser 的截图显示TravelTourDatabase:

Figure 5.2 – SQLite Browser

图 5.2 - SQLite 浏览器

您已经了解了实体、实体框架、EF Core 和DbContext是什么。 你也学会了在哪里获得它们以及如何建立它们。 现在我们有了一个数据库,让我们继续下一节,写入控制器和路由

写入控制器和路由

现在到了部分,我们将编写处理来自客户端应用的 HTTP 请求的控制器。 我们还将编写将 HTTP 请求重定向到其各自控制器的路由。

这将是两个简单的控制器,但我们将在下一章中重构它们,第六章深入 CQRS

TourPackagesController

我们将为TourPackages创建控制器,该控制器将处理任何检索和更新数据库中TourPackages表的请求:

  1. Now go back to the Travel.WebApi project and create TourPackagesController.cs inside of the Controllers directory and write the following code:

    using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; using Travel.Data.Contexts; using Travel.Domain.Entities;

    代码导入前面的namespaces,这是此控制器所需要的。

  2. Next, write the controller class:

    namespace Travel.WebApi.Controllers { [ApiController] [Route("api/[controller]")] public class TourPackagesController : ControllerBase { } }

    我们将使用ApiController属性,该属性为装饰类提供一些 API 行为,如 HTTP API 响应,然后是另一个属性Route—重定向 HTTP 请求的路由。

    TourPackagesController 类派生自ControllerBase,一个 MVC 控制器的基类,但不支持视图。

  3. Next, write the following code inside TourPackagesController:

    private readonly TravelDbContext _context; public TourPackagesController(TravelDbContext context) { _context = context; }

    我们将TravelDbContext注入TourPackagesController的构造函数中,然后将其赋值给后台字段_context。 现在让我们来看看控制器中的第一个公共方法。

  4. Next, we need to write the Get method under the constructor method:

    [HttpGet] public IActionResult Get() { return Ok(_context.TourPackages); }

    HttpGet属性标识一个可以支持HttpGet方法的操作。 另外,Get方法返回一个200状态代码和TourPackage集合。 在使用GET HTTP动词请求时,当请求的端点为api/tourpackages时,此方法将被触发。

    现在让我们转向下一个公共方法。

  5. Next, we need to write the Create method under the Get method:

    [HttpPost] public async Task<IActionResult> Create([FromBody] TourPackage tourPackage) { await _context.TourPackages.AddAsync (tourPackage); await _context.SaveChangesAsync(); return Ok(tourPackage); }

    Create方法之上的HttpPost属性确定了一个可以支持HttpPost方法的动作。 post方法采用带有FromBody装饰的TourPackage对象,指定应该使用请求体绑定参数。

    Create方法返回一个200状态码以及新创建的TourPackage。 当使用POST HTTP动词请求时,请求的端点为api/tourpackages时,此方法将被触发。

    你还会注意到这里的类型为IActionResultTaskIActionResult是一个接口,它定义了一个契约,表示一个操作方法的结果,而Task用于异步操作。

    现在让我们继续下一个公共方法TourPackagesController

  6. Next, we write the Delete method under the Create method:

    [HttpDelete("{id}")] public async Task<IActionResult> Delete([FromRoute] int id) { var tourPackage = await _context.TourPackages.SingleOrDefaultAsync (tp => tp.Id == id); if (tourPackage == null) { return NotFound(); } _context.TourPackages.Remove(tourPackage); await _context.SaveChangesAsync(); return Ok(tourPackage); }

    Delete方法之上的HttpDelete属性标识一个可以支持HttpGet方法的操作。 当使用DELETE HTTP动词请求时,请求的端点为api/tourpackages/{id}时,此方法将被触发。

    您将注意到方法的HttpDelete属性中有一个"{id}"字符串。 SingleOrDefaultAsync使用id参数,将其与现有对象匹配,然后返回要从表中删除的对象。 DbContext必须调用SaveChangesAsync()以完成指定TourPackage对象的删除。

    最后,controller包括200 - OK应答中删除的TourPackage

    现在我们来看看方法。

  7. We need to write the Update method under the Delete method:

    [HttpPut("{id}")] public async Task<IActionResult> Update([FromRoute] int id, [FromBody] TourPackage tourPackage) { _context.Update(tourPackage); await _context.SaveChangesAsync(); return Ok(tourPackage); }

    前面的代码是一个方法,更新一个TourPackage对象。

这就是TourPackagesController。 现在我们可以继续TourListsController

TourListsController

现在在Controllers目录中创建TourListsControllerTourListsControllerTourPackagesController具有相同的结构。

下面是TourListsController代码的摘要。 查看应用的 GitHub repo第 5 章|Finish directory中的代码,并将其写入您的文件:

using Microsoft.AspNetCore.Mvc;

using Travel.Domain.Entities;
namespace Travel.WebApi.Controllers
{
  [ApiController]
  [Route("api/[controller]")]
  public class TourListsController : ControllerBase
  {
    private readonly TravelDbContext _context;
    public TourListsController(TravelDbContext context){…}
    [HttpGet]
    public IActionResult Get(){…}
    [HttpPost]
    public async Task<IActionResult> Create([FromBody]
      TourList tourList){…}
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete([FromRoute] 
      int id){…}
    [HttpPut("{id}")]
    public async Task<IActionResult> Update([FromRoute]
      int id, [FromBody] TourList tourList){…}
  }
}

如您所见,代码也是一个派生自ControllerBase的类,具有GetCreateDeleteUpdate公共方法。

现在我们已经完成了为应用编写的控制器,让我们运行它来看看我们的工作是如何进行的,通过运行下面的命令仍然在WebApi项目中:

dotnet run

上述命令将启动或运行应用。 在应用运行时,我们可以继续下一节,使用 Swagger UI测试控制器,以查看控制器的工作情况。

使用 Swagger UI 测试控制器

我们已经构建了控制器并运行了应用,但是还没有测试它,对吧? 在本节中,我们将向TourList控制器发送GETPOSTPUTDELETE请求。

首先,确保您正在通过进入Web Api项目并运行以下命令来运行应用:

dotnet run

现在检查 Swagger UI,在应用运行时转到 https://localhost:5001/swagger/index.html 链接。

您将看到旅游列表旅游套餐端点。 Swagger UI 向您显示每个控制器的 HTTP 方法的文档。 有GETPOSTDELETE,和PUT方法准备尝试:

Figure 5.3 – Swagger UI

图 5.3 - Swagger UI

下面的截图显示了应用的模式,它们也是由 Swagger UI 为你生成的:

Figure 5.4 – Schemas in Swagger UI

图 5.4 - Swagger UI 中的模式

现在让我们尝试一下TourLists控制器的GET方法。 它将返回一个空的集合或数组,因为我们还没有在其中添加任何数据。

现在转到POST,点击试试按钮。 在单击Execute按钮之前,用以下代码替换请求体:

{
  "city": "Oslo",
  "country": "Norway",
  "about": "Oslo, the capital of Norway, sits on the country's   southern coast at the head of the Oslofjord. It's known   for its green spaces and museums. Many of these are on the   Bygdøy Peninsula, including the waterside Norwegian Maritime   Museum and the Viking Ship Museum, with Viking ships from the   9th century. The Holmenkollbakken is a ski-jumping hill with   panoramic views of the fjord. It also has a ski museum."
}

你可以复制前面的请求体中,我们将使用发送POST 请求到服务器从Commands.txt文件,您可以找到在第五章应用文件夹的 GitHub 回购。

我们将只在请求体的文本区域中包含city属性、country属性和about属性,如下所示:

Figure 5.5 – Request body for the POST method

图 5.5 - POST 方法的请求体

在替换了POST部分的默认请求体后,可以看到图 5.5,点击Execute按钮:

Figure 5.6 – Response body from the TourLists controller

图 5.6 - TourLists 控制器的响应体

您可以在图 5.6中看到响应体具有我们发布的属性和1id

如果您想更新Oslo City条目或删除它,您可以到 Swagger UI 并在那里发出请求。 但是,由于我们也将在应用中使用它,所以我将保持原样。

你可以在本书的 GitHub 库中查看章节的完整解决方案。

现在我们已经完成了这一部分,让我们回顾一下所学的内容。

总结

作为本章的总结,我们已经介绍了实体在应用中表示域模型,并且我们在应用的域层中编写实体。

我们还介绍了 EF Core,它帮助开发人员在不使用 O/RM 的 SQL 查询的情况下从数据库中持久化和查询数据。

我们了解到 Entity Framework 的DbContext是数据库的接口,而DbSet表示数据库中的一个表。

最后,我们用DbContext编写了两个控制器,并通过通过 Swagger UI 发送 HTTP 请求对它们进行了测试。

现在,您已经掌握了使用 EF Core 处理任何客户端应用发送的任何 HTTP 请求并处理它们并将它们保存在任何类型的关系数据库中的技能。

在下一章中,我们将学习如何使用CQRS命令和查询职责隔离来编写可维护和可测试的代码。 那么,回头见。**