这可能是一个简单的问题,但是我是Code First和Migrations的新手,所以请多多包涵。我将示例代码减至最少以显示该问题:

我有一个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();
}

现在,我已经进行了迁移,从而为一些用户注入(inject)了种子,如下所示:
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.
    );
}

现在,我在迁移后使用AddOrUpdate进行播种时遇到了问题。当实体是新实体(正在添加)时,CreatedOn会正确填充,并且一切都会按预期进行。但是,当实体被修改时(它已经存在于数据库中并且与UserName匹配),它将尝试使用我正在创建的新实体对其进行更新...此操作失败,因为CreatedOn包含无效的DateTime(在这种情况下为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;
}

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

最佳答案

AddOrUpdate 的实现使用 CurrentValues.SetValues ,以便所有标量属性都将被修改。

我已经扩展了功能,使其可以接受要在更新时进行修改的属性,否则,它是一种创建,只需使用 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 = "my@email",
    }
);

关于c# - 使AddOrUpdate仅更改某些属性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25928353/

10-12 00:42