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的实体跟踪机制实现,无需编写大量自定义逻辑:
安装依赖包
确保项目中安装AutoMapper和AutoMapper.Extensions.Microsoft.DependencyInjectionNuGet包。配置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()); } }修改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); }优化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
相关产品推荐
相关产品推荐

