简体   繁体   English

实体框架核心创建和更新字段

[英]Entity Framework Core Creation and Update fields

I have four custom fields on all of my entities.我的所有实体上都有四个自定义字段。 The four fields are CreatedBy , CreatedDate , UpdatedBy and UpdatedDate .这四个字段是CreatedByCreatedDateUpdatedByUpdatedDate

Is there a way to hook into Entity Framework core events so that on an insert it will populate the CreatedDate with the current DateTime and CreatedBy with the current user?有没有办法挂钩到 Entity Framework 核心事件,以便在插入时它将使用当前的DateTime填充CreatedDate并使用当前用户填充CreatedBy When there is an update to the database it would populate UpdatedDate with the current DateTime and UpdatedBy with the current user?当数据库有更新时,它会用当前的DateTime填充UpdatedDate ,用当前用户填充UpdatedBy

Basically @Steve's approach is the way to go, but the current implementation of it makes it hard to unit test your project.基本上@Steve 的方法是可行的方法,但它的当前实现使得对您的项目进行单元测试变得困难。

With a little bit of refactoring, you can make it unit test friendly and stay true to SOLID principles and encapsulation.通过一点点重构,您可以使其单元测试友好,并忠于 SOLID 原则和封装。

Here's a refactored version of Steve's example这是 Steve 示例的重构版本

public abstract class AuditableEntity
{
    public DateTime CreatedDate { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDate { get; set; }
    public string UpdatedBy { get; set; }
}

public class AuditableDbContext : DbContext
{
    protected readonly IUserService userService;
    protected readonly DbContextOptions options;
    protected readonly ITimeService timeService;

    public BaseDbContext(DbContextOptions options, IUserService userService, ITimeService timeService) : base(options)
    {
        userService = userService ?? throw new ArgumentNullException(nameof(userService));
        timeService = timeService ?? throw new ArgumentNullException(nameof(timeService));
    }

    public override int SaveChanges()
    {
        // get entries that are being Added or Updated
        var modifiedEntries = ChangeTracker.Entries()
                .Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));

        var identityName = userService.CurrentUser.Name;
        var now = timeService.CurrentTime;

        foreach (var entry in modifiedEntries)
        {
            var entity = entry.Entity as AuditableEntity;

            if (entry.State == EntityState.Added)
            {
                entity.CreatedBy = identityName ?? "unknown";
                entity.CreatedDate = now;
            }

            entity.UpdatedBy = identityName ?? "unknown";
            entity.UpdatedDate = now;
        }

        return base.SaveChanges();
    }
}

Now it's easy to mock time and user/principal for unit tests and model/domain/business layer is free of EF Core dependency, better encapsulating your domain logic way better.现在很容易模拟单元测试的时间和用户/主体,并且模型/域/业务层没有 EF Core 依赖,更好地封装您的域逻辑。

Of course one could further refactor this to use a more modular approach by using strategy pattern, but that's out of scope.当然,可以通过使用策略模式进一步重构它以使用更模块化的方法,但这超出了范围。 You can also use ASP.NET Core Boilerplate which also offers an implementation of an auditable (and soft delete) EF Core DbContext ( here and here )您还可以使用 ASP.NET Core Boilerplate,它也提供了可审计(和软删除)EF Core DbContext(这里这里)的实现

I have exactly the same layout as you with what I call "Audit" fields.我和你的布局完全一样,我称之为“审计”字段。

The way I solved this was to create a base abstract class called AuditableEntity to hold the properties themselves and expose a method called PrepareSave .我解决这个问题的方法是创建一个名为AuditableEntity的基本抽象类来保存属性本身并公开一个名为PrepareSave的方法。 Inside PrepareSave I set the values of the fields as required:PrepareSave我根据需要设置字段的值:

public abstract class AuditableEntity
{
    public DateTime CreatedDate { get; set; }
    public string CreatedBy { get; set; }
    public DateTime UpdatedDate { get; set; }
    public string UpdatedBy { get; set; }

    public virtual void PrepareSave(EntityState state)
    {
        var identityName = Thread.CurrentPrincipal.Identity.Name;
        var now = DateTime.UtcNow;

        if (state == EntityState.Added)
        {
            CreatedBy = identityName ?? "unknown";
            CreatedDate = now;
        }

        UpdatedBy = identityName ?? "unknown";
        UpdatedDate = now;
    }
}

I made PrepareSave virtual so I can override it in my entities if I want.我将PrepareSave虚拟,因此如果需要,我可以在我的实体中覆盖它。 You may need to change how you get the Identity depending on your implementation.您可能需要根据您的实施更改获取身份的方式。

To call this, I overwrote SaveChanges on my DbContext and called PrepareSave on each entity that was being added or updated (which I got from the change tracker):为了调用它,我在我的DbContext上覆盖了SaveChanges并在每个正在添加或更新的实体上调用PrepareSave (我从更改跟踪器中获得):

public override int SaveChanges()
{
    // get entries that are being Added or Updated
    var modifiedEntries = ChangeTracker.Entries()
            .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);

    foreach (var entry in modifiedEntries)
    {
        // try and convert to an Auditable Entity
        var entity = entry.Entity as AuditableEntity;
        // call PrepareSave on the entity, telling it the state it is in
        entity?.PrepareSave(entry.State);
    }

    var result = base.SaveChanges();
    return result;
}

Now, whenever I call SaveChanges on my DbContext (either directly or through a repository), any entity that inherits AuditableEntity will have it's audit fields set as necessary.现在,每当我在 DbContext 上调用SaveChanges (直接或通过存储库)时,任何继承AuditableEntity实体都会根据需要设置其审计字段。

Unfortunately my reputation is to low for adding comments.不幸的是,我在添加评论方面的声誉很低。 For this reason I add my Answer.出于这个原因,我添加了我的答案。 So: one addition to Tseng Answer.所以: 曾回答的一个补充。 When entity in modified state You must set IsModidied = false tor CreatedBy and CreatedDate properties.当实体处于修改状态时,您必须设置 IsModidied = false 或 CreatedBy 和 CreatedDate 属性。 Otherwise that fields will be rewritten by optional values for string and DateTime types.否则,这些字段将被字符串和日期时间类型的可选值重写。

if (entry.State == EntityState.added)
{
    CreatedBy = identityName ?? "unknown";
    CreatedDate = now;
}
else
{
    Entry<AuditableEntity>(entry).Property(p => p.CreatedBy).IsModified = false;
    Entry<AuditableEntity>(entry).Property(p => p.CreatedDate).IsModified = false;
    // or
    // entity.Property("CreatedBy").IsModified = false;
    // entity.Property("CreatedDate").IsModified = false;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM