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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 14:44:59