如何结合模型与EF实体在ASP.NET Core中使用Microsoft.AspNetCore.JsonPatch
解决方案
你可以通过「先将实体映射到DTO(CustomerModel),应用Patch后再将DTO的变更同步回实体」的方式解决类型不匹配问题,同时保持API层不暴露EF实体。以下是具体实现步骤和代码:
1. 修正实体查询逻辑
首先你的代码里存在一个错误:Where返回的是IQueryable<Customer>集合,不是单个实体,需要改为获取单个实体并检查是否存在:
var customerEntity = _dbContext.Customers.FirstOrDefault(x => x.CustomerID == id); if (customerEntity == null) { return NotFound(); }
2. 手动映射(无需第三方库)
如果不用AutoMapper这类映射工具,可以手动完成实体和DTO的属性映射:
[HttpPatch("{id}")] // 注意要在路由里接收id参数 public IActionResult PatchCustomer(int id, [FromBody] JsonPatchDocument<CustomerModel> patchDoc) { if (patchDoc == null) { return BadRequest(ModelState); } // 1. 获取数据库中的实体 var customerEntity = _dbContext.Customers.FirstOrDefault(x => x.CustomerID == id); if (customerEntity == null) { return NotFound(); } // 2. 将实体属性映射到CustomerModel(仅映射允许修改的属性) var customerModel = new CustomerModel { Name = customerEntity.Name, Email = customerEntity.Email, Phone = customerEntity.Phone // 忽略实体中不允许修改的属性,比如CreateTime、CustomerID等 }; // 3. 将Patch应用到CustomerModel patchDoc.ApplyTo(customerModel, ModelState); // 4. 验证ModelState if (!ModelState.IsValid) { return BadRequest(ModelState); } // 5. 将CustomerModel的变更同步回实体 customerEntity.Name = customerModel.Name; customerEntity.Email = customerModel.Email; customerEntity.Phone = customerModel.Phone; // 6. 保存数据库变更 _dbContext.SaveChanges(); return Ok(customerModel); }
3. 使用AutoMapper简化映射(推荐)
如果项目中已经使用AutoMapper,可以通过配置映射规则来简化操作,避免手动赋值的繁琐:
第一步:配置AutoMapper映射
在AutoMapper配置文件中添加实体和DTO的映射规则:
public class MappingProfile : Profile { public MappingProfile() { // 配置Customer到CustomerModel的映射(仅包含允许修改的属性) CreateMap<Customer, CustomerModel>() .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name)) .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.Email)) .ForMember(dest => dest.Phone, opt => opt.MapFrom(src => src.Phone)); // 配置CustomerModel到Customer的反向映射(仅更新允许修改的属性) CreateMap<CustomerModel, Customer>() .ForMember(dest => dest.CustomerID, opt => opt.Ignore()) // 忽略主键,不允许修改 .ForMember(dest => dest.CreateTime, opt => opt.Ignore()) // 忽略创建时间等不可修改属性 .ForAllOtherMembers(opt => opt.Condition((src, dest, srcMember) => srcMember != null)); // 仅更新非空属性(可选) } }
第二步:修改API方法
注入IMapper,用AutoMapper完成映射:
private readonly IMapper _mapper; // 构造函数注入IMapper public YourController(YourDbContext dbContext, IMapper mapper) { _dbContext = dbContext; _mapper = mapper; } [HttpPatch("{id}")] public IActionResult PatchCustomer(int id, [FromBody] JsonPatchDocument<CustomerModel> patchDoc) { if (patchDoc == null) { return BadRequest(ModelState); } var customerEntity = _dbContext.Customers.FirstOrDefault(x => x.CustomerID == id); if (customerEntity == null) { return NotFound(); } // 实体转DTO var customerModel = _mapper.Map<CustomerModel>(customerEntity); // 应用Patch到DTO patchDoc.ApplyTo(customerModel, ModelState); if (!ModelState.IsValid) { return BadRequest(ModelState); } // DTO变更同步回实体 _mapper.Map(customerModel, customerEntity); // 保存变更 _dbContext.SaveChanges(); return Ok(_mapper.Map<CustomerModel>(customerEntity)); }
核心逻辑说明
- 始终用
CustomerModel作为Patch的目标类型,确保调用方只能修改你开放的属性,避免误改实体中的敏感/不可修改字段。 - 通过「实体→DTO→应用Patch→DTO→实体」的流程,实现类型转换和变更同步,既遵守了分层架构的原则,又解决了JsonPatch的类型匹配问题。
内容的提问来源于stack exchange,提问作者LP13
相关产品推荐
相关产品推荐

