开发者

Overriding SaveChanges and setting ModifiedDate, but how do I set ModifiedBy?

I have an ASP.NET MVC3 web application with UI, Business (entities), and Data (DbContext) layers. I am using Entity Framework 4.1 Code First. Right now, I am overriding the DbContext.SaveChanges() in the Data layer so that I can set the ModifiedDate for all changes made to any entity objects that implement my IAuditable interface. I have a static DateProvider class and method (GetCurrentDate) that returns DateTime.Now (unless I'm running a test, in which case, it returns whatever I told it to).

I would like to automatically set the ModifiedBy property to the current user as well. What is the best way to go about doing this? Is there something that is built in the framework that will allow me to access this information or do I need to set something up kind of like the DateProvider class? This is an Active Directory environment and we use WindowsAuthentication in IIS.

Here is my SaveChanges code:

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

    if (changeSet != null)
    {
        foreach (v开发者_如何学Pythonar entry in changeSet.Where(c => c.State != EntityState.Unchanged))
        {
            entry.Entity.ModifiedDate = DateProvider.GetCurrentDate();
        }
    }
    return base.SaveChanges();
}


You can use the HttpContext.Current.User.Identity.Name to get the name of the current user.

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();
}

Better way to do this would be to use constructor injection to pass the current user to the context

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();
   }
}


I also wanted to automate the population of audit fields on my MVC 4 / Entity Framework 5 application. I used information from @Eranga's answer and this blog: http://lourenco.co.za/blog/2013/07/audit-trails-concurrency-and-soft-deletion-with-entity-framework/ to make this approach work for me with Ninject - posting in case valuable to anyone else:

Created an interface and abstract class:

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; }
}

Used them in my entities:

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

Added a constructor to MyDbContext which accepted the HttpContext and overrode 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();
    }

Finally - need to have a DbContext per request and pass HttpContext as below based on this answer: https://stackoverflow.com/a/3617961/1803682 - note that as MyDbContext is now Request scope, the Repositories must be as well.

kernel.Bind<IDataRepository>()
      .To<EFDataRepository>()
      .InRequestScope();

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜