2014-09-19 28 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