如何在查询SomeEntity时自动关联Active Directory中的User实体?
在领域层中,我们有时会存在与用户关联的实体,例如:
class SomeEntity { public User User { get; set; } }
User类示例如下:
class User { public Guid Id { get; set; } public string DisplayName { get; set; } }
我们希望将SomeEntity类型的实体存储在自有数据库中,而用户数据存储在Active Directory中。为此我们修改SomeEntity类:
class SomeEntity { public Guid UserId { get; set; } [NotMapped] public User User { get; set; } }
查询SomeEntity时,我们可从Active Directory补充用户数据以获取完整领域模型,示例代码:
SomeEntity entity = await _dbContext.SomeEntities.FindAsync(id); User user = await _identityService.GetUserById(entity.UserId); entity.User = user;
此方案可行,但当存在大量查询SomeEntity(或其子集)的场景时,需重复编写相同获取逻辑;若SomeEntity嵌套在其他对象中,逻辑会更复杂。
请问是否有办法确保查询SomeEntity时自动获取对应的User?
已尝试或了解到的部分方案无法完全解决问题:
- 为User属性添加 getter,在使用时获取用户(违反清洁架构原则,将获取逻辑加入领域模型)
- 仅在领域模型中保留UserId,需用时再获取User(需重复编写逻辑,嵌套场景下更困难)
- 通过Entity Framework或Automapper实现获取(看似不够规范,未得到有效解决方案)
使用技术栈:
- ASP.NET Core
- Blazor
- Entity Framework
- Automapper
方案1:封装逻辑到仓储层
遵循清洁架构职责划分,将用户数据填充逻辑封装到SomeEntity对应的仓储实现中,所有查询操作统一通过仓储执行,自动完成User数据补充。
示例代码:
public class SomeEntityRepository : ISomeEntityRepository { private readonly AppDbContext _dbContext; private readonly IIdentityService _identityService; public SomeEntityRepository(AppDbContext dbContext, IIdentityService identityService) { _dbContext = dbContext; _identityService = identityService; } public async Task<SomeEntity> GetByIdAsync(Guid id) { var entity = await _dbContext.SomeEntities.FindAsync(id); if (entity != null) { entity.User = await _identityService.GetUserById(entity.UserId); } return entity; } public async Task<List<SomeEntity>> GetAllAsync() { var entities = await _dbContext.SomeEntities.ToListAsync(); var userIds = entities.Select(e => e.UserId).Distinct(); // 假设IdentityService支持批量查询,减少AD请求次数 var users = await _identityService.GetUsersByIdsAsync(userIds); foreach (var entity in entities) { entity.User = users.FirstOrDefault(u => u.Id == entity.UserId); } return entities; } }
优势:领域模型保持纯净,所有查询逻辑集中维护,避免重复代码;批量查询时可一次性获取所需用户数据,提升性能。
方案2:Automapper自定义映射规则
利用Automapper的自定义转换/后置映射逻辑,将数据库实体(仅含UserId)映射到带User属性的领域模型,自动触发AD数据获取。
基础映射配置
public class MappingProfile : Profile { public MappingProfile(IIdentityService identityService) { CreateMap<SomeEntity, SomeEntity>() .ForMember(dest => dest.User, opt => opt.MapFrom(async src => await identityService.GetUserById(src.UserId))); } }
查询时使用投影:
var entities = await _dbContext.SomeEntities .ProjectTo<SomeEntity>(_mapper.ConfigurationProvider) .ToListAsync();
批量优化版本
CreateMap<SomeEntity, SomeEntity>() .AfterMap(async (src, dest, context) => { var identityService = context.Items["IdentityService"] as IIdentityService; dest.User = await identityService.GetUserById(src.UserId); });
查询时传入服务实例:
var dbEntities = await _dbContext.SomeEntities.ToListAsync(); var entities = await _mapper.Map<List<SomeEntity>>(dbEntities, opt => opt.Items["IdentityService"] = _identityService);
优势:适配已有Automapper体系,可快速扩展到嵌套对象场景,只需在对应映射规则中添加相同逻辑。
方案3:EF Core查询拦截器
通过EF Core的查询拦截器,在SomeEntity查询完成后自动触发用户数据填充逻辑,全局生效无需修改现有查询代码。
定义拦截器
public class SomeEntityUserInterceptor : DbCommandInterceptor { private readonly IIdentityService _identityService; public SomeEntityUserInterceptor(IIdentityService identityService) { _identityService = identityService; } public override async ValueTask<DbDataReader> ReaderExecutedAsync( DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default) { var trackedEntities = eventData.Context?.ChangeTracker.Entries<SomeEntity>().ToList(); if (trackedEntities != null && trackedEntities.Any()) { var userIds = trackedEntities.Select(e => e.Entity.UserId).Distinct(); var users = await _identityService.GetUsersByIdsAsync(userIds); foreach (var entry in trackedEntities) { entry.Entity.User = users.FirstOrDefault(u => u.Id == entry.Entity.UserId); } } return await base.ReaderExecutedAsync(command, eventData, result, cancellationToken); } }
注册拦截器
在DbContext配置中添加拦截器:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.AddInterceptors(_someEntityUserInterceptor); }
优势:全局自动处理所有SomeEntity查询,包括嵌套查询场景;无需修改现有业务代码。
注意:需控制拦截器作用范围,避免对无关查询造成性能损耗;优先使用批量用户查询接口减少AD请求。
内容的提问来源于Stack Exchange,提问作者JXD

