似乎有两种方法可以使用“attach”方法更新断开连接的Entity Framework实体。
方法一是简单地将断开连接的实体的状态设置为已修改:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
这将保存“dog”对象上的所有字段。但是你说你是在mvc网页上这样做的,你只允许编辑Dog.Name,页面上唯一的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。这不起作用,抛出运行时错误“不支持修改属性将IsModified设置为false”:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
我更喜欢在任何地方使用Method One,但是在很多情况下我的asp.net mvc视图不包含Dog类的每个标量属性。
我的问题是:
P.S。我知道另一种方法,不使用“附加”,只需从数据库中获取一个新对象,更新所需的属性,然后保存。这就是我正在做的事情,但我很好奇是否有一种方法可以使用“附加”,从而避免额外的数据库访问,但这样做的方式不像上面的方法二那么冗长。通过“获取一个新鲜的对象”我的意思是:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
答案 0 :(得分:4)
以下可以正常工作。
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方法
拒绝使用给定名称对属性所做的任何更改 属性上次加载,附加,已保存或已接受更改。 存储属性的原始值,属性为no 更长时间被标记为已修改。
<击> 我不确定在附加修改后的实体时它的行为是什么。我明天会试试。 击>
警告3 :我现在已经尝试过了。此解决方案有效。使用RejectPropertyChanges()
方法拒绝的属性不会在持久性单元(数据库)中更新。
HOWEVER ,如果通过调用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;