2012-09-24 16 views
6

Parece que sobrescribe directamente SaveChanges en EF para agregar un registrador de auditoría. Consulte el método ApplyAuditLogging para establecer las propiedades de auditoría (creadas, creadas por, actualizadas, actualizadas por) a continuación.Entity Framework 5 Uso de SaveChanges para agregar el registro de auditoría

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 = "????????????????????????"; 
     } 
    } 

El problema es que cómo conseguir la ventanas de inicio de sesión de usuario/nombre de usuario para establecer las propiedades UpdatedBy y CreatedBy del objeto? ¡Por lo tanto, no podría usar esto!

Además, en otro caso, quería agregar automáticamente un nuevo registro de CallHistory a mi contacto; cada vez que se modifica el contacto, se debe agregar un nuevo registro a la tabla secundaria CallHistory. Así que lo hice en InsertOrUpdate del repositorio, pero se siente sucio, sería bueno si pudiera hacerlo a un nivel superior ya que ahora tengo que configurar el usuario actual de la base de datos. De nuevo, aquí el problema es que necesito buscar al usuario de la base de datos para crear un registro CallHistory (SalesRep = User).

El código en mi repositorio hace 2 cosas ahora, 1, se crea una entrada de auditoría sobre el objeto cuando se crea o se actualiza y, 2, sino que también crea una entrada CallHistory cada vez que el contacto se actualiza:

ContactRepository.SetCurrentUser(User).InsertOrUpdate(contact) 

con el fin de tener el usuario en el contexto de repositorio para:

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

¿hay una manera de inyectar alguna manera el usuario de windows en la anulación SaveChanges en el DbContext y es también una manera de buscar un usuario de la base de datos basada en la identificación de inicio de sesión de Windows para que pueda configurar el Sal esReptar en mi CallHistory (ver el código anterior)?

Aquí es mi acción en el controlador de aplicación 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); 
} 

Respuesta

6

Bueno, la forma simple y vago para hacerlo es simplemente acceder al HttpContext.Current.User.Identity.Name desde el código de auditoría . Sin embargo, esto creará una dependencia en System.Web. *, Que probablemente no sea lo que usted desea si tiene una aplicación muy bien nivelada (y no funcionaría si estuviese usando niveles separados reales).

Una opción sería, en lugar de anular SaveChanges, crear una sobrecarga que lleve su nombre de usuario. Luego haces tu trabajo y llamas a SaveChanges real después. La desventaja es que alguien podría llamar a SaveChanges() (el real) por error (o a propósito) y omitir la auditoría.

Una mejor manera sería simplemente agregar una propiedad _currentUser a su DbContext y usar un constructor para pasarla. Luego, cuando crea el contexto, simplemente le pasa el usuario en ese momento. Desafortunadamente, no se puede buscar al usuario en la base de datos desde el constructor.

Pero simplemente puede guardar el ContactID y agregarlo en lugar de todo el contacto. Tu contacto ya debería existir.

+3

el SamAccountName es modificable, por lo tanto, no es un buen identificador. Utilizo el directorio activo guid que todos los objetos obtienen en su lugar – meffect

0

Creo que puede estar cruzando una cierta separación de preocupaciones. El patrón de repositorio se usa para separar la lógica de su negocio, la asignación de bases de datos y las operaciones crud de su base de datos. La aplicación debe preocuparse de qué usuario está conectado, el repositorio solo debería preocuparse por guardar datos. Aconsejaría que no haga referencia a HttpContext en su repositorio porque si lo hace, su repositorio solo puede ser utilizado por una aplicación web. Si está intentando abstraer la población de este tipo de metadatos, hágalo en su aplicación ... por ejemplo en un controlador base o algo así.

1

Sé que esta es una respuesta tardía, pero acabo de contestar esta pregunta. Tuve un caso de uso muy similar. Lo hicimos de la siguiente manera:

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

Y la clase actual:

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

     } 
    } 

Esto devuelve al usuario ventanas del proceso de alta o el usuario que se registra en la applicatoin ASP.NET. Para leer más: http://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx

Cuestiones relacionadas