【问题标题】:Overriding SaveChanges and setting ModifiedDate, but how do I set ModifiedBy?覆盖 SaveChanges 并设置 ModifiedDate,但如何设置 ModifiedBy?
【发布时间】:2011-11-30 06:58:43
【问题描述】:

我有一个带有 UI、业务(实体)和数据(DbContext)层的 ASP.NET MVC3 Web 应用程序。我正在使用实体框架 4.1 代码优先。现在,我正在覆盖数据层中的DbContext.SaveChanges(),以便我可以为对实现我的IAuditable 接口的任何实体对象所做的所有更改设置ModifiedDate。我有一个返回DateTime.Now 的静态DateProvider 类和方法(GetCurrentDate)(除非我正在运行测试,在这种情况下,它会返回我告诉它的任何内容)。

我也想自动将ModifiedBy 属性设置为当前用户。这样做的最佳方法是什么?框架中是否有一些内置的东西可以让我访问这些信息,或者我是否需要设置类似DateProvider 类的东西?这是一个 Active Directory 环境,我们在 IIS 中使用WindowsAuthentication

这是我的 SaveChanges 代码:

public override int SaveChanges()
{
    var changeSet = ChangeTracker.Entries<IAuditable>();

    if (changeSet != null)
    {
        foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
        {
            entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
        }
    }
    return base.SaveChanges();
}

【问题讨论】:

    标签: c# asp.net-mvc-3 entity-framework-4.1 ef-code-first


    【解决方案1】:

    您可以使用HttpContext.Current.User.Identity.Name 获取当前用户的姓名。

    public override int SaveChanges()
    {
        var changeSet = ChangeTracker.Entries<IAuditable>();
    
        if (changeSet != null)
        {
            foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
            {
                entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
                entry.Entity.ModifiedBy = HttpContext.Current.User.Identity.Name;
            }
        }
        return base.SaveChanges();
    }
    

    更好的方法是使用构造函数注入将当前用户传递给上下文

    public class MyContext : DbContext
    {
        public MyContext(string userName)
        {
            UserName = userName;
        }
    
        public string UserName
        {
            get; private set;
        }
    
        public override int SaveChanges()
        {
           var changeSet = ChangeTracker.Entries<IAuditable>();
    
           if (changeSet != null)
           {
              foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
              {
                  entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
                  entry.Entity.ModifiedBy = UserName;
              }
           }
           return base.SaveChanges();
       }
    }
    

    【讨论】:

    • 我非常喜欢构造函数注入方法。谢谢!
    • 是的,好主意,构造函数注入。
    【解决方案2】:

    我还想在我的 MVC 4 / Entity Framework 5 应用程序上自动填充审计字段。我使用了@Eranga 的回答和这个博客中的信息:http://lourenco.co.za/blog/2013/07/audit-trails-concurrency-and-soft-deletion-with-entity-framework/ 使这种方法适用于我的 Ninject - 发布以防对其他人有价值:

    创建了一个接口和抽象类:

    public interface IAuditableEntity {
        DateTime? CreatedDate { get; set; }
        string CreatedBy { get; set; }
        DateTime? LastModifiedDate { get; set; }
        string LastModifiedBy { get; set; }
    }
    
    public abstract class AuditableEntity:IAuditableEntity {
        public DateTime? CreatedDate { get; set; }
        public string CreatedBy { get; set; }
        public DateTime? LastModifiedDate { get; set; }
        public string LastModifiedBy { get; set; }
    }
    

    在我的实体中使用它们:

    public class DataEntity : AuditableEntity {
        public int DataEntityID { get; set; }
        ...
    }
    

    向 MyDbContext 添加了一个构造函数,它接受 HttpContext 并覆盖 SaveChanges:

    public EFDbContext(HttpContext context) {
        _context = context;
    }
    
    public HttpContext _context {
        get;
        private set;
    }
    
    public override int SaveChanges() {
        DateTime currentDateTime = DateTime.Now;
    
        foreach (var auditableEntity in ChangeTracker.Entries<IAuditableEntity>()) {
            if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified) {
                auditableEntity.Entity.LastModifiedDate = currentDateTime;
                switch (auditableEntity.State) {
                        case EntityState.Added:
                            auditableEntity.Entity.CreatedDate = currentDateTime;
                            auditableEntity.Entity.CreatedBy = _context.User.Identity.Name;
                            break;
                        case EntityState.Modified:
                            auditableEntity.Entity.LastModifiedDate = currentDateTime;
                            auditableEntity.Entity.LastModifiedBy = _context.User.Identity.Name;
                            if (auditableEntity.Property(p => p.CreatedDate).IsModified || auditableEntity.Property(p => p.CreatedBy).IsModified) {
                                throw new DbEntityValidationException(string.Format("Attempt to change created audit trails on a modified {0}", auditableEntity.Entity.GetType().FullName));
                            }
                            break;
                    }
                }
            }
            return base.SaveChanges();
        }
    

    最后 - 每个请求都需要一个 DbContext 并根据以下答案传递 HttpContext:https://stackoverflow.com/a/3617961/1803682 - 请注意,由于 MyDbContext 现在是请求范围,因此存储库也必须如此。

    kernel.Bind<IDataRepository>()
          .To<EFDataRepository>()
          .InRequestScope();
    
    kernel.Bind<MyDbContext>().ToSelf()
          .InRequestScope()
          .WithConstructorArgument("context", ninjectContext=>HttpContext.Current);
    

    【讨论】:

    • 我看看@Eranga 的回答和他的博客:lourenco.co.za/blog/2013/07/…。据我所知,它将数据更改保存到新表中。但是,在这个表中只有很少的列并且表不保留值,即更改了哪个记录,旧值是什么,或者调用了哪个操作?那么,如何实现呢?如果我错了,你能澄清一下吗?提前致谢。
    • 完整答案
    猜你喜欢
    • 2022-10-23
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多