.NET Core跨DbContext设置CreatedBy默认值的实现咨询
解决方案
要在ManagementContext中设置CreatedBy字段的值,核心思路是通过依赖注入获取当前用户的身份信息,而不是直接访问另一个DbContext(这会导致不必要的耦合)。下面是具体的实现步骤:
1. 创建一个获取当前用户ID的服务
首先定义一个接口来抽象获取当前用户的逻辑,这样可以避免直接依赖HttpContext,也方便后续测试:
public interface ICurrentUserService { string? GetCurrentUserId(); }
然后实现这个接口,利用IHttpContextAccessor从请求的Claims中获取用户ID(假设你的用户ID存储在NameIdentifier Claim中):
public class CurrentUserService : ICurrentUserService { private readonly IHttpContextAccessor _httpContextAccessor; public CurrentUserService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public string? GetCurrentUserId() { return _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); } }
记得在Program.cs中注册这个服务:
builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
2. 在ManagementContext中注入ICurrentUserService
修改ManagementContext的构造函数,注入ICurrentUserService,这样就能在保存变更时获取用户ID:
namespace Infrastructure.Data { public class ManagementContext : DbContext { private readonly ICurrentUserService _currentUserService; public ManagementContext( DbContextOptions<ManagementContext> options, ICurrentUserService currentUserService) : base(options) { _currentUserService = currentUserService; } public DbSet<Department> Department { get; set; } public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default) { var addedEntities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added) .ToList(); addedEntities.ForEach(e => { e.Property("CreatedOn").CurrentValue = DateTime.UtcNow; // 设置CreatedBy字段 var userId = _currentUserService.GetCurrentUserId(); if (!string.IsNullOrEmpty(userId)) { e.Property("CreatedBy").CurrentValue = userId; } // 可选:如果没有用户ID(比如后台任务),可以抛出异常或设置默认值 // else throw new InvalidOperationException("无法获取当前用户ID"); }); var editedEntities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Modified) .ToList(); editedEntities.ForEach(e => { e.Property("LastModifiedOn").CurrentValue = DateTime.UtcNow; // 可选:也可以设置LastModifiedBy字段 // var userId = _currentUserService.GetCurrentUserId(); // if (!string.IsNullOrEmpty(userId)) // { // e.Property("LastModifiedBy").CurrentValue = userId; // } }); return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); } } }
3. 处理无HttpContext的场景(可选)
如果你的应用有后台任务、定时任务等没有HttpContext的场景,你可以扩展ICurrentUserService,允许手动设置用户ID:
public interface ICurrentUserService { string? GetCurrentUserId(); void SetCurrentUserId(string userId); } public class CurrentUserService : ICurrentUserService { private readonly IHttpContextAccessor _httpContextAccessor; private string? _manualUserId; public CurrentUserService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public string? GetCurrentUserId() { // 优先使用手动设置的ID,再从HttpContext获取 return _manualUserId ?? _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); } public void SetCurrentUserId(string userId) { _manualUserId = userId; } }
然后在后台任务中,就可以手动设置用户ID:
using (var scope = _serviceProvider.CreateScope()) { var currentUserService = scope.ServiceProvider.GetRequiredService<ICurrentUserService>(); var managementContext = scope.ServiceProvider.GetRequiredService<ManagementContext>(); // 设置后台任务执行的用户ID currentUserService.SetCurrentUserId("system-task-user-id"); // 执行数据操作 managementContext.Department.Add(new Department { Name = "Test Dept" }); await managementContext.SaveChangesAsync(); }
为什么不直接访问另一个DbContext?
直接在ManagementContext中注入另一个DbContext会导致两个上下文之间产生强耦合,违反了单一职责原则。而且如果两个上下文使用不同的数据库连接,还可能引发事务、性能等问题。通过ICurrentUserService抽象用户信息的获取,既解耦了依赖,也让代码更易于测试和维护。
内容的提问来源于stack exchange,提问作者CoderM
相关产品推荐
相关产品推荐

