Ho quattro campi personalizzati su tutte le mie entità. I quattro campi sono CreatedBy
, CreatedDate
, UpdatedBy
e UpdatedDate
.
C'è un modo per agganciare gli eventi core di Entity Framework in modo che su un inserto CreatedDate
il CreatedDate
con il DateTime
corrente e CreatedBy
con l'utente corrente? Quando c'è un aggiornamento al database, popolerà UpdatedDate
con l'attuale DateTime
e UpdatedBy
con l'utente corrente?
Fondamentalmente l'approccio di Steve è la strada da percorrere, ma l'attuale implementazione rende difficile testare l'unità del tuo progetto.
Con un po 'di refactoring, puoi renderlo unitario e rimanere fedele ai principi e all'incapsulamento SOLID.
Ecco una versione refactored dell'esempio di Steve
public abstract class AuditableEntity
{
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
}
public class AuditableDbContext : DbContext
{
protected readonly IUserService userService;
protected readonly DbContextOptions options;
protected readonly ITimeService timeService;
public BaseDbContext(DbContextOptions options, IUserService userService, ITimeService timeService) : base(options)
{
userService = userService ?? throw new ArgumentNullException(nameof(userService));
timeService = timeService ?? throw new ArgumentNullException(nameof(timeService));
}
public override int SaveChanges()
{
// get entries that are being Added or Updated
var modifiedEntries = ChangeTracker.Entries()
.Where(x => (x.State == EntityState.Added || x.State == EntityState.Modified));
var identityName = userService.CurrentUser.Name;
var now = timeService.CurrentTime;
foreach (var entry in modifiedEntries)
{
var entity = entry.Entity as AuditableEntity;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = identityName ?? "unknown";
entity.CreatedDate = now;
}
entity.UpdatedBy = identityName ?? "unknown";
entity.UpdatedDate = now;
}
return base.SaveChanges();
}
}
Ora è facile deridere il tempo e l'utente / principal per unit test e modello / dominio / livello aziendale è privo di dipendenza EF Core, incapsulando meglio la logica del dominio in modo migliore.
Ovviamente si potrebbe ulteriormente riformattare questo per usare un approccio più modulare usando il modello di strategia, ma questo è fuori portata. Puoi anche utilizzare ASP.NET Core Boilerplate che offre anche un'implementazione di un DbContext EF Core controllabile (ed eliminazione soft) ( qui e qui )
Ho esattamente lo stesso layout con quelli che chiamo i campi "Audit".
Il modo in cui ho risolto questo è stato quello di creare una classe astratta di base chiamata AuditableEntity
per contenere le proprietà stesse ed esporre un metodo chiamato PrepareSave
. All'interno di PrepareSave
ho impostato i valori dei campi come richiesto:
public abstract class AuditableEntity
{
public DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }
public DateTime UpdatedDate { get; set; }
public string UpdatedBy { get; set; }
public virtual void PrepareSave(EntityState state)
{
var identityName = Thread.CurrentPrincipal.Identity.Name;
var now = DateTime.UtcNow;
if (state == EntityState.Added)
{
CreatedBy = identityName ?? "unknown";
CreatedDate = now;
}
UpdatedBy = identityName ?? "unknown";
UpdatedDate = now;
}
}
Ho reso PrepareSave
virtuale in modo da poterlo sovrascrivere nelle mie entità se voglio. Potrebbe essere necessario modificare il modo in cui ottieni l'identità a seconda dell'implementazione.
Per chiamare questo, ho sovrascritto SaveChanges
sul mio DbContext
e DbContext
chiamato PrepareSave
su ogni entità che veniva aggiunta o aggiornata (che ho ottenuto dal change tracker):
public override int SaveChanges()
{
// get entries that are being Added or Updated
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
foreach (var entry in modifiedEntries)
{
// try and convert to an Auditable Entity
var entity = entry.Entity as AuditableEntity;
// call PrepareSave on the entity, telling it the state it is in
entity?.PrepareSave(entry.State);
}
var result = base.SaveChanges();
return result;
}
Ora, ogni volta che chiamo SaveChanges
sul mio DbContext (direttamente o attraverso un repository), qualsiasi entità che eredita AuditableEntity
avrà i campi di controllo impostati come necessari.