简体   繁体   English

实体框架5使用SaveChanges添加审核日志

[英]Entity Framework 5 Using SaveChanges to add audit log

Seems straight forward override SaveChanges in EF to add an audit logger. 似乎直接覆盖EF中的SaveChanges以添加审计记录器。 See the ApplyAuditLogging method to set the audit properties (created, createdby, updated, updatedby) below. 请参阅ApplyAuditLogging方法以设置下面的审计属性(创建,创建,更新,更新)。

   public override int SaveChanges()
    {
        var autoDetectChanges = Configuration.AutoDetectChangesEnabled;

        try
        {
            Configuration.AutoDetectChangesEnabled = false;
            ChangeTracker.DetectChanges();
            var errors = GetValidationErrors().ToList();
            if(errors.Any())
            {
                throw new DbEntityValidationException("Validation errors were found during save: " + errors);
            }

            foreach (var entry in ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
            {
                ApplyAuditLogging(entry);
            }

            ChangeTracker.DetectChanges();

            Configuration.ValidateOnSaveEnabled = false;

            return base.SaveChanges();
        }
        finally
        {
            Configuration.AutoDetectChangesEnabled = autoDetectChanges;
        }
    }

    private static void ApplyAuditLogging(DbEntityEntry entityEntry)
    {

        var logger = entityEntry.Entity as IAuditLogger;
        if (logger == null) return;

        var currentValue = entityEntry.Cast<IAuditLogger>().Property(p => p.Audit).CurrentValue;
        if (currentValue == null) currentValue = new Audit();
        currentValue.Updated = DateTime.Now;
        currentValue.UpdatedBy = "???????????????????????";
        if(entityEntry.State == EntityState.Added)
        {
            currentValue.Created = DateTime.Now;
            currentValue.CreatedBy = "????????????????????????";
        }
    }

The problem is that how to get the windows user logon/username to set the UpdatedBy and CreatedBy properties of the object? 问题是如何让windows用户登录/用户名来设置对象的UpdatedBy和CreatedBy属性? I could therefore not use this! 因此,我不能使用它!

Also, in another case I wanted to automatically add a new CallHistory record to my Contact; 另外,在另一种情况下,我想自动将新的CallHistory记录添加到我的联系人; whenever the contact is modified, a new record needs to be added to the child table CallHistory. 每当修改联系人时,都需要将新记录添加到子表CallHistory中。 So I did it in InsertOrUpdate of the Repository but it feels dirty, would be nice if I could do it at a higher level as now I have to set the current user from the database. 所以我在Repository的InsertOrUpdate中做了它,但它感觉很脏,如果我可以在更高级别执行它会很好,因为现在我必须从数据库设置当前用户。 Again here the problem is that I need to fetch the user from the database to create a CallHistory record (SalesRep = User). 在这里,问题是我需要从数据库中获取用户以创建CallHistory记录(SalesRep = User)。

The code in my Repository does 2 things now, 1, it created an audit entry on the object when it is created or updated and, 2, it also created a CallHistory entry whenever the Contact is updated: 我的存储库中的代码现在做了2件事,1,它在创建或更新对象时创建了一个审计条目,2,每当更新联系人时它还创建了一个CallHistory条目:

ContactRepository.SetCurrentUser(User).InsertOrUpdate(contact)

In order to have the user in the Repository context for: 为了让用户在Repository上下文中:

    var prop = typeof(T).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

    if (prop.GetValue(entity, null).ToString() == "0")
    {
        // New entity
        _context.Set<T>().Add(entity);
        var auditLogger = entity as IAuditLogger;
        if (auditLogger != null)
            auditLogger.Audit = new Audit(true, _principal.Identity.Name);
    }
    else
    {
        // Existing entity
        _context.Entry(entity).State = EntityState.Modified;
        var auditLogger = entity as IAuditLogger;
        if (auditLogger != null && auditLogger.Audit != null)
        {
            (entity as IAuditLogger).Audit.Updated = DateTime.Now;
            (entity as IAuditLogger).Audit.UpdatedBy = _principal.Identity.Name;
        }

        var contact = entity as Contact;
        if (_currentUser != null)
            contact.CallHistories.Add(new CallHistory
                {
                    CallTime = DateTime.Now,
                    Contact = contact,
                    Created = DateTime.Now,
                    CreatedBy = _currentUser.Logon,
                    SalesRep = _currentUser
                });
    }
}

Is there a way to somehow inject the windows user into the SaveChanges override in the DbContext and is there also a way to fetch a User from the database based on windows logon id so I can set the SalesRep on my CallHistory (see above code)? 有没有办法以某种方式将Windows用户注入到DbContext中的SaveChanges覆盖中,还有一种方法可以根据Windows登录ID从数据库中获取用户,这样我就可以在我的CallHistory上设置SalesRep(参见上面的代码)?

Here is my Action on controller on MVC app: 这是我在MVC应用程序上的控制器上的Action:

[HttpPost]
public ActionResult Create([Bind(Prefix = "Contact")]Contact contact, FormCollection collection)
{
    SetupVOs(collection, contact, true);
    SetupBuyingProcesses(collection, contact, true);

    var result = ContactRepository.Validate(contact);

    Validate(result);

    if (ModelState.IsValid)
    {
        ContactRepository.SetCurrentUser(User).InsertOrUpdate(contact);
        ContactRepository.Save();
        return RedirectToAction("Edit", "Contact", new {id = contact.Id});
    }

    var viewData = LoadContactControllerCreateViewModel(contact);

    SetupPrefixDropdown(viewData, contact);

    return View(viewData);
}

Well, the simple and lazy way to do it is to simply access the HttpContext.Current.User.Identity.Name from within your audit code. 嗯,简单而懒惰的方法是简单地从审计代码中访问HttpContext.Current.User.Identity.Name。 However, this will create a dependency on System.Web.*, which is probably not what you want if you have a nicely tiered application (and it wouldn't work if you were using actual seperate tiers). 但是,这将创建一个依赖于System.Web。*,如果你有一个很好的分层应用程序,它可能不是你想要的(如果你使用实际的单独层,它将无法工作)。

One option would be, instead of overriding SaveChanges, just create an overload that takes your username. 一种选择是,而不是覆盖SaveChanges,只需创建一个带有用户名的重载。 Then you do your work, and call the real SaveChanges afterwards. 然后你做你的工作,然后调用真正的SaveChanges。 The disadvantage is that someone could call SaveChanges() (the real one) by mistake (or on purpose) and bypass the auditing. 缺点是有人可能会错误地(或故意)调用SaveChanges()(真正的)并绕过审计。

A better way would be to simply add a _currentUser property to your DbContext and uase a constructor to pass it in. Then when you create the context, you just pass the user in at that time. 更好的方法是简单地向您的DbContext添加一个_currentUser属性,并使用构造函数将其传入。然后在创建上下文时,您只需在此时传递用户。 Unfortunately, you can't really look up the user in the database from the constructor. 不幸的是,您无法从构造函数中查找数据库中的用户。

But you can simply save the ContactID and add that instead of the entire contact. 但您可以直接保存ContactID并添加,而不是整个联系人。 You're Contact should already exist. 您的联系人应该已经存在。

i know that this is a late answer but i just stubmled on this question. 我知道这是一个迟到的答案,但我只是纠结于这个问题。 I had a very similar use case. 我有一个非常相似的用例。 We did it as follows: 我们这样做了:

var auditUsername = Current.User.Identity.Name;
var auditDate = DateTime.Now;

And the current class: 而目前的课程:

public class Current
    {
        public static IPrincipal User
        {
            get
            {
                return System.Threading.Thread.CurrentPrincipal;
            }
            set
            {
                System.Threading.Thread.CurrentPrincipal = value;
            }

        }
    }

This returns the windows user of the proccess, or the user that is logged in in the ASP.NET applicatoin. 这将返回proccess的Windows用户或ASP.NET应用程序中登录的用户。 To read more: http://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx 阅读更多内容: http//www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx

I think you may be crossing some seperation of concerns boundry. 我想你可能正在跨越一些关注边界的问题。 The repository pattern is used to separate your business logic, database mapping and your database crud operations. 存储库模式用于分隔业务逻辑,数据库映射和数据库crud操作。 The application should be concerned with what user is logged in, the repository should only be concerned with saving data. 应用程序应关注用户登录的内容,存储库应仅关注保存数据。 I would advise against referencing HttpContext in your repository because if you do then your repository can only be used by a web application. 我建议不要在您的存储库中引用HttpContext,因为如果您这样做,那么您的存储库只能由Web应用程序使用。 If you are trying to abstract the population of this kind of metadata, do it in your application...for instance in a base controller or something. 如果您正在尝试抽象此类元数据的填充,请在您的应用程序中执行此操作...例如在基本控制器或其他内容中。

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

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