Entity Framework / SQL2008 – Automatisches Aktualisieren von LastModified-Feldern für Entitäten?

Wenn ich die folgende Entität habe:

public class PocoWithDates { public string PocoName { get; set; } public DateTime CreatedOn { get; set; } public DateTime LastModified { get; set; } } 

Dies entspricht einer SQL Server 2008-Tabelle mit demselben Namen / denselben Attributen …

Wie kann ich automatisch :

  1. Setzen Sie das CreatedOn / LastModified-Feld für den Datensatz auf jetzt (wenn INSERT)
  2. Setzen Sie das LastModified Feld für den Datensatz auf jetzt (beim UPDATE)

Wenn ich automatisch sage, ich meine, ich möchte das tun können:

 poco.Name = "Changing the name"; repository.Save(); 

Nicht das:

 poco.Name = "Changing the name"; poco.LastModified = DateTime.Now; repository.Save(); 

Hinter den Kulissen sollte “etwas” automatisch die Datetime-Felder aktualisieren. Was ist das “Etwas”?

Ich benutze Entity Framework 4.0 – gibt es eine Möglichkeit, dass EF das automatisch für mich tun kann? (eine spezielle Einstellung in der EDMX vielleicht?)

Auf der SQL-Server-Seite kann ich DefaultValue verwenden , aber das funktioniert nur für INSERTs (nicht UPDATEs).

Ähnlich kann ich einen Standardwert mit einem Konstruktor auf den POCOs setzen, aber das funktioniert auch nur, wenn das Objekt instanziiert wird.

Und natürlich könnte ich Trigger verwenden, aber es ist nicht ideal.

Da ich Entity Framework verwende, kann ich mich in das SavingChanges- Event einklinken und die Datumsfelder hier aktualisieren, aber das Problem ist, dass ich die POCOs “kennen” muss (im Moment ist mein Repository mit Generics implementiert). Ich würde eine Art von OO-Trickserei machen müssen (wie zum Beispiel mein POCO’s eine Schnittstelle implementieren und eine Methode dafür aufrufen). Ich bin nicht dazu angetan, aber wenn ich das tun müsste, würde ich lieber die Felder manuell setzen.

Ich suche im Grunde nach einer SQL Server 2008 oder Entity Framework 4.0 Lösung. (oder eine intelligente .NET-Methode)

Irgendwelche Ideen?

BEARBEITEN

Danke an @marc_s für seine Antwort, aber ich ging mit einer Lösung, die für mein Szenario besser ist.

Ich weiß, dass ich ein wenig zu spät zur Party komme, aber ich habe das gerade für ein Projekt getriggers, an dem ich gerade arbeite und dachte, ich würde meine Lösung teilen.

Um die Lösung besser wiederverwendbar zu machen, habe ich zunächst eine Basisklasse mit den Zeitstempeleigenschaften erstellt:

 public class EntityBase { public DateTime? CreatedDate { get; set; } public DateTime? LastModifiedDate { get; set; } } 

Dann habe ich die SaveChanges-Methode auf meinem DbContext überschrieben:

 public class MyContext : DbContext { public override int SaveChanges() { ObjectContext context = ((IObjectContextAdapter)this).ObjectContext; //Find all Entities that are Added/Modified that inherit from my EntityBase IEnumerable objectStateEntries = from e in context.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified) where e.IsRelationship == false && e.Entity != null && typeof(EntityBase).IsAssignableFrom(e.Entity.GetType()) select e; var currentTime = DateTime.Now; foreach (var entry in objectStateEntries) { var entityBase = entry.Entity as EntityBase; if (entry.State == EntityState.Added) { entityBase.CreatedDate = currentTime; } entityBase.LastModifiedDate = currentTime; } return base.SaveChanges(); } } 

Da ich eine Service-Schicht zwischen meinen Controllern (ich benutze ASP.NET MVC) und meinem Repository habe, habe ich beschlossen, die Felder hier zu setzen.

Auch meine POCOs haben keine Beziehungen / Abstraktionen, sie sind völlig unabhängig. Ich möchte es auf diese Weise beibehalten und keine virtuellen Eigenschaften markieren oder Basisklassen erstellen.

Also habe ich eine Schnittstelle, IAutoGenerateDateFields, erstellt:

 public interface IAutoGenerateDateFields { DateTime LastModified { get;set; } DateTime CreatedOn { get;set; } } 

Für alle POCOs, die ich diese Felder automatisch erzeugen möchte, implementiere ich diese Schnittstelle.

Anhand des Beispiels in meiner Frage:

 public class PocoWithDates : IAutoGenerateDateFields { public string PocoName { get; set; } public DateTime CreatedOn { get; set; } public DateTime LastModified { get; set; } } 

In meiner Service-Schicht überprüfe ich jetzt, ob das konkrete Objekt die Schnittstelle implementiert:

 public void Add(SomePoco poco) { var autoDateFieldsPoco = poco as IAutoGenerateDateFields; // returns null if it's not. if (autoDateFieldsPoco != null) // if it implements interface { autoDateFieldsPoco.LastModified = DateTime.Now; autoDateFieldsPoco.CreatedOn = DateTime.Now; } // ..go on about other persistence work. } 

Ich werde diesen Code wahrscheinlich später in der Methode Add to a helper / extension auflösen.

Aber ich denke, das ist eine anständige Lösung für mein Szenario, da ich keine virtuellen Dateien beim Speichern verwenden möchte (da ich Unit of Work, Repository und POCOs verwende) und keine Trigger verwenden möchte.

Wenn Sie irgendwelche Gedanken / Vorschläge haben, lassen Sie es mich wissen.

Ich möchte auch eine späte Lösung vorschlagen. Dieser gilt nur für .NET Framework 4, macht diese Art von Aufgabe jedoch trivial.

 var vs = oceContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); foreach (var v in vs) { dynamic dv = v.Entity; dv.DateLastEdit = DateTime.Now; } 

Hier ist eine bearbeitete Version der vorherigen Antwort. Vorheriges hat nicht für Updates für mich gearbeitet.

 public override int SaveChanges() { var objectStateEntries = ChangeTracker.Entries() .Where(e => e.Entity is TrackedEntityBase && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList(); var currentTime = DateTime.UtcNow; foreach (var entry in objectStateEntries) { var entityBase = entry.Entity as TrackedEntityBase; if (entityBase == null) continue; if (entry.State == EntityState.Added) { entityBase.CreatedDate = currentTime; } entityBase.LastModifiedDate = currentTime; } return base.SaveChanges(); } 

Sie haben zwei Möglichkeiten:

  • haben Sie eine Basisklasse für alle Ihre Business-Entity-classn, die das tut

     poco.LastModified = DateTime.Now; 

    in einer virtuellen .Save() -Methode, die alle anderen aufrufen müssten

  • Verwenden Sie einen Trigger in der database

Ich denke nicht, dass es eine andere, einigermaßen sichere und einfache Methode gibt, um dies zu erreichen.

Wir können partielle classn verwenden und die SaveChanges-Methode überschreiben, um dies zu erreichen.

 using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Web; namespace TestDatamodel { public partial class DiagnosisPrescriptionManagementEntities { public override int SaveChanges() { ObjectContext context = ((IObjectContextAdapter)this).ObjectContext; foreach (ObjectStateEntry entry in (context.ObjectStateManager .GetObjectStateEntries(EntityState.Added | EntityState.Modified))) { if (!entry.IsRelationship) { CurrentValueRecord entryValues = entry.CurrentValues; if (entryValues.GetOrdinal("ModifiedBy") > 0) { HttpContext currentContext = HttpContext.Current; string userId = "nazrul"; DateTime now = DateTime.Now; if (currContext.User.Identity.IsAuthenticated) { if (currentContext .Session["userId"] != null) { userId = (string)currentContext .Session["userId"]; } else { userId = UserAuthentication.GetUserId(currentContext .User.Identity.UserCode); } } if (entry.State == EntityState.Modified) { entryValues.SetString(entryValues.GetOrdinal("ModifiedBy"), userId); entryValues.SetDateTime(entryValues.GetOrdinal("ModifiedDate"), now); } if (entry.State == EntityState.Added) { entryValues.SetString(entryValues.GetOrdinal("CreatedBy"), userId); entryValues.SetDateTime(entryValues.GetOrdinal("CreatedDate"), now); } } } } return base.SaveChanges(); } } } 

musste Basis-Entität hinzufügen, erweitern Sie es aus meiner database erste Teilklasse (was nicht ideal ist)

  public override int SaveChanges() { AddTimestamps(); return base.SaveChanges(); } public override async Task SaveChangesAsync() { AddTimestamps(); return await base.SaveChangesAsync(); } private void AddTimestamps() { //var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && (x.State == EntityState.Added || x.State == EntityState.Modified)); //ObjectiveContext context = ((IObjectContextAdapter)this).ObjectContext; var entities = ChangeTracker.Entries().Where(e => e.Entity is BaseEntity && (e.State == EntityState.Modified || e.State == EntityState.Added)).ToList(); var currentUsername = !string.IsNullOrEmpty(System.Web.HttpContext.Current?.User?.Identity?.Name) ? HttpContext.Current.User.Identity.Name : "Anonymous"; foreach (var entity in entities) { if (entity.State == EntityState.Added) { ((BaseEntity)entity.Entity).CREATEDON = DateTime.UtcNow; ((BaseEntity)entity.Entity).CREATEDBY = currentUsername; } ((BaseEntity)entity.Entity).MODIFIEDON = DateTime.UtcNow; ((BaseEntity)entity.Entity).MODIFIEDBY = currentUsername; } }