在ASP.NET Core中使用Dapper ORM构建CRUD API
ORM是Object Relational Mapper(ORM)的缩写。它也可以代表对象关系映射,它描述了ORM应用的方法。
ORM通常是一个库,使我们与数据库的互动变得简单。它在我们和数据库之间充当代言人的角色。
ORM采用抽象的方式来 "掩盖 "不需要的细节,并将注意力集中在基本的功能上。抽象之后,我们的数据被分解成更小的部分,称为模型。
在这篇文章中,我们将学习如何使用Dapper建立一个API来进行查询。
主要收获
在本文结束时,你应该能够。
- 知道什么是ORM以及它们是如何工作的
- 用dotnet CLI创建一个ASP.NET Core API项目
- 能够使用Dapper ORM进行查询
- 用Swagger记录一个ASP.NET核心API。
先决条件
- 具有C#的基本知识
- 在你的机器上安装了[.NET框架5.0+。]
- 一个你选择的代码编辑器。我使用[Visual Studio Code]
- 在你的机器上安装[Postman]或任何REST客户端
什么是Dapper?
Dapper是一个.NET框架的ORM。它是轻量级的,快速的,并且由于你可以编写你自己的SQL查询,它提供了很大的灵活性。Dapper提供了一些方法,使我们可以很容易地与数据库进行交流。其中一些方法是。
-
Execute:这个方法执行一个命令并返回受影响的行。它通常用于执行INSERT、UPDATE和DELETE操作。
-
查询:该方法执行一个查询并映射结果。它通常用于从数据库中获取多个对象。
-
QueryFirst:该方法执行一个查询,并映射与查询中的参数相匹配的第一个结果。当我们只需要一个符合所提供规格的项目时,就可以使用这个方法。
-
QueryFirstOrDefault:这个功能类似于
QueryFirst,但是如果序列不包含任何元素,则返回一个默认值。 -
QuerySingle:它执行一个查询并映射结果,条件是序列中只有一个项目。如果序列中不正好有一个元素,当没有元素或有一个以上的元素被返回时,它会抛出一个异常。
-
QuerySingleOrDefault:这个方法的工作原理与
QuerySingle,但是如果没有从数据库返回任何项目,则返回一个默认值。 -
QueryMultiple:这个方法可以用一个命令同时执行许多查询,并映射结果。
创建一个新的ASP.NET Core API项目
在安装完Dotnet Framework SDK后,继续在你的控制台输入下面的命令,以确认它确实已经安装。
$ dotnet --version
5.0.301
如果你已经安装了框架,你应该看到SDK的版本就在你的命令下面。
在确认我们已经安装了SDK之后,我们可以创建我们的项目。由于我们将建立一个API,我们将使用创建API项目的命令。要做到这一点,打开一个终端或命令提示符,并导航到你想创建项目的目录。现在输入下面的命令。
$ dotnet new webapi -n TodoAPI
n 标志只是告诉dotnet我们想把我们的应用程序称为什么。这应该为我们的API创建一个新的启动项目。导航到项目文件夹,输入以下命令。
$ dotnet run
它应该在所示的端口上启动我们的服务器。这通常是5000 。当你在你喜欢的编辑器中打开这个项目时,你会看到一个控制器已经被定义在Controllers/WeatherForecastController.cs 。
为了测试这个控制器,打开你的REST客户端或浏览器,并输入链接http://localhost:5000/WeatherForecast 。你应该看到从该控制器返回的数据。
设置我们的数据库
我们在这里要做的第一件事是将连接字符串添加到我们的appsettings.json 文件。像这样。
"ConnectionStrings": {
"SqlConnection": "your_connection_string"
}
在这之后,我们需要安装一个数据库客户端。在这篇文章中,我们将使用SQL客户端。你可以通过这样做来安装。
$ dotnet add package Microsoft.Data.SqlClient
下一件事是处理迁移。这需要一些外部帮助,因为Dapper不能为我们做这个。为了创建必要的表,我们将使用FluentMigrator。要安装它,请在你的项目根目录下运行以下命令。
$ dotnet add package FluentMigrator
我们还需要FluentMigrator的runner来帮助运行迁移。同样地,运行。
$ dotnet add package FluentMigrator.Runner
现在我们已经安装了FluentMigrator,我们可以设置我们的迁移了。在你的项目根目录下创建一个Migrations ,并向其中添加以下文件。
using FluentMigrator;
namespace TodoAPI.Migrations
{
[Migration(202125100001)]
public class Initial_202125100001 : Migration
{
// Drop the tables
public override void Down()
{
Delete.Table("Todos");
Delete.Table("Users");
}
// Create the tables
public override void Up()
{
Create.Table("Users")
.WithColumn("Id").AsGuid().NotNullable().PrimaryKey()
.WithColumn("Firstname").AsString(50).NotNullable()
.WithColumn("Lastname").AsString(60).NotNullable()
.WithColumn("Email").AsString(50).NotNullable();
Create.Table("Todos")
.WithColumn("Id").AsGuid().NotNullable().PrimaryKey()
.WithColumn("Title").AsString(50).NotNullable()
.WithColumn("Status").AsString(10).NotNullable()
.WithColumn("Description").AsString().NotNullable()
.WithColumn("UserId").AsGuid().NotNullable().ForeignKey("Users", "Id");
}
}
}
using System;
using System.Collections.Generic;
using FluentMigrator;
using TodoAPI.Domain.Entities;
namespace TodoAPI.Migrations
{
[Migration(202125100002)]
public class Seed_202125100002 : Migration
{
public override void Down()
{
Delete.FromTable("Users");
Delete.FromTable("Todos");
}
public override void Up()
{
List<Guid> ids = new List<Guid>{};
List<String> names = new List<String>{"Mike", "Olumide", "Precious", "Marv", "Toyo", "Satoshi", "Ichinose", "Vanitas"};
List<String> titles = new List<String>{"Title X", "Titte Y", "Title Z", "Title A", "Title 0"};
for (int i = 0; i < 6; i++)
{
Random rnd = new Random();
String lastname = names[rnd.Next(names.Count)];
String firstname = names[rnd.Next(names.Count)];
Guid id = Guid.NewGuid();
ids.Add(id);
Insert.IntoTable("Users")
.Row(new User{
Firstname = firstname,
Lastname = lastname,
Email = String.Format("{0}{1}@email.co", firstname, lastname),
Id = id
});
for (int j = 0; j < 5; j++)
{
Insert.IntoTable("Todos")
.Row(new TodoItem{
Title = titles[rnd.Next(titles.Count)],
Description = "Some pretty long string",
Status = (TodoStatus)rnd.Next(3),
UserId = id,
Id = Guid.NewGuid()
});
}
}
}
}
}
一个迁移创建了我们需要的表,另一个迁移为我们提供了种子数据,这样我们就有数据在数据库中工作了。
"但是我们如何运行这些迁移呢?",有人可能会问。那么,这就是FluentMigrator的运行器的作用了。所以,让我们创建一个扩展,在我们的应用程序运行之前,运行我们的迁移程序。
创建一个新的文件夹Extensions ,并将下面的文件添加到其中。
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using FluentMigrator.Runner;
namespace TodoAPI.Extensions
{
public static class MigrationManager
{
public static IHost MigrateDatabase(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var migrationService = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
try
{
migrationService.ListMigrations();
migrationService.MigrateUp();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
throw;
}
}
return host;
}
}
}
上面的扩展给IHost 类添加了一个外部方法。这使得只要在IHost 类的任何实例中 "使用 "这个扩展,就可以调用MigrateDatabase 方法。
为了确保这个方法被调用,请到Program.cs ,并修改代码,如下所示。
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.MigrateDatabase() // Add this line
.Run();
}
如上图所示,迁移是在应用程序被构建时和运行前运行的。我们还需要在我们的Startup.cs 文件中添加一些东西。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// Add this
services.AddLogging(c => c.AddFluentMigratorConsole())
.AddFluentMigratorCore()
.ConfigureRunner(c => c.AddSqlServer2016()
.WithGlobalConnectionString(Configuration.GetConnectionString("SqlConnection"))
.ScanIn(Assembly.GetExecutingAssembly()).For.Migrations());
}
上面的片段配置了FluentMigrator,并为其添加了日志记录。这使得我们能够在控制台中直观地看到迁移的情况。上面的代码还为我们的SQL服务器添加了配置。
做完这些之后,通过运行dotnet run ,启动你的应用程序,迁移应该在应用程序启动之前就已经开始了。如果你遇到任何错误,试着回溯你的步骤,找到问题的根源。
配置Dapper
接下来,让我们配置一下Dapper。要安装Dapper,请运行以下命令。
$ dotnet add package Dapper
安装Dapper后,创建一个文件夹Data ,在其中添加以下文件。
using System.Data;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace TodoAPI.Data
{
public class DapperContext
{
private readonly IConfiguration _configuration;
public DapperContext(IConfiguration configuration)
{
_configuration = configuration;
}
public IDbConnection CreateConnection()
=> new SqlConnection(_configuration.GetConnectionString("SqlConnection"));
}
}
上面的类负责创建一个与我们的数据库的连接。然后,Dapper被用来使用该连接与我们的数据库通信。不要忘记把这个类注册为一个服务。
前往Startup.cs ,添加如下所示的行。
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<DapperContext>(); // Add this line to register the service
services.AddControllers();
}
添加我们的应用程序逻辑(模型和存储库)。
首先,创建一个新的文件夹,Domain 。在这个新的文件夹中创建三个文件夹,即。Entities,Repositories 和DTOs 。
在Entities 文件夹中,我们将定义我们的模型。继续并添加以下文件。
using System;
using System.ComponentModel.DataAnnotations;
namespace TodoAPI.Domain.Entities
{
public class User
{
[Key]
public Guid Id { get; set; }
[Required]
public String Firstname { get; set; }
[Required]
public String Lastname { get; set; }
[Required]
public String Email { get; set; }
}
}
using System;
using System.ComponentModel.DataAnnotations;
namespace TodoAPI.Domain.Entities
{
public class TodoItem
{
[Key]
public Guid Id { get; set; }
[Required]
public Guid UserId { get; set; }
[Required]
public String Title { get; set; }
[Required]
public String Description { get; set; }
public TodoStatus Status { get; set; } = TodoStatus.Todo;
}
}
namespace TodoAPI.Domain.Entities
{
public enum TodoStatus
{
Done,
InProgress,
Todo
}
}
接下来,添加我们的数据传输对象(DTOs)。这些将使请求、控制器和存储库之间的数据交换更容易、更整洁。
using System;
using System.ComponentModel.DataAnnotations;
using TodoAPI.Domain.Entities;
namespace TodoAPI.DTOs
{
public class CreateTodoDTO
{
[Required]
public Guid UserId { get; set; }
[Required]
public String Title { get; set; }
[Required]
public String Description { get; set; }
}
public class UpdateTodoDTO
{
[Required]
public String Title { get; set; }
[Required]
public String Description { get; set; }
public TodoStatus Status { get; set; }
}
}
在Domain/Repositories/ 文件夹中,添加以下文件。
using System.Collections.Generic;
using System.Threading.Tasks;
using TodoAPI.DTOs;
using TodoAPI.Domain.Entities;
using System;
namespace TodoAPI.Domain.Repositories
{
public interface ITodoRepository
{
public Task Create(CreateTodoDTO createTodoDTO, Guid userId);
public Task<IEnumerable<TodoItem>> GetAll();
public Task<TodoItem> GetById(Guid id);
public Task<IEnumerable<TodoItem>> GetByUser(Guid id);
public Task Update(UpdateTodoDTO projectDTO, Guid id);
public Task Delete(Guid id);
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using TodoAPI.Domain.Entities;
namespace TodoAPI.Domain.Repositories
{
public interface IUserRepository
{
public Task<IEnumerable<User>> GetAll();
public Task<User> GetById(Guid id);
}
}
这些方法将在Dapper的帮助下与我们的数据库直接通信。我们实际的存储库类将继承这些接口。
现在在你的Data 文件夹中创建一个文件夹Repositories 。在这个文件夹中,我们将添加我们的存储库。这些类将实现我们先前在Domain/Repositories/ 文件夹中添加的接口。这可以确保我们使用正确的方法来与我们的数据库进行通信。这个文件夹将容纳以下文件。
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoAPI.DTOs;
using TodoAPI.Domain.Entities;
using TodoAPI.Domain.Repositories;
using System;
using Dapper;
using System.Data;
namespace TodoAPI.Data.Repositories
{
public class UserRepository : IUserRepository
{
private readonly DapperContext _context;
public UserRepository(DapperContext context)
{
_context = context;
}
public async Task<IEnumerable<User>> GetAll()
{
string sqlQuery = "SELECT * FROM Users";
using(var connection = _context.CreateConnection())
{
var users = await connection.QueryAsync<User>(sqlQuery);
return users.ToList();
}
}
public async Task<User> GetById(Guid id)
{
string sqlQuery = "SELECT * FROM Users WHERE Id = @Id";
using (var connection = _context.CreateConnection())
{
return await connection.QuerySingleAsync<User>(sqlQuery, new { Id = id });
}
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TodoAPI.DTOs;
using TodoAPI.Domain.Entities;
using TodoAPI.Domain.Repositories;
using System;
using Dapper;
using System.Data;
namespace TodoAPI.Data.Repositories
{
public class TodoRepository : ITodoRepository
{
private readonly DapperContext _context;
public TodoRepository(DapperContext context)
{
_context = context;
}
public async Task Create(CreateTodoDTO createTodoDTO, Guid userId)
{
string sqlQuery = "INSERT into Todos (UserId, Title, Description, Id, Status) values (@UserId, @Title, @Description, @Id, @Status)";
var parameters = new DynamicParameters();
parameters.Add("Title", createTodoDTO.Title, DbType.String);
parameters.Add("UserId", createTodoDTO.UserId, DbType.Guid);
parameters.Add("Description", createTodoDTO.Description, DbType.String);
parameters.Add("Status", TodoStatus.Todo, DbType.String);
parameters.Add("Id", Guid.NewGuid(), DbType.Guid);
Console.WriteLine(TodoStatus.Todo);
using (var connection = _context.CreateConnection())
{
var r = await connection.ExecuteAsync(sqlQuery, parameters);
Console.Write(r);
}
}
public async Task<IEnumerable<TodoItem>> GetAll()
{
string sqlQuery = "SELECT * FROM Todos";
using (var connection = _context.CreateConnection())
{
var todos = await connection.QueryAsync<TodoItem>(sqlQuery);
return todos.ToList();
}
}
public async Task<TodoItem> GetById(Guid id)
{
string sqlQuery = "SELECT * FROM Todos WHERE Id = @Id";
using (var connection = _context.CreateConnection())
{
var todo = await connection.QuerySingleAsync<TodoItem>(sqlQuery, new { Id = id });
return todo;
}
}
public async Task<IEnumerable<TodoItem>> GetByUser(Guid id)
{
string sqlQuery = "SELECT * FROM Todos WHERE UserId = @UserId";
using (var connection = _context.CreateConnection())
{
IEnumerable<TodoItem> todos = await connection.QueryAsync<TodoItem>(sqlQuery, new { UserId = id });
return todos;
}
}
public async Task Update(UpdateTodoDTO updateTodoDTO, Guid id)
{
string sqlQuery = "UPDATE Todos SET Title = @Title, Status = @Status, Description = @Description WHERE Id = @Id";
var parameters = new DynamicParameters();
parameters.Add("Title", updateTodoDTO.Title, DbType.String);
parameters.Add("Status", updateTodoDTO.Status, DbType.String);
parameters.Add("Description", updateTodoDTO.Description, DbType.String);
parameters.Add("Id", id, DbType.Guid);
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(sqlQuery, parameters);
}
}
public async Task Delete(Guid id)
{
string query = "DELETE FROM Todos WHERE Id = @Id";
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(query, new { Id = id });
}
}
}
}
Domain/Repositories/TodoRepository.cs 如上图所示,有六个,而且是正确的,因为它所实现的接口也有相同数量的方法。
以上面的Create 方法为例。
public async Task Create(CreateTodoDTO createTodoDTO, Guid userId)
{
string sqlQuery = "INSERT into Todos (UserId, Title, Description, Id, Status) values (@UserId, @Title, @Description, @Id, @Status)"; // SQL query that we want to execute with dapper
var parameters = new DynamicParameters();
parameters.Add("Title", createTodoDTO.Title, DbType.String);
parameters.Add("UserId", createTodoDTO.UserId, DbType.Guid);
parameters.Add("Description", createTodoDTO.Description, DbType.String);
parameters.Add("Status", TodoStatus.Todo, DbType.String);
parameters.Add("Id", Guid.NewGuid(), DbType.Guid);
using (var connection = _context.CreateConnection())
{
await connection.ExecuteAsync(sqlQuery, parameters);
}
}
GetAll 方法的工作原理与Create 方法类似,但使用了connection.QueryAsync<TodoItem>() 方法。这个方法查询所有的项目并将它们映射到TodoItem 对象。
GetById 使用 ,因为ID属性是唯一的。正如本文前面所说的,如果发现一个以上的元素,它会抛出一个错误。connection.QuerySingleAsync<TodoItem>()
GetByUser 将获取所有的项目,其 ,与查询参数中的UserId相同。像 使用 ,因为我们想获取多个项目。UserId GetAll connection.QueryAsync<TodoItem>()
Update 和Delete 方法与Create 工作类似。它们也使用connection.ExecuteAsync() 方法,因为它们不需要返回任何数据。
添加我们的控制器
在Controllers 文件夹中,添加以下文件。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using TodoAPI.Data.Repositories;
using TodoAPI.Domain.Repositories;
using TodoAPI.DTOs;
namespace TodoAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class TodosController : ControllerBase
{
private readonly ILogger<TodosController> _logger;
private readonly ITodoRepository _todosRepository;
public TodosController(ILogger<TodosController> logger, ITodoRepository todosRepository)
{
_logger = logger;
_todosRepository = todosRepository;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
try
{
var Data = await _todosRepository.GetAll();
return Ok(new {
Success = true,
Message = "All todo items returned.",
Data
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpGet]
[Route("{todoId}")]
public async Task<IActionResult> GetById(Guid todoId)
{
try
{
var todo = await _todosRepository.GetById(todoId);
if(todo == null) return NotFound();
return Ok(new {
success = true,
message = "One todo item returned.",
data = todo
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpGet]
[Route("users/{userId}")]
public async Task<IActionResult> GetByUserId(Guid userId)
{
try
{
var Data = await _todosRepository.GetByUser(userId);
return Ok(new {
Success = true,
Message = "Todo items returned.",
Data
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpPost]
public async Task<IActionResult> Create(CreateTodoDTO createTodoDTO, Guid userId)
{
try
{
await _todosRepository.Create(createTodoDTO, userId);
return Ok(new {
Success = true,
Message = "Todo item created."
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpPatch]
[Route("{todoId}")]
public async Task<IActionResult> Update(UpdateTodoDTO updateTodoDTO, Guid todoId)
{
try
{
await _todosRepository.Update(updateTodoDTO, todoId);
return Ok(new {
Success = true,
Message = "Todo item updated."
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpDelete]
[Route("{todoId}")]
public async Task<IActionResult> Delete(Guid todoId)
{
try
{
await _todosRepository.Delete(todoId);
return Ok(new {
Success = true,
Message = "Todo deleted."
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using TodoAPI.Data.Repositories;
using TodoAPI.Domain.Repositories;
namespace TodoAPI.Controllers
{
[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase // 1
{
private readonly ILogger<UsersController> _logger;
private readonly IUserRepository _userRepository;
public UsersController(ILogger<UsersController> logger, IUserRepository userRepository) //2
{
_logger = logger;
_userRepository = userRepository;
}
[HttpGet]
public async Task<IActionResult> GetAll()
{
try
{
var Data = await _userRepository.GetAll();
return Ok(new {
Success = true,
Message = "all users returned.",
Data
});
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
[HttpGet] //3
[Route("{userId}")] //4
public async Task<IActionResult> GetById(Guid userId) //5
{
try
{
var Data = await _userRepository.GetById(userId); //6
return Ok(new { //7
Success = true,
Message = "User fetched.",
Data
});
}
catch (Exception ex) //8
{
Console.WriteLine(ex.Message);
return StatusCode(500, ex.Message);
}
}
}
}
上面的文件负责从我们的请求中获取数据并作出响应。在我们之前定义的DTO的帮助下,实体之间的数据交换变得无缝。
使用上面的UsersController 类作为参考,注释#1是我们的类被定义的地方。它继承自上面看到的ControllerBase 类。ASP.NET是相当聪明的。它把 "Controller "前面的字母拿出来,并把这些方法映射到它们各自的端点上。在UsersController 的情况下,它将这些方法映射到users 路径。
在#2,这就是我们的依赖关系被注入的地方。这是有可能的,因为这些依赖已经在早期被注册为服务。
看一下GetById ,注释#3表示这个方法所接受的方法。它是一个GET 方法。
#4告诉我们的方法,我们希望它回答的是什么路线。在一些像这样的情况下,我们也可以将路由中的数据作为参数传递。userId 是我们在这种情况下所期望的参数。
到#5,所需的数据userId ,已经从路由中解析出来了。
#6是我们使用_userRepository ,我们在#2之前注入的,来与数据库通信。
#7是我们将获取的数据作为一个代码为200的响应返回的地方。这是一个200的响应,因为它是用Ok() 来包装的。在这里了解更多关于dotnet API的响应。如果遇到任何错误,它将在#8处被捕获,并作为错误信息返回,状态代码为500。
测试端点
现在让我们启动我们的应用程序并使用Postman进行测试。
用户端点
[GET] /users- 获取所有用户

[GET] /users/{user_id}- 按ID获取用户。我们将用上面返回的结果中的一个用户的ID来测试它。

Todos端点
[GET] /todos- 获取所有的todo项目

[POST] /todos- 创建一个新的todo项目

[GET] /todos/{todo_id}- 通过ID获取todo项目。对于这一点,我们将使用新创建的项目的ID,该ID从上面的[POST] /todos 端点返回。这将测试我们的get-by-ID端点,同时确认该todo项目确实被添加到了数据库中。

[GET] /todos/users/{user_id}- 按用户ID获取todo项目

[PATCH] /todos- 更新一个todo项目

让我们再次尝试获取该项目,以确保它被适当地更新。

果然,正如我们在上面看到的,字段确实被更新了。
[DELETE] /todos- 删除一个todo项目

然后我们将尝试获取我们刚刚删除的那个项目。

我们可以看到,端点返回了一个404(Not Found) 错误,表明我们的项目找不到了,而这正是我们想要的。
奖励:记录我们的API
对于ASP.NET Core 5来说,它内置了对OpenAPI和Swagger UI的支持。你所要做的就是导航到/swagger 。你应该看到与下面的图片类似的东西。

结语
这篇文章向你介绍了对象关系映射器(ORMs)以及它们是如何工作的。更具体地说,它解释了什么是Dapper以及为什么你要使用它。
我们还亲身体验了如何将Dapper集成到ASP.NET Core API中。作为奖励,我们看到如何为我们的API生成Swagger文档。