Blazor Server EF Core:为所有实体添加创建/修改人及时间字段的问题
解决方案:实体自动填充创建/修改人及时间(含子实体)
核心思路
避开在ApplicationDbContext中直接依赖UserManager的循环依赖问题,通过轻量级的当前用户信息服务传递用户ID,让DbContext能统一处理所有继承BaseModel的实体(包括关联子实体)的时间与操作人字段填充。
步骤1:实现当前用户访问服务
创建一个独立服务获取当前登录用户ID,避免直接在DbContext中依赖UserManager:
public interface ICurrentUserService { Guid? GetUserId(); } public class CurrentUserService : ICurrentUserService { private readonly IHttpContextAccessor _httpContextAccessor; public CurrentUserService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Guid? GetUserId() { var userIdStr = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); return string.IsNullOrEmpty(userIdStr) ? null : Guid.Parse(userIdStr); } }
在项目启动类(Startup/Program.cs)中注册服务:
builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped<ICurrentUserService, CurrentUserService>();
步骤2:改造ApplicationDbContext
注入ICurrentUserService,修改SaveChangesAsync方法,统一处理所有BaseModel类型实体的字段填充(包括子实体,ChangeTracker会自动跟踪所有关联实体):
public class ApplicationDbContext : IdentityDbContext { private readonly ICurrentUserService _currentUserService; public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ICurrentUserService currentUserService) : base(options) { _currentUserService = currentUserService; } public DbSet<Debtor> Debtors { get; set; } public DbSet<Contact> Contacts { get; set; } public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { var currentUserId = _currentUserService.GetUserId(); var now = DateTime.Now; foreach (var entry in ChangeTracker.Entries<BaseModel>()) { switch (entry.State) { case EntityState.Added: entry.Entity.CreatedAt = now; entry.Entity.CreatedBy = currentUserId ?? Guid.Empty; // 匿名场景可根据业务调整默认值 entry.Entity.ModifiedAt = now; entry.Entity.ModifiedBy = currentUserId ?? Guid.Empty; break; case EntityState.Modified: entry.Entity.ModifiedAt = now; entry.Entity.ModifiedBy = currentUserId ?? Guid.Empty; // 禁止修改创建时间和创建人 entry.Property(nameof(BaseModel.CreatedAt)).IsModified = false; entry.Property(nameof(BaseModel.CreatedBy)).IsModified = false; break; // 软删除场景可选处理 case EntityState.Deleted: if (entry.Entity is ISoftDelete softDeleteEntity) { entry.State = EntityState.Modified; softDeleteEntity.Deleted = true; softDeleteEntity.DeletedAt = now; softDeleteEntity.DeletedBy = currentUserId ?? Guid.Empty; } break; } } return await base.SaveChangesAsync(cancellationToken); } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<Debtor>().Navigation(d => d.Contacts).AutoInclude(); } }
步骤3:统一BaseModel定义
确保所有需要自动填充的实体(包括Debtor、Contact)都继承BaseModel:
public class BaseModel { public DateTime CreatedAt { get; set; } public Guid CreatedBy { get; set; } public DateTime ModifiedAt { get; set; } public Guid ModifiedBy { get; set; } public bool Deleted { get; set; } // 软删除字段可选 } // 示例实体 public class Debtor : BaseModel { public int Id { get; set; } // 其他业务字段 public ICollection<Contact> Contacts { get; set; } } public class Contact : BaseModel { public int Id { get; set; } public int DebtorId { get; set; } // 其他业务字段 }
步骤4:子实体自动跟踪处理
只要子实体(如Debtor下的Contacts)被ChangeTracker跟踪,DbContext就会自动处理其字段填充:
- 新增Debtor时,直接将Contact添加到Debtor的Contacts集合,保存时会自动填充Contact的创建/修改字段
- 更新Debtor时,先从数据库加载完整实体(包含Contacts),修改后保存,ChangeTracker会识别子实体的状态变化并自动填充字段
示例改造DebtorService的Upsert方法:
public async Task<ResponseModel> UpsertAsync(Debtor debtor) { try { if (debtor.Id > 0) { var existingDebtor = await _unitOfWork.Repository<Debtor>().Get(debtor.Id); // 更新主实体字段 _context.Entry(existingDebtor).CurrentValues.SetValues(debtor); // 同步子实体(根据业务逻辑新增/删除/修改Contacts) // 例如:清空现有Contacts,添加传入的新Contacts existingDebtor.Contacts.Clear(); foreach (var contact in debtor.Contacts) { existingDebtor.Contacts.Add(contact); } } else { await _unitOfWork.Repository<Debtor>().AddAsync(debtor); } await _unitOfWork.SaveAsync(); return ResponseModel.SuccessResponse(GlobalDeclaration._successResponse, debtor); } catch (Exception ex) { return ResponseModel.FailureResponse(GlobalDeclaration._internalServerError); } }
关键注意事项
- 循环依赖规避:通过
ICurrentUserService间接获取用户ID,避免DbContext与UserManager的双向依赖 - 子实体跟踪:确保子实体处于ChangeTracker的跟踪状态(从数据库加载或主动添加到主实体集合)
- 匿名场景处理:根据业务需求设置匿名操作时的默认用户ID(如
Guid.Empty)
内容的提问来源于stack exchange,提问作者Jeremy
相关产品推荐
相关产品推荐

