制作AddOrUpdate变化只有一些属性 [英] Making AddOrUpdate change only some properties

查看:1292
本文介绍了制作AddOrUpdate变化只有一些属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这可能是一个简单的问题,但是我是新来的Code First和迁移所以忍耐一下。我会继续示例代码到最低限度,以显示该问题:

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:

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

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

现在一个(例如)用户 POCO从它继承:

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



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

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

现在我有对播种问题 AddOrUpdate 在迁移之后。当实体是新的(它被添加), CreatedOn 被填写正确,一切都按预期工作。然而,当实体被修改(它已经存在于数据库和用户名匹配),它试图用我创造新的实体......这失败,因为要更新 CreatedOn 具有无效的DateTime (在这种情况下, DateTime.MinValue )。

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).

有没有办法使用 AddOrUpdate 方法,因此,它实际上从检索匹配实体数据库,只需更新非默认的领域?或者,也许一些方法来告诉它哪些字段不更新?对于这个特定的情况下,我想在 CreatedOn 字段将保持不变,但一个通用的解决方案,将不胜感激。

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.

也许我应该做我自己 AddOrUpdate 方法,包括强似它一个全新的实体,我想换个领域的谓词?

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?

这是EF 6.1

我知道我可以轻松解决本作的 CreatedOn 日期,这是目前我在做什么这个特定情况:

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

推荐答案

AddOrUpdate 用途的 CurrentValues​​.SetValues​​ 使所有标量属性将被修改。

The implementation of AddOrUpdate uses CurrentValues.SetValues so that all scalar properties will be modified.

我有扩展接受的属性时,它的更新要修改的功能,否则它是一个创造,只是使用的 DbSet< T> ::添加

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.

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



用法。

Usage.

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",
    }
);

这篇关于制作AddOrUpdate变化只有一些属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆