You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合模型与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:13:13