當前文章的短網址連結為: https://unos.top/aprp
最佳实践:
强烈推荐将 EF Core 生成数据库表的实体模型(Entity)和前端提交数据的校验模型(DTO/Input Model)完全分开,而不是直接在 EF Core Entity 上添加 [Required]、[EmailAddress] 等 DataAnnotations 用于前端校验。
为什么不能直接在 EF Core Entity 上加校验 Annotations?
- EF Core Entity 的主要职责 是数据库映射和持久化。DataAnnotations(如 [Required]、[MaxLength]、[Key] 等)在这里主要用于生成数据库 Schema(例如 [Required] 会让列变成 NOT NULL,[MaxLength(100)] 会设置列长度)。
- EF Core 不会自动在 SaveChanges() 时校验 DataAnnotations(不像老版的 EF6)。如果你只依赖 Entity 上的注解,前端提交无效数据仍然可能进入数据库。
- 直接暴露 Entity 的问题(尤其是 Web API):
- 安全风险:容易发生 over-posting(用户恶意提交 Entity 中不应修改的字段,如 Id、IsAdmin)。
- 紧耦合:数据库结构一改,API 接口就得跟着改,破坏 API 契约稳定性。
- 性能/简洁:Entity 可能包含导航属性、内部字段,前端不需要全部返回。
- 校验规则不灵活:创建用户和更新用户可能需要不同的校验逻辑。
微软官方和社区主流推荐(Web API + EF Core 项目):使用 DTO 专门负责输入/输出和校验,Entity 只负责数据层。
推荐架构分层(最佳实践)
- Data Layer:EF Core Entity(可少量加 DB 约束注解)。
- Application/API Layer:DTO(带完整 DataAnnotations 校验)。
- Mapping:用 AutoMapper(推荐)或手动 new User { … } 把 DTO 转成 Entity。
完整案例(.NET 10 LTS + EF Core + Web API / Minimal API)
假设我们有一个 User 实体,需要前端提交创建用户。
1. EF Core Entity(Data/Models/User.cs)
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations; // 这里只用于 DB 约束
public class User
{
[Key]
public int Id { get; set; }
[Required] // 只影响 DB 列 NOT NULL
[MaxLength(100)] // 只影响 DB 列长度
public string Name { get; set; } = string.Empty;
[Required]
[MaxLength(200)]
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}C#2. 前端提交用的 DTO(Application/Dtos/CreateUserDto.cs)
using System.ComponentModel.DataAnnotations;
public class CreateUserDto
{
[Required(ErrorMessage = "姓名不能为空")]
[StringLength(100, MinimumLength = 2, ErrorMessage = "姓名长度必须在2-100字符之间")]
public string Name { get; set; } = string.Empty;
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public string Email { get; set; } = string.Empty;
// 可以加更多自定义校验,如密码确认、手机号正则等
}C#3. Controller 写法(传统 Web API)
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly AppDbContext _context;
public UsersController(AppDbContext context) => _context = context;
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateUserDto dto)
{
if (!ModelState.IsValid) // ASP.NET Core 自动根据 DataAnnotations 校验
{
return BadRequest(ModelState); // 返回 400 + 详细错误
}
var user = new User
{
Name = dto.Name,
Email = dto.Email
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetById), new { id = user.Id }, user);
}
}C#4. .NET 10 Minimal API 写法(更简洁,内置校验支持)
在 Program.cs 里先注册:
builder.Services.AddValidation(); // .NET 10 新特性C#然后直接用:
app.MapPost("/api/users", async (CreateUserDto dto, AppDbContext db) =>
{
// .NET 10 会自动校验 DataAnnotations,无需手动 ModelState
var user = new User { Name = dto.Name, Email = dto.Email };
db.Users.Add(user);
await db.SaveChangesAsync();
return Results.Created($"/api/users/{user.Id}", user);
});C#额外提示(进阶最佳实践)
- 输出也用 DTO:返回给前端时再创建一个 UserResponseDto,避免把 Entity 完整暴露。
- 复杂校验:DataAnnotations 够用简单场景;如果需要跨字段校验(如密码和确认密码必须一致),可以实现 IValidatableObject 或切换到 FluentValidation(更现代、更易测试)。
- 映射工具:安装 AutoMapper + AutoMapper.Extensions.Microsoft.DependencyInjection,配置 CreateMap<CreateUserDto, User>() 自动转换。
- 小项目例外:如果你的项目非常简单(纯 CRUD,无复杂业务),可以直接在 Entity 上加注解并复用。但一旦项目变大,马上会后悔——分离是长期维护的王道。