【问题标题】:Exclude property from updating when SaveChanges() is called调用 SaveChanges() 时从更新中排除属性
【发布时间】:2013-07-24 16:25:16
【问题描述】:

似乎有两种方法可以使用“附加”方法更新断开连接的实体框架实体。

方法一是简单地将断开连接的实体的状态设置为已修改:

myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();

这将保存“狗”对象上的所有字段。但是假设您是从一个只允许编辑 Dog.Name 的 mvc 网页执行此操作,并且页面上包含的唯一 Dog 属性是 Name。然后可以做方法二:

myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();

当有很多属性要更新时,方法二可能会变得非常冗长。这促使我尝试方法三,在我不想更改的属性上设置 IsModified = false。这不起作用,抛出运行时错误“不支持将已修改的属性设置为 false”:

myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();

我更喜欢在任何地方使用方法一,但是在很多情况下,我的 asp.net mvc 视图不包含 Dog 类的每个标量属性。

我的问题是:

  1. 我是否可以在 POCO 类上使用任何属性来告诉 Entity Framework 我永远不想更新属性?例如,[永不更新]。我知道 [NotMapped] 属性,但这不是我需要的。
  2. 如果做不到这一点,有什么方法可以使用上面的方法一(myDbContext.Entry(dog).State = EntityState.Modified; ) 并排除我不想更新的字段?

附:我知道另一种方法,不使用“附加”并简单地从数据库中获取一个新对象,更新所需的属性并保存。这就是我正在做的事情,但我很好奇是否有一种方法可以使用“附加”,从而避免额外的数据库访问,但要以一种不像上面的方法二那么冗长的方式来做。 “获取一个新对象”是指:

Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();

【问题讨论】:

  • 顺便说一句,您不需要附加线。将状态更改为 modified 会自动附加它

标签: asp.net-mvc entity-framework


【解决方案1】:

以下可能有效有效。

myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;

var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
    // You need to give Foreign Key Property name
    // instead of Navigation Property name
    entry.RejectPropertyChanges("OwnerID"); 

}

myDbContext.SaveChanges();

如果要在一行中完成,请使用以下扩展方法:

public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
    var objectContext = ((IObjectContextAdapter) context).ObjectContext;
    foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
    {
        entry.RejectPropertyChanges(propertyName); 
    }
}

并像这样使用它

// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();

如您所见,您可以修改此解决方案以满足您的需求,例如使用string[] properties 而不是string propertyName 作为参数。


建议的方法

更好的解决方案是按照您的建议使用属性 ([NeverUpdate])。为了让它工作,你需要使用 SavingChanges 事件(查看我的blog):

void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
    ObjectContext context = sender as ObjectContext;
    if(context != null)
    {
        foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var type = typeof(entry.Entity);
            var properties = type.GetProperties();
            foreach( var property in properties )
            {
                var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
                if(attributes.Length > 0)
                    entry.RejectPropertyChanges(property.Name);
            }
        }
    }
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{

}

//In your POCO
public class Dogs
{
    [NeverUpdate]
    public int OwnerID { get; set; }
}

警告:我没有编译这段代码。我不在家:/


警告 2:我刚刚阅读了MSDN documentation,上面写着:

ObjectStateEntry.RejectPropertyChanges 方法

拒绝对具有给定名称的属性所做的任何更改,因为 属性上次加载、附加、保存或接受更改。 财产的原始价值被存储,财产将不会 不再被标记为已修改。

我不确定在附加修改实体的情况下它的行为是什么。我明天试试这个。


警告 3:我已经尝试过了。此解决方案有效。使用RejectPropertyChanges() 方法拒绝的属性不会在持久性单元(数据库)中更新。

但是,如果更新的实体是通过调用Attach() 附加的,则当前上下文在SaveChanges() 之后仍然是脏的。假设数据库中存在以下行:

Dogs
ID: 1
Name: Max
OwnerID: 1

考虑以下代码:

var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;

myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();

SaveChanges()后数据库的当前状态:

Dogs:
ID: 1
Name: Achilles
OwnerID: 1

SaveChanges()后myDbContext的当前状态:

var ownerId = myDog.OwnerID;  // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged

那你应该怎么做?在 SaveChanges() 之后将其分离:

Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;

【讨论】:

  • 感谢您的原始答案和精彩的解释,完美运行。
猜你喜欢
  • 2012-01-10
  • 1970-01-01
  • 1970-01-01
  • 2021-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-01
  • 1970-01-01
相关资源
最近更新 更多