C#笔记——使用ASP.NET和Entity Framework Core
本文最后更新于:2023年11月4日 下午
环境配置
首先检查下自己电脑装没装 .NET,建议用 vs 当懒狗,直接全装了。在终端中输入命令:dotnet --list-sdks
,如果没列出任何版本或者未找到命令就是没装,赶紧给我装啊!.jpg
至于项目文件,全都是文档中有提到的,看看文档就找到了!
项目基本结构
属于十分经典的 API 项目结构啊:
- Models: 保存实体模型,如 POCO 或者说 DTO。
- Services: 业务逻辑层。
- Controllers: 控制器层,负责搜集参数、参数校验、调用 Service(服务)等。
- Data: 保存数据库相关,如
DbContext
。
使用 ASP.NET Core 控制器创建 Web API
创建 Web API 项目
Visual Studio
新建项目中搜索: ASP.NET Core Web API ,然后创建即可。
Visual Studio Code
在终端窗口中进入对应文件夹,运行命令:dotnet new webapi -f net7.0
。该命令根据当前文件夹名称命名 C# 项目文件。
接口测试工具
我们可以用 Postman 来测试接口,也可以用 .NET HTTP REPL 工具来测试接口。前者直接官网下载即可,后者则在终端运行该命令:dotnet tool install -g Microsoft.dotnet-httprepl
。
.NET HTTP REPL 食用方法
通过以下命令连接:httprepl https://localhost:{port}
;或者,在Httprepl
运行时随时运行以下命令:connect https://localhost:{port}
。
如果Httprepl
工具发出“找不到 OpenAPI 说明”的警告,最有可能的原因是开发证书不受信任。执行命令:dotnet dev-certs https --trust
配置系统以信任开发证书。
浏览可用的 endpoint:ls
进入某一个 endpoint:cd endpointName
发出请求:get
、post
、put
、patch
、delete
,若带参数,加在后面即可,如:get 5
。需要 body,那就用-c
然后加在后边,如:post -c "{"id": 3, "name": "MyTest", "isFree": false}"
和put 3 -c "{"id": 3, "name": "MyTest", "isFree": false}"
结束当前Httprepl
会话:exit
创建 ASP.NET Core Web API 控制器
控制器应该放在 Controllers 文件夹下,操作通过路由被公开为 HTTP endpoint。命名格式为:XxxController
,其实就是加一个Controller
后缀。我们的https://localhost:{port}/xxx
的各种 HTTP 请求将执行XxxController
类下的对应方法。
注意Web API
的控制器的基类应该是ControllerBase
而不是Controller
,后者派生自前者并且添加了对视图的支持,用来处理网页而不是Web API
请求。
我们还需要将两个重要属性应用到我们创建的控制器上,这两个属性分别是:[ApiController]
和[Route("[controller]")]
。[ApiController]
启用固定行为,使生成 Web API 更加容易。如果要在多个控制器上应用该属性,一种方法是创建通过[ApiController]
属性批注的自定义基控制器类,比如:
1 |
|
又或者是将该属性应用到程序集,如修改Program.cs
:
1 |
|
不过需要的是,如果某个控制器应用了[ApiController]
属性,那么同时也要对该控制器应用[Route]
属性,该属性说明见下面。[Route("[controller]")]
属性定义路由模式。[controller]
令牌(参数)替换为控制器的名称(不区分大小写,无 Controller 后缀)。举两个例子:
[Route("[controller]")]
应用到AnimalController
类上,则该控制器处理对https://localhost:{port}/animal
的请求。[Route("api/[controller]")]
应用到AnimalController
类上,则该控制器处理对https://localhost:{port}/api/animal
的请求。
在控制器中实现 REST 谓词
Get
不带参数的实现:
1 |
|
带参数的实现(注意int
只是举个例子,实际是什么类型就填什么):
1 |
|
POST
将项作为参数传递入方法时,ASP.NET Core 会自动发送到 endpoint 的任何应用程序/JSON 转换为填充的 .NET TEntity 对象。
1 |
|
PUT
1 |
|
PATCH
基本包貌似不提供这个词操作,详情见:ASP.NET Core Web API 中的 JSON 修补程序
DELETE
1 |
|
Swashbuckle 和 ASP.NET Core 入门
如何为我的 Web API 应用添加 Swashbuckle
包安装
Visual Studio
用 NuGet 可以快速管理当前项目的各个包,将“包源”设置为“nuget.org”,然后直接在搜索框中搜索“Swashbuckle.AspNetCore”,安装最新的“Swashbuckle.AspNetCore”包即可。下面的步骤根据 Visual Studio 2022 编写:
- “工具” -> “NuGet 包管理器” -> “管理解决方案的 NuGet 包”,然后会打开一个窗口。
- 选择浏览,然后直接在搜索框中搜索“Swashbuckle.AspNetCore”,安装最新的“Swashbuckle.AspNetCore”包即可。
Visual Studio Code
从“终端”窗口中运行命令(注意在项目文件夹下):dotnet add Xxx.csproj package Swashbuckle.AspNetCore -v 6.5.0
。命令中的Xxx
换成自己的项目文件名。根据自己的需要选择版本号,编写该文章时候的最新稳定版为6.5.0
。
添加并配置 Swagger 中间件
添加以下代码到Program.cs
中:
1 |
|
注意到app.Environment.IsDevelopment()
,这句代码表明:仅当将当前环境设置为“开发”时,才会添加 Swagger 中间件。
如果使用目录及 IIS 或反向代理,请使用./
前缀将 Swagger 终结点设置为相对路径。例如./swagger/v1/swagger.json
。
自定义和扩展
API 信息和说明
传递给AddSwaggerGen
方法的配置操作会添加诸如作者、许可证和说明的信息。例如:
1 |
|
启动来看看效果:
XML 注释
要启用 XML 注释功能,就将GenerateDocumentationFile
添加到.csproj
(C#项目)文件:
1 |
|
一些 Swagger 功能无需使用 XML文档文件即可起作用,但是对于大多数功能(即方法摘要以及参数说明和响应代码说明),必须使用 XML 文件。所以可以添加以下代码,通过反射生成与 Web API 项目相匹配的 XML 文件名:
1 |
|
AppContext.BaseDirectory
属性用于构造 XML 文件的路径。
元素
启用 XML 注释后,三斜杠注释将被添加到 Swagger UI 中,比如:
1 |
|
看看效果:
元素
还可以添加<remarks>
元素来提供额外说明,例如:
1 |
|
看看效果:
元素
我们还可以在 XML 注释中表示错误代码和响应类型,比如在Create
方法的 XML 注释中,加入如下注释:
1 |
|
我们还需要为Craete
方法添加如下的额外的属性:
1 |
|
来看看效果:
同样的,我们为Get
方法也添加下 XML 注释:
1 |
|
然后看看效果:
元素和元素
就如同他们的中文意思一般,为这两个元素写 XML 注释,其实就是给对应参数和返回类型写注释。下面为Get
方法额外添加一点注释:
1 |
|
看看效果:
使用 Entity Framework Core 持久保存和检索关系数据
什么是 EF Core?
“Entity Framework Core (EF Core) 是对象-关系映射程序 (ORM)。 ORM 在代码和数据库中实现的域模型之间提供一个层。 EF Core 是一种数据访问 API,允许使用 .NET 普通旧公共运行时语言 (CLR) 对象 (POCO) 和强类型语言集成查询 (LINQ) 语法与数据库进行交互。
在 EF Core 中,数据库可在 .NET POCO 后面抽象化。 EF Core 处理与基础数据库的直接交互。 使用此 API 时,可以花更少的时间来与数据库之间转换请求以及编写 SQL,从而让你有更多时间专注于重要的业务逻辑。”
在 EF Core 中,有一个很重要的类,必须了解和掌握,那就是:DbContext
(数据库上下文)类。DbContext
是表示工作单元的特殊类。DbContext
提供的方法可用于配置选项、连接字符串、日志记录以及用于将域映射到数据库的模型。
派生自DbContext
的类:
- 表示与数据库之间的活动会话
- 保存和查询实体的实例
- 包括
DbSet<T>
类型的属性,其表示数据库中的表。
包安装
要使用 EF Core 进行开发,我们依然要安装对应包。前面已经提到过 VS 可以轻松通过 Nuget 包管理器 来进行安装所以这里不提了。主要是通过命令行的安装:
- 首先运行:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
,因为项目用的是sqlite
所以这里最后面是Sqlite
,你也可以相应地换成MySQL
或是其他数据库。 - 然后运行:
dotnet add package Microsoft.EntityFrameworkCore.Design
,该命令添加 EF Core 工具所需的包。 - 最后运行:
dotnet tool install --global dotnet-ef
,该命令将安装dotnet ef
,用于创建迁移和基架的工具。如需更新,则运行dotnet tool update --global dotnet-ef
。
现有模型迁移至数据库
搭建模型和 DbContext 的基架
在Models
文件夹下搭建我们需要的模型,这些模型在迁移之后就会成为数据库中的各个表,比如例子中的Pizza
、Topping
、Sauce
模型,迁移后就是Pizza
、Topping
、Sauce
三张表。
模型搭建完毕后,我们还需要在Data
文件夹下添加并配置DbContext
实现。DbContext
是一个网关,可以通过该网关与数据库进行交互。
在本例中,我们添加了一个继承自DbContext
类的PizzaContext
类,用于跟数据库进行交互。代码如下:
1 |
|
可以发现,如果没其他要求的话,我们只需要将模型的DbSet
集合添加至类中即可,DbSet<T>
属性也对应于要在数据库中创建的表。
然后,设置数据源,在Program.cs
中,添加该代码:builder.Services.AddSqlite<XxxContext>("Data Source=Xxx.db")
。自己在自己的应用中实现时,将XxxContext
和Xxx.db
替换掉就好了,例子中这两处地方分别为PizzaContext
和ContosoPizza.db
。
创建并运行迁移
运行该命令来创建迁移:dotnet ef migrations add MigrationName --context XxxContext
。
在实际使用中,将命令中的MigrationName
替换为自己想要创建的迁移的名字,迁移将被命名为该名字;XxxContext
则替换为对应的类的名字,如PizzaContext
。
运行完该命令后,项目中将会生成Migrations
文件夹,在该文件夹下包含<timestamp>_MigrationName.cs
文件。
创建完迁移后,还需要运行该命令:dotnet ef database update --context XxxContext
,该命令将会应用迁移。
EF Core 约定通过推断开发者的意图来缩短开发时间。 例如,名为Id
或<entity name>Id
的属性被推断为生成的表的主键。如果选择不采用命名约定,则该属性必须使用[Key]
特性进行批注或配置为DbContext
的OnModelCreating
方法中的键。
更改模型和更新数据库架构
某些时候我们可能需要对之前的模型进行修改,然后再把修改同步至数据库。要执行这样的操作,和上一节“创建并运行迁移”的操作是一样的,即先创建迁移,再应用迁移。不过请注意进行版本控制和备份以防数据丢失。
与数据交互
编写业务代码逻辑一般都是在 Service 层上,所以这里默认是编写在对应的Service
类上的。
要与对应的数据库进行交互,我们就需要有对应的Context
类的字段,如:private readonly PizzaContext _context
,并在构造函数中将参数分配给该字段。
要访问数据库中的某张表,获取对应的DbSet
属性即可,如:_context.Pizzas
。这里再放几个链式调用中常见的方法:
AsNoTracking
扩展方法指示 EF Core 禁用更改跟踪。如果某个操作是只读的,该方法可以优化性能。ToList
,转换成列表,懂得都懂。Include
扩展方法采用 lambda 表达式来指定将某些导航属性(在例子中是Topping
和Sauce
,这两个都是 模型/数据库中的表,而且包含在Pizza
模型中)通过使用预先加载来包含在结果中,如果不使用此表达式, EF Core 会为这些属性返回null
。(我觉得可以理解为如果是引用属性那就需要用这个表达式来将其包含在结果集中)SingleOrDefault
方法返回与 lambda 表达式匹配的结果。如果没有匹配的结果,则返回null
;如果有多个匹配的结果,将会引发异常。Find
是按主键查询记录的优化方法。Remove
方法根据传入的对象来移除 EF Core 对象图中的对应实体。
在执行修改操作后,我们还需要执行_context.SaveChanges()
来将操作结果应用到数据库。
现有数据库逆向工程至模型
如果我们已经有了数据库,想要根据现有数据库来生成基架代码,有没有什么方便又减少工作量的办法呢?
答案是有的。运行命令:dotnet ef dbcontext scaffold "Data Source=Xxx/Xxx.db" Microsoft.EntityFrameworkCore.Sqlite --context-dir Data --output-dir Models
。该命令将使用提供的连接字符串("Data Source=Xxx.db"
)搭建DbContext
类和模型类基架;指定使用Microsoft.EntityFrameworkCore.Sqlite
数据库提供程序;--context-dir
指定DbContext
类的目录,--output-dir
指定模型(Model
)类的目录。
创建完后别忘了检查一遍,如果有需要也可以对DbContext
和模型类进行修改。
如果数据库发生更改,可以生成新的基架文件。生成的文件每次都会被覆盖,但会创建为partial
类,因此你可以在自己的单独文件中使用自定义属性和行为来扩展它们。
最后,别忘了在Program.cs
中将新生成的DbContext
类注册到依赖项注入系统:builder.Services.AddSqlite<XxxContext>("Data Source=Xxx.db");
ASP.NET Core Web API 中控制器操作的返回类型
IActionResult 类型
当操作中可能有多个ActionResult
返回类型时,适合使用IActionResult
返回类型。ActionResult
类型表示多中 HTTP 状态代码。派生自ActionResult
的任何非抽象类都限定为有效的返回类型,例如:BadRequestResult
(400)、NotFoundResult
(404)、OkObjectResult
(200)。或者,可以使用ControllerBase
类中的便利方法从操作返回ActionResult
类型,例如:return BadRequest();
,是return new BadRequestResult();
的简写形式。
同步操作
1 |
|
异步操作
1 |
|
ActionResult 类型
ActionResult<T>
支持返回从ActionResult
派生的类型或返回特定类型。
同步操作
1 |
|
异步操作
1 |
|
ActionResult 与 IActionResult
对比一下上面几部分代码,就可以发现,IActionResult
返回的是Ok(Entity)
,把实体类放入OkObjectResult
类中;而ActionResult<T>
则是直接返回实体类。
希望本文章能够帮到您~