2014-09-19 73 views
8

这可能是一个简单的问题,但是我是Code First和Migrations的新手,因此需要我。我会继续示例代码到最低限度,借以说明问题:制作AddOrUpdate仅更改一些属性

我有一个BaseAuditableEntity,其中包括本(除其他事项外,但让我们简化):

public abstract class BaseAuditableEntity : BaseEntity, IAuditableEntity 
{ 
    public DateTime CreatedOn { get; set; } 
    public DateTime LastModified { get; set; } 
} 

现在,(例如)User POCO从它继承:

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

我有这个在我的上下文的SaveChanges方法,填补了CreatedOnLastModified日期(简体):

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

现在我有地方种子一些用户来说,这样的迁移:

protected override void Seed(MyContext context) 
{ 
    context.Users.AddOrUpdate(
     p => p.UserName, 
     new User { Active = true, 
       FullName = "My user name", 
       UserName = "ThisUser", 
       PasswordHash = "", 
       Email = "[email protected]", 
       LastLogin = null, 
      } 
     // etc. 
    ); 
} 

现在我有播种有问题,在迁移之后AddOrUpdate。当实体是新的时候(它被添加),CreatedOn被正确填充并且一切按预期工作。但是,当实体被修改时(它已经存在于数据库和UserName匹配项中),它会尝试使用我正在创建的新实体更新它...因为CreatedOnDateTime(本例中为DateTime.MinValue)的值无效。

有什么方法可以使用AddOrUpdate方法,以便它实际上从数据库中检索匹配的实体并更新非默认字段?或者,也许某种方式来告诉它哪些字段不更新?对于这种特定情况,我希望CreatedOn字段保持不变,但一般的解决方案将不胜感激。

也许我应该做我自己的AddOrUpdate方法,其中包括一个谓词与我想改变的领域,而不是传递一个全新的实体?

这是EF 6.1

更新

我知道我可以很容易地解决这个为CreatedOn日期,这是目前我在做什么,这种特定的情况:

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

我我正在寻找更通用的解决方案

回答

10

AddOrUpdate的实现使用CurrentValues.SetValues,以便所有标量专业版perties将被修改。

我已经扩展了接受要修改的属性的功能,当它是一个更新时,否则它是一个创建,只是使用DbSet<T>::Add

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Data.Entity.Migrations; 
using System.Diagnostics; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 

public static class SeedExtension 
{ 
    public static void Upsert<T>(this DbContext db, Expression<Func<T, object>> identifierExpression, Expression<Func<T, object>> updatingExpression, params T[] entities) 
     where T : class 
    { 
     if (updatingExpression == null) 
     { 
      db.Set<T>().AddOrUpdate(identifierExpression, entities); 
      return; 
     } 

     var identifyingProperties = GetProperties<T>(identifierExpression).ToList(); 
     Debug.Assert(identifyingProperties.Count != 0); 

     var updatingProperties = GetProperties<T>(updatingExpression).Where(pi => IsModifiedable(pi.PropertyType)).ToList(); 
     Debug.Assert(updatingProperties.Count != 0); 

     var parameter = Expression.Parameter(typeof(T)); 
     foreach (var entity in entities) 
     { 
      var matches = identifyingProperties.Select(pi => Expression.Equal(Expression.Property(parameter, pi.Name), Expression.Constant(pi.GetValue(entity, null)))); 
      var matchExpression = matches.Aggregate<BinaryExpression, Expression>(null, (agg, v) => (agg == null) ? v : Expression.AndAlso(agg, v)); 

      var predicate = Expression.Lambda<Func<T, bool>>(matchExpression, new[] { parameter }); 
      var existing = db.Set<T>().SingleOrDefault(predicate); 
      if (existing == null) 
      { 
       // New. 
       db.Set<T>().Add(entity); 
       continue; 
      } 

      // Update. 
      foreach (var prop in updatingProperties) 
      { 
       var oldValue = prop.GetValue(existing, null); 
       var newValue = prop.GetValue(entity, null); 
       if (Equals(oldValue, newValue)) continue; 

       db.Entry(existing).Property(prop.Name).IsModified = true; 
       prop.SetValue(existing, newValue);      
      } 
     } 
    } 

    private static bool IsModifiedable(Type type) 
    { 
     return type.IsPrimitive || type.IsValueType || type == typeof(string); 
    } 

    private static IEnumerable<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> exp) where T : class 
    { 
     Debug.Assert(exp != null); 
     Debug.Assert(exp.Body != null); 
     Debug.Assert(exp.Parameters.Count == 1); 

     var type = typeof(T); 
     var properties = new List<PropertyInfo>(); 

     if (exp.Body.NodeType == ExpressionType.MemberAccess) 
     { 
      var memExp = exp.Body as MemberExpression; 
      if (memExp != null && memExp.Member != null) 
       properties.Add(type.GetProperty(memExp.Member.Name)); 
     } 
     else if (exp.Body.NodeType == ExpressionType.Convert) 
     { 
      var unaryExp = exp.Body as UnaryExpression; 
      if (unaryExp != null) 
      { 
       var propExp = unaryExp.Operand as MemberExpression; 
       if (propExp != null && propExp.Member != null) 
        properties.Add(type.GetProperty(propExp.Member.Name)); 
      } 
     } 
     else if (exp.Body.NodeType == ExpressionType.New) 
     { 
      var newExp = exp.Body as NewExpression; 
      if (newExp != null) 
       properties.AddRange(newExp.Members.Select(x => type.GetProperty(x.Name))); 
     } 

     return properties.OfType<PropertyInfo>(); 
    } 
} 

用法。

context.Upsert(
    p => p.UserName, 
    p => new { p.Active, p.FullName, p.Email }, 
    new User 
    { 
     Active = true, 
     FullName = "My user name", 
     UserName = "ThisUser", 
     Email = "[email protected]", 
    } 
); 
+1

谢谢你的'Upsert'方法工作的很好,这正是我所期待的! – Jcl 2014-09-19 10:02:51

+0

我已经修改了更新,以便它检查属性的值不是相同的值(对于基元/值/字符串类型),所以如果它们的值相同,并且它们不会将实体标记为“已修改” Unchanged'。为了播种的目的,我相信这会更好。 在我的问题的具体情况下,这使得它始终不修改'LastModified' – Jcl 2014-09-19 11:06:24

+2

@JCL,你是对的,这可以防止不必要的更新。我相应地更新了答案 – 2014-09-19 11:56:56