如何使用Dapper建立一个API来进行查询

161 阅读12分钟

在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提供了一些方法,使我们可以很容易地与数据库进行交流。其中一些方法是。

  1. Execute:这个方法执行一个命令并返回受影响的行。它通常用于执行INSERT、UPDATE和DELETE操作。

  2. 查询:该方法执行一个查询并映射结果。它通常用于从数据库中获取多个对象。

  3. QueryFirst:该方法执行一个查询,并映射与查询中的参数相匹配的第一个结果。当我们只需要一个符合所提供规格的项目时,就可以使用这个方法。

  4. QueryFirstOrDefault:这个功能类似于QueryFirst ,但是如果序列不包含任何元素,则返回一个默认值。

  5. QuerySingle:它执行一个查询并映射结果,条件是序列中只有一个项目。如果序列中不正好有一个元素,当没有元素或有一个以上的元素被返回时,它会抛出一个异常。

  6. QuerySingleOrDefault:这个方法的工作原理与QuerySingle ,但是如果没有从数据库返回任何项目,则返回一个默认值。

  7. 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,RepositoriesDTOs

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

UpdateDelete 方法与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

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

get user

Todos端点

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

get todos

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

create todo

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

get todo

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

get todos by user

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

update todo

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

get todo after update

果然,正如我们在上面看到的,字段确实被更新了。

[DELETE] /todos- 删除一个todo项目

delete todo

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

get todo after delete

我们可以看到,端点返回了一个404(Not Found) 错误,表明我们的项目找不到了,而这正是我们想要的。

奖励:记录我们的API

对于ASP.NET Core 5来说,它内置了对OpenAPI和Swagger UI的支持。你所要做的就是导航到/swagger 。你应该看到与下面的图片类似的东西。

swagger docs

结语

这篇文章向你介绍了对象关系映射器(ORMs)以及它们是如何工作的。更具体地说,它解释了什么是Dapper以及为什么你要使用它。

我们还亲身体验了如何将Dapper集成到ASP.NET Core API中。作为奖励,我们看到如何为我们的API生成Swagger文档。