问题
If i have the following entity:
public class PocoWithDates
{
public string PocoName { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
Which corresponds to a SQL Server 2008 table with the same name/attributes...
How can i automatically:
- Set the CreatedOn/LastModified field for the record to now (when doing INSERT)
- Set the LastModified field for the record to now (when doing UPDATE)
When i say automatically, i mean i want to be able to do this:
poco.Name = \"Changing the name\";
repository.Save();
Not this:
poco.Name = \"Changing the name\";
poco.LastModified = DateTime.Now;
repository.Save();
Behind the scenes, \"something\" should automatically update the datetime fields. What is that \"something\"?
I\'m using Entity Framework 4.0 - is there a way that EF can do that automatically for me? (a special setting in the EDMX maybe?)
From the SQL Server side, i can use DefaultValue, but that will only work for INSERT\'s (not UPDATE\'s).
Similarly, i can set a default value using a constructor on the POCO\'s, but again this will only work when instantiating the object.
And of course i could use Triggers, but it\'s not ideal.
Because i\'m using Entity Framework, i can hook into the SavingChanges event and update the date fields here, but the problem is i need to become \"aware\" of the POCO\'s (at the moment, my repository is implemented with generics). I would need to do some sort of OO trickery (like make my POCO\'s implement an interface, and call a method on that). I\'m not adversed to that, but if i have to do that, i would rather manually set the fields.
I\'m basically looking for a SQL Server 2008 or Entity Framework 4.0 solution. (or a smart .NET way)
Any ideas?
EDIT
Thanks to @marc_s for his answer, but i went with a solution which is better for my scenario.
回答1:
I know I'm a little late to the party, but I just solved this for a project I'm working on and thought I'd share my solution.
First, to make the solution more re-usable, I created a base class with the timestamp properties:
public class EntityBase
{
public DateTime? CreatedDate { get; set; }
public DateTime? LastModifiedDate { get; set; }
}
Then I overrode the SaveChanges method on my DbContext:
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<ObjectStateEntry> 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();
}
}
回答2:
As i have a service layer mediating between my controllers (im using ASP.NET MVC), and my repository, i have decided to auto-set the fields here.
Also, my POCO's have no relationships/abstractions, they are completely independant. I would like to keep it this way, and not mark any virtual properties, or create base classes.
So i created an interface, IAutoGenerateDateFields:
public interface IAutoGenerateDateFields
{
DateTime LastModified { get;set; }
DateTime CreatedOn { get;set; }
}
For any POCO's i wish to auto-generate these fields, i implement this inteface.
Using the example in my question:
public class PocoWithDates : IAutoGenerateDateFields
{
public string PocoName { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
In my service layer, i now check if the concrete object implements the interface:
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.
}
I will probably break that code in the Add out to a helper/extension method later on.
But i think this is a decent solution for my scenario, as i dont want to use virtuals on the Save (as i'm using Unit of Work, Repository, and Pure POCO's), and don't want to use triggers.
If you have any thoughts/suggestions, let me know.
回答3:
I would also like to put forward a late solution. This one is only applicable to the .NET Framework 4 but makes this sort of task trivial.
var vs = oceContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var v in vs)
{
dynamic dv = v.Entity;
dv.DateLastEdit = DateTime.Now;
}
回答4:
here is edited version of previous response. Previous one didn't work for updates for me.
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();
}
回答5:
You have two options:
have a base class for all your business entity classes that does the
poco.LastModified = DateTime.Now;
in a virtual
.Save()
method that all others would have to calluse a trigger in the database
I don't think there's any other reasonably safe and easy method to achieve this.
回答6:
had to add base entity extend it from my database first partial class (which is not ideal)
public override int SaveChanges()
{
AddTimestamps();
return base.SaveChanges();
}
public override async Task<int> 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;
}
}
回答7:
We can use partial class and override SaveChanges method to achieve this.
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();
}
}
}
来源:https://stackoverflow.com/questions/3879011/entity-framework-sql2008-how-to-automatically-update-lastmodified-fields-for-e