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

EF Core如何动态更新含无限嵌套关联的数据库实体?

问题

数据库中包含role、user、company等大量实体,所有实体支持自引用形成树形结构,且实体间存在复杂关联。现有API端点接收与原数据库对象结构一致的JSON快照数据(包含嵌套对象的新增、删除、修改),需要动态更新用户实体,但希望避免编写完全自定义的实现。

尝试过以下方法均失败:

  • 使用JsonConvert.PopulateObject,报错“Cannot track some objects”;
  • 手动调用_context.TryAttach跟踪对象,导致provider对象被清空而非更新;
  • 编写自定义ExtendedJsonConverter进行反序列化并自动附加对象,同样失败。

当前API端点及AuthFacade.Update方法代码如下:

API端点代码

[HttpPut]
[Route("UpdateSystemUser")]
public async Task<IActionResult> UpdateSystemUser(
    [Caller] SystemUser caller, [FromForm]int key, [FromForm]string values, CancellationToken cancellationToken)
{
    var user = 
        await _userManager.Users
            .Include(x => x.roles)
            .Include(x => x.companies)
            .Include(x => x.providers)
            .SingleOrDefaultAsync(x => x.Id == key, cancellationToken: cancellationToken);
    if (user == null)
    {
        return NotFound();
    }

    JsonConvert.PopulateObject(values, user, new JsonSerializerSettings()
    {
        ObjectCreationHandling = ObjectCreationHandling.Replace
    });


    await _authFacade.Update(caller, user, cancellationToken);
    
    var (systemUser, token) = 
        await _authFacade.GenerateActivationDataAsync(user);

    var result = JsonConvert.SerializeObject(systemUser, new JsonSerializerSettings
    {
        ContractResolver = 
            new ExtendedContractResolver(
                ignoredProperties: new Dictionary<Type, string[]>
                {
                    [typeof(SystemRole)] = new []{nameof(SystemRole.users)},
                    [typeof(Company)] = new []{nameof(Company.users), nameof(Company.parent)},
                    [typeof(Provider)] = new []{nameof(Provider.users)}
                })
    });

    return Ok(result);
}

AuthFacade.Update方法代码

public async Task<SystemUser> Update(SystemUser caller, SystemUser user, CancellationToken cancellationToken)
{
    var result = await _userManager.UpdateAsync(user);
    if (!result.Succeeded)
    {
        throw new Exception(result.Errors.Select(x => x.Description).Aggregate((i, j) => i + ", " + j));
    }
    return user;
}

寻求无需自定义复杂逻辑的统一解决方案,是否存在简洁的实现方式?


简洁解决方案

可以借助AutoMapper配合EF Core的实体跟踪机制实现,无需编写大量自定义逻辑:

  1. 安装依赖包
    确保项目中安装AutoMapper和AutoMapper.Extensions.Microsoft.DependencyInjection NuGet包。

  2. 配置AutoMapper映射规则
    创建映射配置,处理快照对象到已跟踪实体的属性映射,同时忽略反向导航属性避免循环引用和跟踪冲突:

    public class MappingProfile : Profile
    {
        public MappingProfile()
        {
            // 用户实体映射:忽略主键,映射嵌套集合
            CreateMap<SystemUser, SystemUser>()
                .ForMember(dest => dest.Id, opt => opt.Ignore())
                .ForMember(dest => dest.roles, opt => opt.MapFrom(src => src.roles))
                .ForMember(dest => dest.companies, opt => opt.MapFrom(src => src.companies))
                .ForMember(dest => dest.providers, opt => opt.MapFrom(src => src.providers));
    
            // 嵌套实体映射:忽略反向导航属性
            CreateMap<SystemRole, SystemRole>()
                .ForMember(dest => dest.users, opt => opt.Ignore());
            CreateMap<Company, Company>()
                .ForMember(dest => dest.users, opt => opt.Ignore())
                .ForMember(dest => dest.parent, opt => opt.Ignore());
            CreateMap<Provider, Provider>()
                .ForMember(dest => dest.users, opt => opt.Ignore());
        }
    }
    
  3. 修改API端点逻辑
    先将JSON快照反序列化为独立对象,再用AutoMapper将快照数据映射到已跟踪的用户实体,最后保存变更:

    [HttpPut]
    [Route("UpdateSystemUser")]
    public async Task<IActionResult> UpdateSystemUser(
        [Caller] SystemUser caller, [FromForm]int key, [FromForm]string values, CancellationToken cancellationToken)
    {
        var existingUser = 
            await _userManager.Users
                .Include(x => x.roles)
                .Include(x => x.companies)
                .Include(x => x.providers)
                .SingleOrDefaultAsync(x => x.Id == key, cancellationToken: cancellationToken);
        if (existingUser == null)
        {
            return NotFound();
        }
    
        // 反序列化JSON为快照对象
        var userSnapshot = JsonConvert.DeserializeObject<SystemUser>(values, new JsonSerializerSettings
        {
            ObjectCreationHandling = ObjectCreationHandling.Replace
        });
    
        // 用AutoMapper将快照数据映射到已跟踪实体
        _mapper.Map(userSnapshot, existingUser);
    
        // 执行更新(EF Core自动跟踪实体变更)
        await _authFacade.Update(caller, existingUser, cancellationToken);
        
        var (systemUser, token) = 
            await _authFacade.GenerateActivationDataAsync(existingUser);
    
        var result = JsonConvert.SerializeObject(systemUser, new JsonSerializerSettings
        {
            ContractResolver = 
                new ExtendedContractResolver(
                    ignoredProperties: new Dictionary<Type, string[]>
                    {
                        [typeof(SystemRole)] = new []{nameof(SystemRole.users)},
                        [typeof(Company)] = new []{nameof(Company.users), nameof(Company.parent)},
                        [typeof(Provider)] = new []{nameof(Provider.users)}
                    })
        });
    
        return Ok(result);
    }
    
  4. 优化AuthFacade.Update方法(可选)
    若UserManager.UpdateAsync无法处理嵌套实体变更,直接使用DbContext保存:

    public async Task<SystemUser> Update(SystemUser caller, SystemUser user, CancellationToken cancellationToken)
    {
        await _context.SaveChangesAsync(cancellationToken);
        return user;
    }
    

核心逻辑:AutoMapper自动处理属性和嵌套集合的映射,EF Core依赖对现有实体的跟踪识别变更,完全规避手动处理实体附加、跟踪冲突的复杂逻辑。

内容的提问来源于stack exchange,提问作者codeceptive solutions

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 10:50:33