Entity Framework 5 Использование SaveChanges для добавления журнала аудита

Кажется, прямо переопределить SaveChanges в EF, чтобы добавить регистратор аудита. См. метод 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 = "????????????????????????";
        }
    }

Проблема в том, что как получить логин/имя пользователя Windows для установки свойств UpdatedBy и CreatedBy объекта? Поэтому я не мог использовать это!

Кроме того, в другом случае я хотел автоматически добавить новую запись CallHistory в свой контакт; всякий раз, когда контакт изменяется, необходимо добавить новую запись в дочернюю таблицу CallHistory. Поэтому я сделал это в InsertOrUpdate репозитория, но это кажется грязным, было бы неплохо, если бы я мог сделать это на более высоком уровне, так как теперь мне нужно установить текущего пользователя из базы данных. Опять же, проблема в том, что мне нужно получить пользователя из базы данных, чтобы создать запись CallHistory (SalesRep = User).

Код в моем репозитории теперь делает 2 вещи: 1) он создает запись аудита для объекта при его создании или обновлении и 2) он также создает запись CallHistory всякий раз, когда контакт обновляется:

ContactRepository.SetCurrentUser(User).InsertOrUpdate(contact)

Чтобы иметь пользователя в контексте репозитория для:

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

Есть ли способ как-то внедрить пользователя Windows в переопределение SaveChanges в DbContext, а также есть ли способ получить пользователя из базы данных на основе идентификатора входа в Windows, чтобы я мог установить SalesRep в моей CallHistory (см. код выше)?

Вот мое действие на контроллере в приложении MVC:

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

person user1538467    schedule 24.09.2012    source источник


Ответы (3)


Ну, простой и ленивый способ сделать это — просто получить доступ к HttpContext.Current.User.Identity.Name из кода аудита. Однако это создаст зависимость от System.Web.*, что, вероятно, не то, что вам нужно, если у вас есть многоуровневое приложение (и это не сработает, если вы используете фактические отдельные уровни).

Одним из вариантов было бы вместо переопределения SaveChanges просто создать перегрузку, которая берет ваше имя пользователя. Затем вы выполняете свою работу, а затем вызываете настоящие SaveChanges. Недостатком является то, что кто-то может вызвать SaveChanges() (настоящую) по ошибке (или намеренно) и обойти проверку.

Лучшим способом было бы просто добавить свойство _currentUser в ваш DbContext и использовать конструктор для его передачи. Затем, когда вы создаете контекст, вы просто передаете пользователя в это время. К сожалению, вы не можете найти пользователя в базе данных из конструктора.

Но вы можете просто сохранить ContactID и добавить его вместо всего контакта. Ваш контакт уже должен существовать.

person Erik Funkenbusch    schedule 24.09.2012
comment
SamAccountName является изменяемым, поэтому не является хорошим идентификатором. Вместо этого я использую guid активного каталога, который получают все объекты. - person Northstrider; 05.04.2013

я знаю, что это поздний ответ, но я просто наткнулся на этот вопрос. У меня был очень похожий вариант использования. Мы сделали это следующим образом:

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

И текущий класс:

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

        }
    }

Это возвращает пользователя Windows процесса или пользователя, вошедшего в систему в приложении ASP.NET. Подробнее: http://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthentication

person Botis    schedule 18.07.2013

Я думаю, что вы, возможно, пересекаете границу разделения интересов. Шаблон репозитория используется для разделения вашей бизнес-логики, отображения базы данных и ваших операций с базой данных. Приложение должно заботиться о том, какой пользователь вошел в систему, репозиторий должен заботиться только о сохранении данных. Я бы посоветовал не ссылаться на HttpContext в вашем репозитории, потому что, если вы это сделаете, ваш репозиторий может использоваться только веб-приложением. Если вы пытаетесь абстрагироваться от такого рода метаданных, делайте это в своем приложении... например, в базовом контроллере или чем-то еще.

person user2536418    schedule 10.07.2013