初始化实体的通用属性

Initialize Common properties of entities

我有很多实体模型,它们都有一些共同的属性,这些属性如下

  1. 创建时间
  2. 创建方式
  3. 上次修改时间
  4. 上次修改者

每次我需要将对象保存在数据库中时,我都必须手动初始化这些属性。 我想要一个方法或一些基础 class,我可以在其中初始化这些属性以避免一次又一次地编写相同代码的麻烦。 注意:我想要没有基本构造函数的 Base class 的通用实现。 提前谢谢你

我个人会使用接口定义。您的实体都将实现一个名为 IAuditedEntity(或您认为合适的任何名称)的接口。该接口会将您的四个属性(CreatedOn、CreateBy、LastModifiedOn、LastModifiedBy)声明为get 和set。任何需要这些字段的实体都需要实现接口。

然后当您的记录更新到数据库时,检查对象是否实现了 IAuditEntity 接口,例如如果(myRecord 是 IAuditedEntity)。如果它确实实现了接口,则将属性设置为适当的值。

在那种情况下,最好将对象转换为接口以确保类型安全。

例如((IAuditedEntity) myObj).CreatedOn = DateTime.Now.

此实现将允许您将创建的字段和修改的字段拆分为两个单独的界面 - 创建可以被视为修改,但修改不一定是创建操作。

理论上,通过接口实现,您将能够将接口应用于不同的实体类型,但将初始化这些字段的代码保留在一个地方。

出于多种原因,您不能对您的实体 class 执行此操作。 CreatedOn 可以通过默认值处理,但其余的需要实体 class 永远不会拥有的知识,例如 HttpContext.User 或者只是它是否正在被修改或创建。

相反,您需要将此功能构建到您的上下文中。首先,您应该让所有实体 classes 实现一个特定的接口,以便从特定的基础 class 继承。在任何一种情况下,您都希望将这些属性添加到其中,以便您可以确信任何 implementation/derived class 都将拥有所述属性。

然后,您可以将私有方法添加到上下文中 class,例如:

private void PopulateAuditTrailProperties()
{
    var httpContextAccessor = this.GetService<IHttpContextAccessor>();
    var username = httpContextAccessor?.HttpContext.User.Identity.Name;

    foreach (var entry in ChangeTracker.Entries<IEntity>().Where(x => x.State == EntityState.Added))
    {
        entry.Entity.CreatedOn = DateTimeOffset.UtcNow;
        entry.Entity.CreatedBy = username;
    }

    foreach (var entry in ChangeTracker.Entries<IEntity>().Where(x => x.State == EntityState.Modified))
    {
         entry.Entity.ModifiedOn = DateTimeOffset.UtcNow;
         entry.Entity.ModifiedBy = username;
     }
}

然后,您需要在您的上下文中覆盖 SaveChangesSaveChangesAsync,以便在保存之前分别调用此方法:

public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
    PopulateAuditTrailProperties();
    return base.SaveChanges(acceptAllChangesOnSuccess);
}

public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    PopulateAuditTrailProperties();
    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}

最后,您需要向 Startup.cs 添加一些内容:

services.AddDbContext<MyContext>(o =>
    o.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))
        .UseInternalServiceProvider()); // Add this line

并且:

services.AddHttpContextAccessor();