This might be a simple question, however I'm new to Code First and Migrations so bear with me. I'll keep sample code to a minimum to show the problem:
I have a BaseAuditableEntity
which includes this (among other things, but let's simplify):
public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity
{
public DateTime CreatedOn { get; set; }
public DateTime LastModified { get; set; }
}
Now a (for example) User
POCO inherits from it:
public class User : BaseAuditableEntity
{
public string UserName { get; set; }
public string PasswordHash { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
public bool Active { get; set; }
public DateTime? LastLogin { get; set; }
}
I have this on my context's SaveChanges
method, to fill in the CreatedOn
and LastModified
dates (simplified):
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditableEntity>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(p => p.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
entry.Entity.CreatedOn = now;
entry.Entity.LastModified = now;
}
}
return base.SaveChanges();
}
And now I have a migration in place that seeds some users, like this:
protected override void Seed(MyContext context)
{
context.Users.AddOrUpdate(
p => p.UserName,
new User { Active = true,
FullName = "My user name",
UserName = "ThisUser",
PasswordHash = "",
Email = "my@email",
LastLogin = null,
}
// etc.
);
}
Now I have a problem on seeding with AddOrUpdate
after the migration. When the entity is new (it's being added), CreatedOn
gets filled correctly and everything works as expected. However when the entity is modified (it already exists on the database and UserName
matches), it tries to update it with the new entity I'm creating... this fails because CreatedOn
has an invalid DateTime
(in this case, DateTime.MinValue
).
Is there any way to use the AddOrUpdate
method so that it actually retrieves the matching entity from the database and just update the non-default fields? Or maybe some way to tell it which fields NOT to update? For this specific case, I'd like the CreatedOn
field to be unchanged, but a generic solution would be appreciated.
Maybe I should do my own AddOrUpdate
method which includes a predicate with the fields I want to change, instead of passing it a completely new entity?
This is EF 6.1
Update
I know I can easily solve this for the CreatedOn
date, this is what I'm currently doing for this specific case:
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entry.Entity.CreatedOn = now;
}
else
{
if (entry.Property(p => p.CreatedOn).CurrentValue == DateTime.MinValue)
{
var original = entry.Property(p => p.CreatedOn).OriginalValue;
entry.Property(p => p.CreatedOn).CurrentValue = original != SqlDateTime.MinValue ? original : now;
entry.Property(p => p.CreatedOn).IsModified = true;
}
}
entry.Entity.LastModified = now;
}
I am looking for a more generic solution though
I ran into an issue with Expression.Equals in @Yuliam's answer where a type was nullable and had to add the following.
I also updated this to fetch all records first so "SingleOrDefault" doesn't execute a sql query in each for loop iteration.
I also set AddRange which gets a little better performance.
Here is a gist of my solution. Thanks for posting this Yuliam! I've been looking for something like this for a while.
https://gist.github.com/twilly86/eb6b61a22b66b4b33717aff84a31a060
The implementation of
AddOrUpdate
usesCurrentValues.SetValues
so that all scalar properties will be modified.I have extended the functionality to accept properties to be modified when it's an update, otherwise it's a creation, just use
DbSet<T>::Add
.Usage.