按业务递进顺序推进路线Dapper 接入 → 通用分页 DTO 封装 → RabbitMQ 消息队列 → 部署 配置优化循序渐进贴合项目实战流程。目标EF 负责常规 CRUDDapper 负责联表、分页、复杂 SQL二者混用不冲突。1. 确认 NuGet 包DeviceRepair.Infrastructure已提前安装DapperMicrosoft.EntityFrameworkCore.SqlServer无需重复安装。2. 改造通用 Dapper 工具类路径DeviceRepair.Infrastructure/Utils/DapperHelper.cs整合连接串读取、常用查询方法兼容现有配置using Dapper; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using System.Data; namespace DeviceRepair.Infrastructure.Utils { public static class DapperHelper { private static string _connStr string.Empty; /// summary /// 初始化连接字符串 /// /summary public static void Init(IConfiguration config) { _connStr config.GetConnectionString(SqlServer) ?? string.Empty; } /// summary /// 获取数据库连接 /// /summary private static IDbConnection GetConnection() { return new SqlConnection(_connStr); } /// summary /// 单表/多表查询 /// /summary public static async TaskListT QueryAsyncT(string sql, object? param null) { using var conn GetConnection(); return (await conn.QueryAsyncT(sql, param)).ToList(); } /// summary /// 执行增删改SQL /// /summary public static async Taskint ExecuteAsync(string sql, object? param null) { using var conn GetConnection(); return await conn.ExecuteAsync(sql, param); } } }3. 初始化 Dapper统一入口打开ServiceCollectionExtensions.cs在AddDbAndRepositories方法末尾添加初始化public static void AddDbAndRepositories(this IServiceCollection services, IConfiguration config) { string connStr config.GetConnectionString(SqlServer)!; services.AddDbContextAppDbContext(opt { opt.UseSqlServer(connStr); }); // 注册全部仓储 services.AddScopedIUserRepository, UserRepository(); services.AddScopedIBaseRepositoryDevice, DeviceRepository(); services.AddScopedIBaseRepositoryRepairOrder, RepairOrderRepository(); services.AddScopedIBaseRepositorySystemMessage, SystemMessageRepository(); // 初始化 Dapper 连接串 DapperHelper.Init(config); }4. 扩展仓储新增 Dapper 专属接口4.1 新建分页接口针对工单做联表查询工单 提交人 设备作为复杂查询示例using DeviceRepair.Core.Dtos; namespace DeviceRepair.Core.Interfaces { public interface IOrderDapperRepository { /// summary /// 工单联表分页查询 /// /summary TaskPageResultRepairOrderDto GetOrderPageListAsync(PageQuery dto); } }4.2 新建 Dapper 实体 / 查询 DTO先提前创建通用分页 DTOnamespace DeviceRepair.Core.Dtos { /// summary /// 分页入参 /// /summary //用途前端做分页列表时传给后端的参数。 public class PageQuery { public int PageIndex { get; set; } 1; // 当前页码默认第1页 public int PageSize { get; set; } 10; // 每页几条数据默认每页10条 } /// summary /// 分页出参 /// /summary /// typeparam nameT数据实体/typeparam //作用后端查完数据把「总条数、当前页、每页大小、数据列表」打包一起返回给前端前端就能渲染分页控件 列表。 public class PageResultT { public int Total { get; set; } // 数据总条数 public int PageIndex { get; set; } // 当前页码 public int PageSize { get; set; } // 每页条数 public ListT Data { get; set; } new ListT(); // 当前页的数据列表 } /// summary /// 工单联表展示DTO,数据库里工单表、用户表、设备表是分开的联表查询后不能直接返回数据库实体字段冗余、不安全所以单独建这个类。 /// /summary public class RepairOrderDto {//专门存放「多张表联查后」要展示给前端的字段只保留页面需要的内容。 public int Id { get; set; } public string UserName { get; set; } string.Empty; // 提交工单的用户名 public string DeviceName { get; set; } string.Empty; // 故障设备名 public string ProblemDesc { get; set; } string.Empty;// 故障描述 public int OrderStatus { get; set; } // 工单状态码 public DateTime CreateTime { get; set; } // 工单创建时间 } }4.3 Dapper 仓储实现路径DeviceRepair.Infrastructure/Repositories/OrderDapperRepository.csusing DeviceRepair.Core.Dtos; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; namespace DeviceRepair.Infrastructure.Repositories { public class OrderDapperRepository : IOrderDapperRepository { public async TaskPageResultRepairOrderDto GetOrderPageListAsync(PageQuery dto) { var pageResult new PageResultRepairOrderDto { PageIndex dto.PageIndex, PageSize dto.PageSize }; // 1. 查询总条数 string countSql SELECT COUNT(1) FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0; pageResult.Total await DapperHelper.ExecuteAsync(countSql); // 2. 分页联表查询 int skip (dto.PageIndex - 1) * dto.PageSize; string dataSql $ SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0 ORDER BY o.CreateTime DESC OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY; pageResult.Data await DapperHelper.QueryAsyncRepairOrderDto(dataSql); return pageResult; } } }int skip (dto.PageIndex - 1) * dto.PageSize;第 1 页(1-1)*10 0→ 跳过 0 条取前 10 条第 2 页(2-1)*10 10→ 跳过前 10 条取接下来 10 条第 3 页(3-1)*10 20→ 跳过前 20 条skip 要跳过多少条数据。4.4 注册 Dapper 仓储ServiceCollectionExtensions.cs追加注册// Dapper 专属仓储 services.AddScopedIOrderDapperRepository, OrderDapperRepository();5. 新增分页接口控制器测试打开RepairOrderController.cs添加分页接口private readonly IBaseRepositoryRepairOrder _orderRepo; // 注入Dapper仓储 private readonly IOrderDapperRepository _orderDapperRepo; public RepairOrderController(IBaseRepositoryRepairOrder orderRepo, IOrderDapperRepository orderDapperRepo) { _orderRepo orderRepo; _orderDapperRepo orderDapperRepo; } /// summary /// 工单联表分页查询Dapper实现 /// /summary [HttpGet(page)] public async TaskActionResultResultModelPageResultRepairOrderDto GetPageList([FromQuery] PageQuery dto) { var data await _orderDapperRepo.GetOrderPageListAsync(dto); return Ok(ResultModelPageResultRepairOrderDto.Success(data)); }6. 测试重启项目Swagger 调用/api/v1/RepairOrder/page接口传入分页参数验证联表 分页数据正常返回。阶段二通用分页 DTO 规范化封装基于上一阶段的分页基础统一入参 / 出参 DTO 规范、全局分页逻辑、实体与 DTO 映射规则解决实体直接返回前端的安全问题。统一区分InputDto入参、OutputDto出参禁止实体直接对外暴露封装通用分页基类全模块复用简单映射手动映射后续可扩展 AutoMapper改造现有接口全部使用 DTO 交互核心目标隔离数据库实体、统一入参 / 出参规范、通用分页复用、规范字段映射禁止直接把实体返回前端兼顾安全性与可维护性。一、目录结构调整统一 DTO 分层在DeviceRepair.Core/Dtos下拆分目录区分入参、出参、通用模型DeviceRepair.Core ├─ Entities ├─ Enums ├─ Interfaces ├─ Dtos │ ├─ Common # 通用模型分页、全局请求/响应 │ ├─ Input # 接口入参 DTO │ └─ Output # 接口出参 DTO └─ Utils二、封装通用基础模型Common1. 通用分页模型全局复用namespace DeviceRepair.Core.Dtos.Common { /// summary /// 分页查询入参所有分页接口统一使用 /// /summary public class PageQuery { /// summary /// 页码默认第1页 /// /summary public int PageIndex { get; set; } 1; /// summary /// 页大小默认10条 /// /summary public int PageSize { get; set; } 10; } /// summary /// 分页结果出参所有分页接口统一返回 /// /summary /// typeparam nameT列表项实体/DTO/typeparam public class PageResultT { /// summary /// 总数据条数 /// /summary public int Total { get; set; } /// summary /// 当前页码 /// /summary public int PageIndex { get; set; } /// summary /// 每页条数 /// /summary public int PageSize { get; set; } /// summary /// 数据列表 /// /summary public ListT Data { get; set; } new ListT(); } }2. 全局基础 DTO可选后续扩展查询条件可用新建Dtos/Common/BaseQuery.csnamespace DeviceRepair.Core.Dtos.Common { /// summary /// 通用查询基类可扩展关键字搜索、时间范围等 /// /summary public class BaseQuery : PageQuery { /// summary /// 关键字模糊查询 /// /summary public string? Keyword { get; set; } } }三、按模块拆分 入参 DTOInput把原有接口实体传参、零散 DTO 全部迁移至Input禁止控制器直接接收 Entity。1. 账号模块 Input新建Dtos/Input/Account/LoginInput.csnamespace DeviceRepair.Core.Dtos.Input.Account { /// summary /// 登录入参 /// /summary public class LoginInput { public string UserName { get; set; } string.Empty; public string Password { get; set; } string.Empty; } }2. 设备模块 Input新建Dtos/Input/Device/DeviceInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.Device { /// summary /// 新增/编辑设备入参 /// /summary public class DeviceInput { public int Id { get; set; } [Required(ErrorMessage 设备名称不能为空)] [MaxLength(100, ErrorMessage 设备名称最长100字符)] public string DeviceName { get; set; } string.Empty; [MaxLength(50)] public string? DeviceType { get; set; } [MaxLength(100)] public string? Location { get; set; } } }3. 工单模块 Input新建Dtos/Input/RepairOrder/OrderInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.RepairOrder { /// summary /// 新增/编辑工单入参 /// /summary public class OrderInput { public int Id { get; set; } [Required(ErrorMessage 请选择用户)] public int UserId { get; set; } [Required(ErrorMessage 请选择设备)] public int DeviceId { get; set; } [Required(ErrorMessage 问题描述不能为空)] [MaxLength(500)] public string ProblemDesc { get; set; } string.Empty; public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } } }4. 消息模块 Input新建Dtos/Input/Message/MessageInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.Message { /// summary /// 新增/编辑消息入参 /// /summary public class MessageInput { public int Id { get; set; } [Required] public int ReceiveUserId { get; set; } [Required] [MaxLength(200)] public string Content { get; set; } string.Empty; } }四、按模块拆分 出参 DTOOutput创建对外输出模型过滤敏感字段如密码、精简冗余字段前端只拿需要的数据。1. 账号模块 Output新建Dtos/Output/Account/UserOutput.csusing DeviceRepair.Core.Enums; namespace DeviceRepair.Core.Dtos.Output.Account { /// summary /// 用户信息出参屏蔽密码 /// /summary public class UserOutput { public int Id { get; set; } public string UserName { get; set; } string.Empty; public string? RealName { get; set; } public UserRole Role { get; set; } public DateTime CreateTime { get; set; } } }2. 设备模块 Output新建Dtos/Output/Device/DeviceOutput.csnamespace DeviceRepair.Core.Dtos.Output.Device { /// summary /// 设备信息出参 /// /summary public class DeviceOutput { public int Id { get; set; } public string DeviceName { get; set; } string.Empty; public string? DeviceType { get; set; } public string? Location { get; set; } } }3. 工单模块 Output含联表分页 DTO新建Dtos/Output/RepairOrder/OrderOutput.csnamespace DeviceRepair.Core.Dtos.Output.RepairOrder { /// summary /// 工单基础信息出参 /// /summary public class OrderOutput { public int Id { get; set; } public int UserId { get; set; } public int DeviceId { get; set; } public string ProblemDesc { get; set; } string.Empty; public int OrderStatus { get; set; } public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } public DateTime CreateTime { get; set; } public DateTime? FinishTime { get; set; } } /// summary /// 工单联表分页出参Dapper 专用 /// /summary public class OrderPageOutput { public int Id { get; set; } public string UserName { get; set; } string.Empty; public string DeviceName { get; set; } string.Empty; public string ProblemDesc { get; set; } string.Empty; public int OrderStatus { get; set; } public DateTime CreateTime { get; set; } } }4. 消息模块 Output新建Dtos/Output/Message/MessageOutput.csnamespace DeviceRepair.Core.Dtos.Output.Message { /// summary /// 系统消息出参 /// /summary public class MessageOutput { public int Id { get; set; } public int ReceiveUserId { get; set; } public string Content { get; set; } string.Empty; public bool IsRead { get; set; } public DateTime CreateTime { get; set; } } }五、统一实体 ↔ DTO 映射规则现阶段使用手动映射轻量化无第三方组件约定映射位置Controller 层简单字段映射单实体Repository/Service 层复杂联表、分页映射禁止在视图 / 前端直接使用数据库实体扩展可复用映射静态类可选新建DeviceRepair.Core/Utils/ModelMapper.cs集中写映射方法全局调用using DeviceRepair.Core.Entities; using DeviceRepair.Core.Dtos.Input.Device; using DeviceRepair.Core.Dtos.Input.RepairOrder; using DeviceRepair.Core.Dtos.Input.Message; using DeviceRepair.Core.Dtos.Output.Account; using DeviceRepair.Core.Dtos.Output.Device; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Dtos.Output.Message; namespace DeviceRepair.Core.Utils { /// summary /// 实体与DTO手动映射工具 /// /summary public static class ModelMapper { #region 用户映射 public static UserOutput ToOutput(this User entity) { return new UserOutput { Id entity.Id, UserName entity.UserName, RealName entity.RealName, Role entity.Role, CreateTime entity.CreateTime }; } #endregion #region 设备映射 public static Device ToEntity(this DeviceInput dto) { return new Device { Id dto.Id, DeviceName dto.DeviceName, DeviceType dto.DeviceType, Location dto.Location }; } public static DeviceOutput ToOutput(this Device entity) { return new DeviceOutput { Id entity.Id, DeviceName entity.DeviceName, DeviceType entity.DeviceType, Location entity.Location }; } #endregion #region 工单映射 public static RepairOrder ToEntity(this OrderInput dto) { return new RepairOrder { Id dto.Id, UserId dto.UserId, DeviceId dto.DeviceId, ProblemDesc dto.ProblemDesc, RepairUserId dto.RepairUserId, RepairRemark dto.RepairRemark }; } public static OrderOutput ToOutput(this RepairOrder entity) { return new OrderOutput { Id entity.Id, UserId entity.UserId, DeviceId entity.DeviceId, ProblemDesc entity.ProblemDesc, OrderStatus entity.OrderStatus, RepairUserId entity.RepairUserId, RepairRemark entity.RepairRemark, CreateTime entity.CreateTime, FinishTime entity.FinishTime }; } #endregion #region 消息映射 public static SystemMessage ToEntity(this MessageInput dto) { return new SystemMessage { Id dto.Id, ReceiveUserId dto.ReceiveUserId, Content dto.Content }; } public static MessageOutput ToOutput(this SystemMessage entity) { return new MessageOutput { Id entity.Id, ReceiveUserId entity.ReceiveUserId, Content entity.Content, IsRead entity.IsRead, CreateTime entity.CreateTime }; } #endregion } }你现在这种手动映射当前代码优点无第三方依赖、灵活、可控、排查问题简单小 / 中型项目常用。缺点字段多了会写很多重复赋值代码。行业常用进阶AutoMapper 自动映射第三方库配置一行代码就能自动拷贝同名字段不用手写A.XXB.XX。 你现在是手动版映射是学习基础。六、改造原有接口全部切换为 DTO 交互逐个修改控制器替换入参、出参使用映射方法删除直接返回实体的写法。1. AccountController 改造using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; using DeviceRepair.Core.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class AccountController : ControllerBase { private readonly IAccountService _accountService; public AccountController(IAccountService accountService) { _accountService accountService; } /// summary /// 登录 /// /summary [HttpPost(login)] [AllowAnonymous] public async TaskActionResultResultModelstring Login([FromBody] LoginInput dto) { var result await _accountService.LoginAsync(dto); return Ok(result); } } }同步修改IAccountService和AccountService入参IAccountService.csusing DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; namespace DeviceRepair.Core.Interfaces { public interface IAccountService { TaskResultModelstring LoginAsync(LoginInput dto); } }AccountService.csusing DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using Microsoft.Extensions.Configuration; namespace DeviceRepair.Services.Modules.Account { public class AccountService : IAccountService { private readonly IUserRepository _userRepo; private readonly IConfiguration _config; public AccountService(IUserRepository userRepo, IConfiguration config) { _userRepo userRepo; _config config; } public async TaskResultModelstring LoginAsync(LoginInput dto) { var user await _userRepo.CheckLoginAsync(dto.UserName, dto.Password); if (user null) { return ResultModelstring.Fail(账号或密码错误, 401); } var secret _config[Jwt:SecretKey]!; var expire int.Parse(_config[Jwt:ExpireMinutes]!); var token JwtHelper.GenerateToken(user, secret, expire); return ResultModelstring.Success(token, 登录成功); } } }2. DeviceController 改造using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Input.Device; using DeviceRepair.Core.Dtos.Output.Device; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Enums; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using DeviceRepair.Api.Filters; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class DeviceController : ControllerBase { private readonly IBaseRepositoryDevice _deviceRepo; public DeviceController(IBaseRepositoryDevice deviceRepo) { _deviceRepo deviceRepo; } [HttpGet] public async TaskActionResultResultModelListDeviceOutput GetList() { var list await _deviceRepo.GetListAsync(); var outputList list.Select(x x.ToOutput()).ToList(); return Ok(ResultModelListDeviceOutput.Success(outputList)); } [HttpGet({id})] public async TaskActionResultResultModelDeviceOutput? GetById(int id) { var entity await _deviceRepo.GetByIdAsync(id); if (entity null) return Ok(ResultModelDeviceOutput?.Fail(数据不存在)); return Ok(ResultModelDeviceOutput?.Success(entity.ToOutput())); } [HttpPost] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Add([FromBody] DeviceInput dto) { var entity dto.ToEntity(); await _deviceRepo.AddAsync(entity); return Ok(ResultModelbool.Success(true, 新增成功)); } [HttpPut] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Update([FromBody] DeviceInput dto) { var entity dto.ToEntity(); await _deviceRepo.UpdateAsync(entity); return Ok(ResultModelbool.Success(true, 修改成功)); } [HttpDelete({id})] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Delete(int id) { await _deviceRepo.DeleteAsync(id); return Ok(ResultModelbool.Success(true, 删除成功)); } } }3. RepairOrderController 改造含 Dapper 分页using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Input.RepairOrder; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Enums; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using DeviceRepair.Api.Filters; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class RepairOrderController : ControllerBase { private readonly IBaseRepositoryRepairOrder _orderRepo; private readonly IOrderDapperRepository _orderDapperRepo; public RepairOrderController(IBaseRepositoryRepairOrder orderRepo, IOrderDapperRepository orderDapperRepo) { _orderRepo orderRepo; _orderDapperRepo orderDapperRepo; } [HttpGet] public async TaskActionResultResultModelListOrderOutput GetList() { var list await _orderRepo.GetListAsync(); var output list.Select(x x.ToOutput()).ToList(); return Ok(ResultModelListOrderOutput.Success(output)); } [HttpGet({id})] public async TaskActionResultResultModelOrderOutput? GetById(int id) { var entity await _orderRepo.GetByIdAsync(id); if (entity null) return Ok(ResultModelOrderOutput?.Fail(工单不存在)); return Ok(ResultModelOrderOutput?.Success(entity.ToOutput())); } [HttpPost] [AllowRole(UserRole.Staff, UserRole.RepairMan)] public async TaskActionResultResultModelbool Add([FromBody] OrderInput dto) { var entity dto.ToEntity(); entity.CreateTime DateTime.Now; entity.OrderStatus OrderStatus.WaitAccept; await _orderRepo.AddAsync(entity); return Ok(ResultModelbool.Success(true, 工单提交成功)); } [HttpPut] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Update([FromBody] OrderInput dto) { var entity dto.ToEntity(); await _orderRepo.UpdateAsync(entity); return Ok(ResultModelbool.Success(true, 工单修改成功)); } [HttpDelete({id})] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Delete(int id) { await _orderRepo.DeleteAsync(id); return Ok(ResultModelbool.Success(true, 工单删除成功)); } /// summary /// Dapper 联表分页查询 /// /summary [HttpGet(page)] public async TaskActionResultResultModelPageResultOrderPageOutput GetPageList([FromQuery] PageQuery dto) { var pageData await _orderDapperRepo.GetOrderPageListAsync(dto); return Ok(ResultModelPageResultOrderPageOutput.Success(pageData)); } } }同步修改IOrderDapperRepository和OrderDapperRepository中 DTO 引用IOrderDapperRepository.csusing DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Output.RepairOrder; namespace DeviceRepair.Core.Interfaces { public interface IOrderDapperRepository { TaskPageResultOrderPageOutput GetOrderPageListAsync(PageQuery dto); } }OrderDapperRepository.csusing DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; namespace DeviceRepair.Infrastructure.Repositories { public class OrderDapperRepository : IOrderDapperRepository { public async TaskPageResultOrderPageOutput GetOrderPageListAsync(PageQuery dto) { var pageResult new PageResultOrderPageOutput { PageIndex dto.PageIndex, PageSize dto.PageSize }; string countSql SELECT COUNT(1) FROM RepairOrders o LEFT JOIN Users u ON o.UserIdu.Id LEFT JOIN Devices d ON o.DeviceIdd.Id WHERE o.IsDeleted0; pageResult.Total await DapperHelper.ExecuteAsync(countSql); int skip (dto.PageIndex - 1) * dto.PageSize; string dataSql $ SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0 ORDER BY o.CreateTime DESC OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY; pageResult.Data await DapperHelper.QueryAsyncOrderPageOutput(dataSql); return pageResult; } } }4. SystemMessageController 改造自行参照上面规则完成规则一致入参用MessageInput、出参用MessageOutput调用ModelMapper映射。七、测试验证清理并重新生成解决方案确认无编译报错启动项目打开 Swagger全流程测试登录接口接收LoginInput正常返回 Token设备 / 工单 / 消息 增删改查入参校验生效、返回精简 DTO、无敏感字段分页接口传入PageIndex/PageSize正常返回分页结构体检查数据库实体不再直接暴露给前端安全性达标本阶段总结✅ DTO 分层规范Common/Input/Output ✅ 通用分页模型全局复用 ✅ 实体与 DTO 手动映射工具类统一管理 ✅ 所有接口完成 DTO 改造隔离数据库实体 ✅ 参数校验、字段长度约束统一配置
5-Dapper(与 EF Core 共存)
发布时间:2026/6/14 23:10:57
按业务递进顺序推进路线Dapper 接入 → 通用分页 DTO 封装 → RabbitMQ 消息队列 → 部署 配置优化循序渐进贴合项目实战流程。目标EF 负责常规 CRUDDapper 负责联表、分页、复杂 SQL二者混用不冲突。1. 确认 NuGet 包DeviceRepair.Infrastructure已提前安装DapperMicrosoft.EntityFrameworkCore.SqlServer无需重复安装。2. 改造通用 Dapper 工具类路径DeviceRepair.Infrastructure/Utils/DapperHelper.cs整合连接串读取、常用查询方法兼容现有配置using Dapper; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using System.Data; namespace DeviceRepair.Infrastructure.Utils { public static class DapperHelper { private static string _connStr string.Empty; /// summary /// 初始化连接字符串 /// /summary public static void Init(IConfiguration config) { _connStr config.GetConnectionString(SqlServer) ?? string.Empty; } /// summary /// 获取数据库连接 /// /summary private static IDbConnection GetConnection() { return new SqlConnection(_connStr); } /// summary /// 单表/多表查询 /// /summary public static async TaskListT QueryAsyncT(string sql, object? param null) { using var conn GetConnection(); return (await conn.QueryAsyncT(sql, param)).ToList(); } /// summary /// 执行增删改SQL /// /summary public static async Taskint ExecuteAsync(string sql, object? param null) { using var conn GetConnection(); return await conn.ExecuteAsync(sql, param); } } }3. 初始化 Dapper统一入口打开ServiceCollectionExtensions.cs在AddDbAndRepositories方法末尾添加初始化public static void AddDbAndRepositories(this IServiceCollection services, IConfiguration config) { string connStr config.GetConnectionString(SqlServer)!; services.AddDbContextAppDbContext(opt { opt.UseSqlServer(connStr); }); // 注册全部仓储 services.AddScopedIUserRepository, UserRepository(); services.AddScopedIBaseRepositoryDevice, DeviceRepository(); services.AddScopedIBaseRepositoryRepairOrder, RepairOrderRepository(); services.AddScopedIBaseRepositorySystemMessage, SystemMessageRepository(); // 初始化 Dapper 连接串 DapperHelper.Init(config); }4. 扩展仓储新增 Dapper 专属接口4.1 新建分页接口针对工单做联表查询工单 提交人 设备作为复杂查询示例using DeviceRepair.Core.Dtos; namespace DeviceRepair.Core.Interfaces { public interface IOrderDapperRepository { /// summary /// 工单联表分页查询 /// /summary TaskPageResultRepairOrderDto GetOrderPageListAsync(PageQuery dto); } }4.2 新建 Dapper 实体 / 查询 DTO先提前创建通用分页 DTOnamespace DeviceRepair.Core.Dtos { /// summary /// 分页入参 /// /summary //用途前端做分页列表时传给后端的参数。 public class PageQuery { public int PageIndex { get; set; } 1; // 当前页码默认第1页 public int PageSize { get; set; } 10; // 每页几条数据默认每页10条 } /// summary /// 分页出参 /// /summary /// typeparam nameT数据实体/typeparam //作用后端查完数据把「总条数、当前页、每页大小、数据列表」打包一起返回给前端前端就能渲染分页控件 列表。 public class PageResultT { public int Total { get; set; } // 数据总条数 public int PageIndex { get; set; } // 当前页码 public int PageSize { get; set; } // 每页条数 public ListT Data { get; set; } new ListT(); // 当前页的数据列表 } /// summary /// 工单联表展示DTO,数据库里工单表、用户表、设备表是分开的联表查询后不能直接返回数据库实体字段冗余、不安全所以单独建这个类。 /// /summary public class RepairOrderDto {//专门存放「多张表联查后」要展示给前端的字段只保留页面需要的内容。 public int Id { get; set; } public string UserName { get; set; } string.Empty; // 提交工单的用户名 public string DeviceName { get; set; } string.Empty; // 故障设备名 public string ProblemDesc { get; set; } string.Empty;// 故障描述 public int OrderStatus { get; set; } // 工单状态码 public DateTime CreateTime { get; set; } // 工单创建时间 } }4.3 Dapper 仓储实现路径DeviceRepair.Infrastructure/Repositories/OrderDapperRepository.csusing DeviceRepair.Core.Dtos; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; namespace DeviceRepair.Infrastructure.Repositories { public class OrderDapperRepository : IOrderDapperRepository { public async TaskPageResultRepairOrderDto GetOrderPageListAsync(PageQuery dto) { var pageResult new PageResultRepairOrderDto { PageIndex dto.PageIndex, PageSize dto.PageSize }; // 1. 查询总条数 string countSql SELECT COUNT(1) FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0; pageResult.Total await DapperHelper.ExecuteAsync(countSql); // 2. 分页联表查询 int skip (dto.PageIndex - 1) * dto.PageSize; string dataSql $ SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0 ORDER BY o.CreateTime DESC OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY; pageResult.Data await DapperHelper.QueryAsyncRepairOrderDto(dataSql); return pageResult; } } }int skip (dto.PageIndex - 1) * dto.PageSize;第 1 页(1-1)*10 0→ 跳过 0 条取前 10 条第 2 页(2-1)*10 10→ 跳过前 10 条取接下来 10 条第 3 页(3-1)*10 20→ 跳过前 20 条skip 要跳过多少条数据。4.4 注册 Dapper 仓储ServiceCollectionExtensions.cs追加注册// Dapper 专属仓储 services.AddScopedIOrderDapperRepository, OrderDapperRepository();5. 新增分页接口控制器测试打开RepairOrderController.cs添加分页接口private readonly IBaseRepositoryRepairOrder _orderRepo; // 注入Dapper仓储 private readonly IOrderDapperRepository _orderDapperRepo; public RepairOrderController(IBaseRepositoryRepairOrder orderRepo, IOrderDapperRepository orderDapperRepo) { _orderRepo orderRepo; _orderDapperRepo orderDapperRepo; } /// summary /// 工单联表分页查询Dapper实现 /// /summary [HttpGet(page)] public async TaskActionResultResultModelPageResultRepairOrderDto GetPageList([FromQuery] PageQuery dto) { var data await _orderDapperRepo.GetOrderPageListAsync(dto); return Ok(ResultModelPageResultRepairOrderDto.Success(data)); }6. 测试重启项目Swagger 调用/api/v1/RepairOrder/page接口传入分页参数验证联表 分页数据正常返回。阶段二通用分页 DTO 规范化封装基于上一阶段的分页基础统一入参 / 出参 DTO 规范、全局分页逻辑、实体与 DTO 映射规则解决实体直接返回前端的安全问题。统一区分InputDto入参、OutputDto出参禁止实体直接对外暴露封装通用分页基类全模块复用简单映射手动映射后续可扩展 AutoMapper改造现有接口全部使用 DTO 交互核心目标隔离数据库实体、统一入参 / 出参规范、通用分页复用、规范字段映射禁止直接把实体返回前端兼顾安全性与可维护性。一、目录结构调整统一 DTO 分层在DeviceRepair.Core/Dtos下拆分目录区分入参、出参、通用模型DeviceRepair.Core ├─ Entities ├─ Enums ├─ Interfaces ├─ Dtos │ ├─ Common # 通用模型分页、全局请求/响应 │ ├─ Input # 接口入参 DTO │ └─ Output # 接口出参 DTO └─ Utils二、封装通用基础模型Common1. 通用分页模型全局复用namespace DeviceRepair.Core.Dtos.Common { /// summary /// 分页查询入参所有分页接口统一使用 /// /summary public class PageQuery { /// summary /// 页码默认第1页 /// /summary public int PageIndex { get; set; } 1; /// summary /// 页大小默认10条 /// /summary public int PageSize { get; set; } 10; } /// summary /// 分页结果出参所有分页接口统一返回 /// /summary /// typeparam nameT列表项实体/DTO/typeparam public class PageResultT { /// summary /// 总数据条数 /// /summary public int Total { get; set; } /// summary /// 当前页码 /// /summary public int PageIndex { get; set; } /// summary /// 每页条数 /// /summary public int PageSize { get; set; } /// summary /// 数据列表 /// /summary public ListT Data { get; set; } new ListT(); } }2. 全局基础 DTO可选后续扩展查询条件可用新建Dtos/Common/BaseQuery.csnamespace DeviceRepair.Core.Dtos.Common { /// summary /// 通用查询基类可扩展关键字搜索、时间范围等 /// /summary public class BaseQuery : PageQuery { /// summary /// 关键字模糊查询 /// /summary public string? Keyword { get; set; } } }三、按模块拆分 入参 DTOInput把原有接口实体传参、零散 DTO 全部迁移至Input禁止控制器直接接收 Entity。1. 账号模块 Input新建Dtos/Input/Account/LoginInput.csnamespace DeviceRepair.Core.Dtos.Input.Account { /// summary /// 登录入参 /// /summary public class LoginInput { public string UserName { get; set; } string.Empty; public string Password { get; set; } string.Empty; } }2. 设备模块 Input新建Dtos/Input/Device/DeviceInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.Device { /// summary /// 新增/编辑设备入参 /// /summary public class DeviceInput { public int Id { get; set; } [Required(ErrorMessage 设备名称不能为空)] [MaxLength(100, ErrorMessage 设备名称最长100字符)] public string DeviceName { get; set; } string.Empty; [MaxLength(50)] public string? DeviceType { get; set; } [MaxLength(100)] public string? Location { get; set; } } }3. 工单模块 Input新建Dtos/Input/RepairOrder/OrderInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.RepairOrder { /// summary /// 新增/编辑工单入参 /// /summary public class OrderInput { public int Id { get; set; } [Required(ErrorMessage 请选择用户)] public int UserId { get; set; } [Required(ErrorMessage 请选择设备)] public int DeviceId { get; set; } [Required(ErrorMessage 问题描述不能为空)] [MaxLength(500)] public string ProblemDesc { get; set; } string.Empty; public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } } }4. 消息模块 Input新建Dtos/Input/Message/MessageInput.csusing System.ComponentModel.DataAnnotations; namespace DeviceRepair.Core.Dtos.Input.Message { /// summary /// 新增/编辑消息入参 /// /summary public class MessageInput { public int Id { get; set; } [Required] public int ReceiveUserId { get; set; } [Required] [MaxLength(200)] public string Content { get; set; } string.Empty; } }四、按模块拆分 出参 DTOOutput创建对外输出模型过滤敏感字段如密码、精简冗余字段前端只拿需要的数据。1. 账号模块 Output新建Dtos/Output/Account/UserOutput.csusing DeviceRepair.Core.Enums; namespace DeviceRepair.Core.Dtos.Output.Account { /// summary /// 用户信息出参屏蔽密码 /// /summary public class UserOutput { public int Id { get; set; } public string UserName { get; set; } string.Empty; public string? RealName { get; set; } public UserRole Role { get; set; } public DateTime CreateTime { get; set; } } }2. 设备模块 Output新建Dtos/Output/Device/DeviceOutput.csnamespace DeviceRepair.Core.Dtos.Output.Device { /// summary /// 设备信息出参 /// /summary public class DeviceOutput { public int Id { get; set; } public string DeviceName { get; set; } string.Empty; public string? DeviceType { get; set; } public string? Location { get; set; } } }3. 工单模块 Output含联表分页 DTO新建Dtos/Output/RepairOrder/OrderOutput.csnamespace DeviceRepair.Core.Dtos.Output.RepairOrder { /// summary /// 工单基础信息出参 /// /summary public class OrderOutput { public int Id { get; set; } public int UserId { get; set; } public int DeviceId { get; set; } public string ProblemDesc { get; set; } string.Empty; public int OrderStatus { get; set; } public int? RepairUserId { get; set; } public string? RepairRemark { get; set; } public DateTime CreateTime { get; set; } public DateTime? FinishTime { get; set; } } /// summary /// 工单联表分页出参Dapper 专用 /// /summary public class OrderPageOutput { public int Id { get; set; } public string UserName { get; set; } string.Empty; public string DeviceName { get; set; } string.Empty; public string ProblemDesc { get; set; } string.Empty; public int OrderStatus { get; set; } public DateTime CreateTime { get; set; } } }4. 消息模块 Output新建Dtos/Output/Message/MessageOutput.csnamespace DeviceRepair.Core.Dtos.Output.Message { /// summary /// 系统消息出参 /// /summary public class MessageOutput { public int Id { get; set; } public int ReceiveUserId { get; set; } public string Content { get; set; } string.Empty; public bool IsRead { get; set; } public DateTime CreateTime { get; set; } } }五、统一实体 ↔ DTO 映射规则现阶段使用手动映射轻量化无第三方组件约定映射位置Controller 层简单字段映射单实体Repository/Service 层复杂联表、分页映射禁止在视图 / 前端直接使用数据库实体扩展可复用映射静态类可选新建DeviceRepair.Core/Utils/ModelMapper.cs集中写映射方法全局调用using DeviceRepair.Core.Entities; using DeviceRepair.Core.Dtos.Input.Device; using DeviceRepair.Core.Dtos.Input.RepairOrder; using DeviceRepair.Core.Dtos.Input.Message; using DeviceRepair.Core.Dtos.Output.Account; using DeviceRepair.Core.Dtos.Output.Device; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Dtos.Output.Message; namespace DeviceRepair.Core.Utils { /// summary /// 实体与DTO手动映射工具 /// /summary public static class ModelMapper { #region 用户映射 public static UserOutput ToOutput(this User entity) { return new UserOutput { Id entity.Id, UserName entity.UserName, RealName entity.RealName, Role entity.Role, CreateTime entity.CreateTime }; } #endregion #region 设备映射 public static Device ToEntity(this DeviceInput dto) { return new Device { Id dto.Id, DeviceName dto.DeviceName, DeviceType dto.DeviceType, Location dto.Location }; } public static DeviceOutput ToOutput(this Device entity) { return new DeviceOutput { Id entity.Id, DeviceName entity.DeviceName, DeviceType entity.DeviceType, Location entity.Location }; } #endregion #region 工单映射 public static RepairOrder ToEntity(this OrderInput dto) { return new RepairOrder { Id dto.Id, UserId dto.UserId, DeviceId dto.DeviceId, ProblemDesc dto.ProblemDesc, RepairUserId dto.RepairUserId, RepairRemark dto.RepairRemark }; } public static OrderOutput ToOutput(this RepairOrder entity) { return new OrderOutput { Id entity.Id, UserId entity.UserId, DeviceId entity.DeviceId, ProblemDesc entity.ProblemDesc, OrderStatus entity.OrderStatus, RepairUserId entity.RepairUserId, RepairRemark entity.RepairRemark, CreateTime entity.CreateTime, FinishTime entity.FinishTime }; } #endregion #region 消息映射 public static SystemMessage ToEntity(this MessageInput dto) { return new SystemMessage { Id dto.Id, ReceiveUserId dto.ReceiveUserId, Content dto.Content }; } public static MessageOutput ToOutput(this SystemMessage entity) { return new MessageOutput { Id entity.Id, ReceiveUserId entity.ReceiveUserId, Content entity.Content, IsRead entity.IsRead, CreateTime entity.CreateTime }; } #endregion } }你现在这种手动映射当前代码优点无第三方依赖、灵活、可控、排查问题简单小 / 中型项目常用。缺点字段多了会写很多重复赋值代码。行业常用进阶AutoMapper 自动映射第三方库配置一行代码就能自动拷贝同名字段不用手写A.XXB.XX。 你现在是手动版映射是学习基础。六、改造原有接口全部切换为 DTO 交互逐个修改控制器替换入参、出参使用映射方法删除直接返回实体的写法。1. AccountController 改造using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; using DeviceRepair.Core.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class AccountController : ControllerBase { private readonly IAccountService _accountService; public AccountController(IAccountService accountService) { _accountService accountService; } /// summary /// 登录 /// /summary [HttpPost(login)] [AllowAnonymous] public async TaskActionResultResultModelstring Login([FromBody] LoginInput dto) { var result await _accountService.LoginAsync(dto); return Ok(result); } } }同步修改IAccountService和AccountService入参IAccountService.csusing DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; namespace DeviceRepair.Core.Interfaces { public interface IAccountService { TaskResultModelstring LoginAsync(LoginInput dto); } }AccountService.csusing DeviceRepair.Core; using DeviceRepair.Core.Dtos.Input.Account; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using Microsoft.Extensions.Configuration; namespace DeviceRepair.Services.Modules.Account { public class AccountService : IAccountService { private readonly IUserRepository _userRepo; private readonly IConfiguration _config; public AccountService(IUserRepository userRepo, IConfiguration config) { _userRepo userRepo; _config config; } public async TaskResultModelstring LoginAsync(LoginInput dto) { var user await _userRepo.CheckLoginAsync(dto.UserName, dto.Password); if (user null) { return ResultModelstring.Fail(账号或密码错误, 401); } var secret _config[Jwt:SecretKey]!; var expire int.Parse(_config[Jwt:ExpireMinutes]!); var token JwtHelper.GenerateToken(user, secret, expire); return ResultModelstring.Success(token, 登录成功); } } }2. DeviceController 改造using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Input.Device; using DeviceRepair.Core.Dtos.Output.Device; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Enums; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using DeviceRepair.Api.Filters; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class DeviceController : ControllerBase { private readonly IBaseRepositoryDevice _deviceRepo; public DeviceController(IBaseRepositoryDevice deviceRepo) { _deviceRepo deviceRepo; } [HttpGet] public async TaskActionResultResultModelListDeviceOutput GetList() { var list await _deviceRepo.GetListAsync(); var outputList list.Select(x x.ToOutput()).ToList(); return Ok(ResultModelListDeviceOutput.Success(outputList)); } [HttpGet({id})] public async TaskActionResultResultModelDeviceOutput? GetById(int id) { var entity await _deviceRepo.GetByIdAsync(id); if (entity null) return Ok(ResultModelDeviceOutput?.Fail(数据不存在)); return Ok(ResultModelDeviceOutput?.Success(entity.ToOutput())); } [HttpPost] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Add([FromBody] DeviceInput dto) { var entity dto.ToEntity(); await _deviceRepo.AddAsync(entity); return Ok(ResultModelbool.Success(true, 新增成功)); } [HttpPut] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Update([FromBody] DeviceInput dto) { var entity dto.ToEntity(); await _deviceRepo.UpdateAsync(entity); return Ok(ResultModelbool.Success(true, 修改成功)); } [HttpDelete({id})] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Delete(int id) { await _deviceRepo.DeleteAsync(id); return Ok(ResultModelbool.Success(true, 删除成功)); } } }3. RepairOrderController 改造含 Dapper 分页using DeviceRepair.Core; using DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Input.RepairOrder; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Entities; using DeviceRepair.Core.Enums; using DeviceRepair.Core.Interfaces; using DeviceRepair.Core.Utils; using DeviceRepair.Api.Filters; using Microsoft.AspNetCore.Mvc; namespace DeviceRepair.Api.Controllers { [Route(api/v1/[controller])] [ApiController] public class RepairOrderController : ControllerBase { private readonly IBaseRepositoryRepairOrder _orderRepo; private readonly IOrderDapperRepository _orderDapperRepo; public RepairOrderController(IBaseRepositoryRepairOrder orderRepo, IOrderDapperRepository orderDapperRepo) { _orderRepo orderRepo; _orderDapperRepo orderDapperRepo; } [HttpGet] public async TaskActionResultResultModelListOrderOutput GetList() { var list await _orderRepo.GetListAsync(); var output list.Select(x x.ToOutput()).ToList(); return Ok(ResultModelListOrderOutput.Success(output)); } [HttpGet({id})] public async TaskActionResultResultModelOrderOutput? GetById(int id) { var entity await _orderRepo.GetByIdAsync(id); if (entity null) return Ok(ResultModelOrderOutput?.Fail(工单不存在)); return Ok(ResultModelOrderOutput?.Success(entity.ToOutput())); } [HttpPost] [AllowRole(UserRole.Staff, UserRole.RepairMan)] public async TaskActionResultResultModelbool Add([FromBody] OrderInput dto) { var entity dto.ToEntity(); entity.CreateTime DateTime.Now; entity.OrderStatus OrderStatus.WaitAccept; await _orderRepo.AddAsync(entity); return Ok(ResultModelbool.Success(true, 工单提交成功)); } [HttpPut] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Update([FromBody] OrderInput dto) { var entity dto.ToEntity(); await _orderRepo.UpdateAsync(entity); return Ok(ResultModelbool.Success(true, 工单修改成功)); } [HttpDelete({id})] [AllowRole(UserRole.Admin)] public async TaskActionResultResultModelbool Delete(int id) { await _orderRepo.DeleteAsync(id); return Ok(ResultModelbool.Success(true, 工单删除成功)); } /// summary /// Dapper 联表分页查询 /// /summary [HttpGet(page)] public async TaskActionResultResultModelPageResultOrderPageOutput GetPageList([FromQuery] PageQuery dto) { var pageData await _orderDapperRepo.GetOrderPageListAsync(dto); return Ok(ResultModelPageResultOrderPageOutput.Success(pageData)); } } }同步修改IOrderDapperRepository和OrderDapperRepository中 DTO 引用IOrderDapperRepository.csusing DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Output.RepairOrder; namespace DeviceRepair.Core.Interfaces { public interface IOrderDapperRepository { TaskPageResultOrderPageOutput GetOrderPageListAsync(PageQuery dto); } }OrderDapperRepository.csusing DeviceRepair.Core.Dtos.Common; using DeviceRepair.Core.Dtos.Output.RepairOrder; using DeviceRepair.Core.Interfaces; using DeviceRepair.Infrastructure.Utils; namespace DeviceRepair.Infrastructure.Repositories { public class OrderDapperRepository : IOrderDapperRepository { public async TaskPageResultOrderPageOutput GetOrderPageListAsync(PageQuery dto) { var pageResult new PageResultOrderPageOutput { PageIndex dto.PageIndex, PageSize dto.PageSize }; string countSql SELECT COUNT(1) FROM RepairOrders o LEFT JOIN Users u ON o.UserIdu.Id LEFT JOIN Devices d ON o.DeviceIdd.Id WHERE o.IsDeleted0; pageResult.Total await DapperHelper.ExecuteAsync(countSql); int skip (dto.PageIndex - 1) * dto.PageSize; string dataSql $ SELECT o.Id, u.UserName, d.DeviceName, o.ProblemDesc, o.OrderStatus, o.CreateTime FROM RepairOrders o LEFT JOIN Users u ON o.UserId u.Id LEFT JOIN Devices d ON o.DeviceId d.Id WHERE o.IsDeleted 0 ORDER BY o.CreateTime DESC OFFSET {skip} ROWS FETCH NEXT {dto.PageSize} ROWS ONLY; pageResult.Data await DapperHelper.QueryAsyncOrderPageOutput(dataSql); return pageResult; } } }4. SystemMessageController 改造自行参照上面规则完成规则一致入参用MessageInput、出参用MessageOutput调用ModelMapper映射。七、测试验证清理并重新生成解决方案确认无编译报错启动项目打开 Swagger全流程测试登录接口接收LoginInput正常返回 Token设备 / 工单 / 消息 增删改查入参校验生效、返回精简 DTO、无敏感字段分页接口传入PageIndex/PageSize正常返回分页结构体检查数据库实体不再直接暴露给前端安全性达标本阶段总结✅ DTO 分层规范Common/Input/Output ✅ 通用分页模型全局复用 ✅ 实体与 DTO 手动映射工具类统一管理 ✅ 所有接口完成 DTO 改造隔离数据库实体 ✅ 参数校验、字段长度约束统一配置