当前位置: 首页 > news >正文

C# ASP.NET MVC Model 分类:数据传输对象(DTO)—— 跨层传数的 “精简快递“

目录

    • 引言:为什么我们需要 "DTO" 这个角色?
    • 一、什么是 DTO?3 分钟搞懂核心定义
      • 1.1 DTO 的本质
      • 1.2 DTO 的 3 个核心作用(列表版)
    • 二、没有 DTO 会怎样?踩过的坑告诉你
    • 三、DTO 实战:代码例子带你落地
      • 3.1 先定义实体(Entity)
      • 3.2 设计对应的 DTO
      • 3.3 Entity 转 DTO:两种常用方式
        • 方式 1:手动转换(简单场景推荐)
        • 方式 2:用 AutoMapper 自动转换(复杂场景推荐)
      • 3.4 在 API 中返回 DTO
    • 四、DTO 数据流向:一张流程图看懂全局
    • 五、新手常踩的 5 个坑及解决方案
      • 坑 1:DTO 和 Entity 字段完全一致
      • 坑 2:转换时遗漏字段
      • 坑 3:DTO 中包含业务逻辑
      • 坑 4:过度设计 DTO
      • 坑 5:忽略 DTO 的验证
    • 六、总结:DTO 的 "三字经"
    • 提问:

引言:为什么我们需要 “DTO” 这个角色?

想象一个场景:你网购了一台手机,商家不会把生产线的原材料(芯片、屏幕、电池)直接打包发给你,而是组装成整机,去掉多余的包装和调试工具,只发你需要的手机 + 充电器 + 说明书 —— 这就是生活中的 “精简传输”。
在ASP.NET MVC 开发中,数据从数据库到前端的传递,就像这个快递过程:数据库里的实体(Entity)包含大量细节(比如用户表的密码哈希、创建时间戳),但前端可能只需要用户名和头像;跨服务调用时,服务 A 也不需要知道服务 B 的实体完整结构,只需要关键字段。
数据传输对象(DTO,Data Transfer Object) 就是这个 “精简包装” 的角色 —— 它只包含跨层 / 跨服务传输所需的必要数据,屏蔽冗余信息,让数据传递更高效、更安全。

在这里插入图片描述

一、什么是 DTO?3 分钟搞懂核心定义

1.1 DTO 的本质

DTO 是一个纯数据载体类,没有业务逻辑,仅包含属性(字段)和简单的 get/set 方法,用于在不同层(如服务层→API 层)或不同服务(如微服务 A→微服务 B)之间传递数据。

1.2 DTO 的 3 个核心作用(列表版)

精简数据: 只传输必要字段,减少网络带宽消耗(比如 Entity 有 10 个字段,DTO 只传 3 个);
隐藏敏感信息: 屏蔽实体中的敏感数据(如用户密码、身份证号);
解耦层间依赖: 前端 / 其他服务不需要依赖实体类的结构,避免实体修改影响外层(比如 Entity 加字段,DTO 可不变)。
本节小结: DTO 是数据传输的 “定制快递箱”,按需打包,只送必要内容,还能保护隐私。

二、没有 DTO 会怎样?踩过的坑告诉你

如果直接用数据库实体(Entity)跨层传输,会遇到这些问题:

  • 敏感信息泄露: 比如 User 实体包含PasswordHash,直接返回给前端可能被抓包获取;
  • 数据冗余: Entity 的CreateTime(DateTime 类型)、IsDeleted(布尔值)等字段对前端无用,却要占用传输资源;
  • 层间强耦合: 前端依赖 Entity 结构,一旦 Entity 改字段(如改UserName为Name),前端代码必须同步修改,维护成本高;
  • 适配困难: 前端需要CreateTime显示为 “2023-10-01”,但 Entity 是 DateTime 类型,直接传需要前端二次处理。
    本节小结: 不用 DTO,就像把原材料直接寄给客户 —— 既不安全,又麻烦,还容易出错。

三、DTO 实战:代码例子带你落地

3.1 先定义实体(Entity)

假设我们有一个用户实体,对应数据库表:

// 数据库实体(Entity):包含完整信息,有敏感字段
public class UserEntity
{public int Id { get; set; }          // 用户IDpublic string UserName { get; set; } // 用户名public string Email { get; set; }    // 邮箱public string PasswordHash { get; set; } // 密码哈希(敏感)public DateTime CreateTime { get; set; } // 创建时间(DateTime类型)public bool IsDeleted { get; set; }  // 是否删除(内部字段)
}

3.2 设计对应的 DTO

前端只需要展示用户 ID、用户名、邮箱和格式化的创建时间,因此 DTO 可以这样定义:

// DTO:仅包含前端需要的字段,适配展示需求
public class UserDTO
{public int Id { get; set; }          // 必要字段:用户IDpublic string UserName { get; set; } // 必要字段:用户名public string Email { get; set; }    // 必要字段:邮箱// 衍生字段:格式化后的创建时间,方便前端直接展示public string CreateTimeStr { get; set; } 
}

3.3 Entity 转 DTO:两种常用方式

数据从 Entity 到 DTO 需要 “转换”,就像把原材料加工成成品,常用两种方式:

方式 1:手动转换(简单场景推荐)
public class UserService
{// 从数据库获取实体后,手动转换为DTOpublic UserDTO GetUserDTO(int userId){// 1. 从数据库查询实体(模拟)var userEntity = _dbContext.Users.FirstOrDefault(u => u.Id == userId);if (userEntity == null)return null;// 2. 手动映射字段(核心步骤)return new UserDTO{Id = userEntity.Id,UserName = userEntity.UserName,Email = userEntity.Email,// 格式化时间,前端直接用CreateTimeStr = userEntity.CreateTime.ToString("yyyy-MM-dd HH:mm")};}
}
方式 2:用 AutoMapper 自动转换(复杂场景推荐)

当 DTO 和 Entity 字段较多时,手动转换繁琐,可使用 AutoMapper 工具:
安装 NuGet 包: AutoMapper 和 AutoMapper.Extensions.Microsoft.DependencyInjection;
配置映射关系:

// 定义映射配置
public class MappingProfile : Profile
{public MappingProfile(){// 配置UserEntity到UserDTO的映射CreateMap<UserEntity, UserDTO>()// 自定义映射:将CreateTime转换为格式化字符串.ForMember(dest => dest.CreateTimeStr, opt => opt.MapFrom(src => src.CreateTime.ToString("yyyy-MM-dd HH:mm")));}
}

在 Startup/Program.cs 中注册:

builder.Services.AddAutoMapper(typeof(MappingProfile)); // 注册AutoMapper

在服务中使用:

public class UserService
{private readonly IMapper _mapper;// 注入AutoMapperpublic UserService(IMapper mapper){_mapper = mapper;}public UserDTO GetUserDTO(int userId){var userEntity = _dbContext.Users.FirstOrDefault(u => u.Id == userId);// 自动转换return _mapper.Map<UserDTO>(userEntity); }
}

3.4 在 API 中返回 DTO

最后,在 Controller 中调用服务,返回 DTO 给前端:

[ApiController]
[Route("api/users")]
public class UserController : ControllerBase
{private readonly UserService _userService;public UserController(UserService userService){_userService = userService;}[HttpGet("{id}")]public IActionResult GetUser(int id){var userDTO = _userService.GetUserDTO(id);if (userDTO == null)return NotFound();return Ok(userDTO); // 返回DTO,前端只收到必要数据}
}

本节小结: DTO 的核心是 “按需定义 + 正确转换”,手动转换适合简单场景,AutoMapper 适合复杂场景,按需选择即可。

四、DTO 数据流向:一张流程图看懂全局

服务层查询
转换处理
API返回
数据库 表数据
Entity 完整实体
DTO 精简数据
前端 展示数据

流程说明:
服务层从数据库查询数据,得到 Entity(包含所有字段);
服务层将 Entity 转换为 DTO(只保留需要的字段,可能做格式化);
API 层将 DTO 返回给前端,前端直接使用 DTO 数据展示。

五、新手常踩的 5 个坑及解决方案

坑 1:DTO 和 Entity 字段完全一致

问题: 图省事直接复制 Entity 的字段到 DTO,导致 DTO 失去 “精简” 意义,还可能包含敏感信息。
解决: 严格按照 “传输需求” 设计 DTO,问自己:这个字段前端 / 其他服务真的需要吗?

坑 2:转换时遗漏字段

问题: 手动转换时,漏写某个字段(比如 UserDTO 的 Email 没赋值),导致前端数据缺失。
解决:
手动转换时加单元测试,验证所有字段是否正确映射;
用 AutoMapper 时开启验证(CreateMap后加.ValidateMemberList(MemberList.Destination)),启动时会报错。

坑 3:DTO 中包含业务逻辑

问题: 在 DTO 中写复杂计算逻辑(比如public int GetAge()),违背 DTO"纯数据载体" 的设计原则。
解决: 业务逻辑放在服务层,DTO 只存数据,最多有简单的格式化属性(如CreateTimeStr)。

坑 4:过度设计 DTO

问题: 一个 Entity 对应 N 个 DTO(比如UserListDTO、UserDetailDTO、UserEditDTO),导致类爆炸,维护困难。解决:按业务场景合并,比如列表和详情可用同一个 DTO(前端忽略不需要的字段),除非字段差异极大。

坑 5:忽略 DTO 的验证

问题: 只在 Entity 上加数据验证(如[Required]),但 DTO 作为 API 入参时没加,导致无效数据传入。
解决: 在 DTO 的属性上添加验证特性(如[Required]、[EmailAddress]),和 Entity 的验证分开维护。
本节小结: DTO 的坑多源于 “偷懒” 或 “过度设计”,记住核心原则:按需设计、纯数据、正确转换。

六、总结:DTO 的 “三字经”

用 3 句话总结 DTO 的核心要点:

  • 不冗余: 只传必要数据,拒绝 “全量打包”;
  • 不泄密: 屏蔽敏感字段,守住数据安全;
  • 不耦合: 隔离层间依赖,降低修改成本。

提问:

你在使用 DTO 时遇到过哪些奇葩问题?或者有什么独家优化技巧?欢迎在评论区分享,我们一起避坑进步!

http://www.dtcms.com/a/568799.html

相关文章:

  • Elasticsearch 7.0 介绍与配置详解
  • 如何理解前端开发中框架一词及其优点?
  • ELK学习记录
  • Paho MQTT C 客户端源码深入解析
  • 零基础学AI大模型之RAG系统链路构建:文档切割转换全解析
  • Vue 核心语法详解:模板语法中的绑定表达式与过滤器(附 Vue3 替代方案)
  • CentOS7.6 部署 k3s 单机版
  • 【算法训练营 · 专项练习篇】Stream流与函数式编程
  • 泰州企业做网站百度地图怎么导航环线
  • int8_to_float(output_tensor->data.int8, output_float, load_class_num);
  • 使用Nmap扫描某个服务器所有开放端口
  • 如何看网站是用什么程序做的如何把qq音乐导入到wordpress
  • SpringCloud网关实战:路由与鉴权全解析
  • 基于ResNet50和PyTorch的猫狗图像分类系统设计与实现
  • 自回归模型例题(AR)与ACF/PACF图绘制
  • ESP32-WROOM-32E LED点灯系列
  • 《红色脉络:一部PLMN在中国的演进史诗 (1G-6G)》 第15篇 | 结语:无尽的前沿——PLMN的未来与中国的全球角色
  • 付网站开发费计入什么科目seo外包杭州
  • 外贸网站域名被封免费网络游戏大全
  • PySide6 Win10记事本从零到一——第七章 格式菜单界面与功能实现
  • PDF文件损坏打不开怎么修复?2025年最新修复工具测评与对比
  • 谈谈MYSQL索引失效场景
  • Qwen-Image-Edit本地到底如何部署使用?怎么还有comfyui
  • 佳能LBP6018L打印浅淡问题的尝试性解决方法
  • 微算法科技(NASDAQ MLGO):以隐私计算区块链筑牢多方安全计算(MPC)安全防线
  • SpringCache :让缓存开发更高效
  • 电路分析 | Phasor Analysis(篇 1)
  • 网站备案取消长春网站建设模板样式
  • get_ccopt系列命令介绍(二)
  • 成都工业学院文献检索在哪个网站做破解wordpress密码