2012-09-24 10 views
6

Sembra ignorare i ChangeChanges in EF per aggiungere un registratore di controllo. Vedere il metodo ApplyAuditLogging per impostare le proprietà di controllo (create, createby, updated, updatedby) di seguito.Entity Framework 5 Utilizzo di SaveChanges per aggiungere il registro di controllo

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

Il problema è che come ottenere il finestre di accesso utente/nome utente per impostare le proprietà UpdatedBy e CreatedBy dell'oggetto? Quindi non potrei usare questo!

Inoltre, in un altro caso, ho voluto aggiungere automaticamente un nuovo record CallHistory al mio contatto; ogni volta che il contatto viene modificato, è necessario aggiungere un nuovo record alla tabella figlio CallHistory. Quindi l'ho fatto in InsertOrUpdate del repository ma sembra sporco, sarebbe bello se potessi farlo ad un livello più alto come ora devo impostare l'utente corrente dal database. Anche qui il problema è che ho bisogno di recuperare l'utente dal database per creare un record CallHistory (SalesRep = User).

Il codice nel mio repository fa 2 cose ora, 1, ha creato una voce di controllo per l'oggetto quando viene creato o aggiornato e, 2, ha anche creato una voce CallHistory ogni volta che il contatto viene aggiornato:

ContactRepository.SetCurrentUser(User).InsertOrUpdate(contact) 

al fine di avere l'utente nel contesto Repository per:

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

c'è un modo per iniettare qualche modo l'utente di Windows in l'override SaveChanges nel DbContext e c'è anche un modo per andare a prendere un utente da il database basato su ID di accesso di Windows in modo da poter impostare il Sal esRep sul mio CallHistory (vedi sopra il codice)?

Qui è la mia azione sul controller MVC app:

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

risposta

6

Beh, il modo semplice e pigro per farlo è quello di accedere semplicemente il HttpContext.Current.User.Identity.Name dal codice di controllo . Tuttavia, ciò creerà una dipendenza da System.Web. *, Che probabilmente non è ciò che si desidera se si dispone di un'applicazione gradatamente a livelli (e non funzionerebbe se si utilizzassero livelli separati effettivi).

Un'opzione sarebbe, invece di sovrascrivere SaveChanges, solo creare un sovraccarico che prende il tuo nome utente. Quindi fai il tuo lavoro e chiama i veri SaveChanges in seguito. Lo svantaggio è che qualcuno potrebbe chiamare SaveChanges() (quello reale) per errore (o di proposito) e bypassare l'auditing.

Un modo migliore sarebbe semplicemente aggiungere una proprietà _currentUser a DbContext e utilizzare un costruttore per passarlo. Quindi quando si crea il contesto, si passa semplicemente l'utente in quel momento. Sfortunatamente, non è possibile cercare l'utente nel database dal costruttore.

Ma è sufficiente salvare il ContactID e aggiungerlo al posto dell'intero contatto. Il tuo contatto dovrebbe già esistere.

+3

SamAccountName è modificabile quindi non è un buon identificatore. Io uso il guid della directory attiva che invece ottengono tutti gli oggetti – meffect

0

Penso che tu stia attraversando qualche separazione delle preoccupazioni. Il modello di repository viene utilizzato per separare la business logic, il mapping del database e le operazioni di database crud. L'applicazione dovrebbe riguardare l'utente che ha effettuato l'accesso, il repository dovrebbe preoccuparsi solo del salvataggio dei dati. Vi sconsigliamo di fare riferimento a HttpContext nel vostro repository perché se lo fate allora il vostro repository può essere usato solo da un'applicazione web. Se stai cercando di astrarre la popolazione di questo tipo di metadati, fallo nella tua applicazione ... per esempio in un controller di base o qualcosa del genere.

1

so che questa è una risposta tardiva, ma mi sono limitato a questa domanda. Ho avuto un caso d'uso molto simile. L'abbiamo fatto nel modo seguente:

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

E la classe corrente:

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

     } 
    } 

Questo riporta l'utente di Windows del proccess, o l'utente che ha effettuato l'accesso a nella applicatoin ASP.NET. Per saperne di più: http://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx

Problemi correlati