2009-05-22 85 views
6

我需要設置一個EntityObject的EntityKey。我知道它的類型和它的id值。我不想不必要地查詢數據庫。EntityKey和ApplyPropertyChanges()

這工作...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    Model.EntityKey = (from Department d in db.Department 
         where d.Id == id 
         select d).FirstOrDefault().EntityKey; 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

這種失敗...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    String EntitySetName = db.DefaultContainerName + "." + Model.GetType().Name; 
    Model.EntityKey = new System.Data.EntityKey(EntitySetName, "Id", Model.Id); 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

ApplyPropertyChanges()線失敗與此異常:

的ObjectStateManager不 包含一個ObjectStateEntry和一個 參考 'Sample.Models.Department'類型的對象。

這兩個EntityKeys是相等的。爲什麼第二塊代碼失敗?我該如何解決它?

回答

9

你的第二塊代碼失敗的原因是因爲EF無法在ObjectStateManager中找到對象 - 也就是當它從數據庫中提取對象時,它將它們放入狀態管理器中,以便它可以跟蹤它們 - 這與Identity Map模式。儘管有一個EntityKey,但您的對象不在狀態管理器中,因此EF無法保留更改。你可以通過把對象放入狀態管理器來解決這個問題,但是你對它有點鬼鬼祟祟。

這工作:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    var entitySetName = db.DefaultContainerName + "." + model.GetType().Name; 
    var entityKey = new System.Data.EntityKey(entitySetName, "Id", model.Id); 

    db.Attach(new Department{Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, model); 
    db.SaveChanges(); 
} 

...但它不是很乾淨。基本上,這只是將一個「空」對象與一個實體鍵相關聯,接受所有更改,然後使用實際的實際更新值調用ApplyPropertyChanges。

下面是在擴展方法中包裝的相同的東西 - 這應該適用於任何使用主鍵的單個數據庫列的任何內容。調用該方法的唯一有趣的是,你需要告訴它如何通過委託作爲第二個參數的擴展方法來找到問題的關鍵特性:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    db.ApplyDetachedPropertyChanges(model, x => x.Id); 
    db.SaveChanges(); 
} 

和擴展方法:

public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = getIdDelegate(entity); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    db.Attach(new Department {Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 

由於擴展方法調用了AcceptAllChanges,如果您一次更新多個實體,則需要小心地調用此方法 - 如果您不小心,可能很容易「丟失」更新。因此,這種方法僅適用於簡單的更新場景 - 例如很多MVC的動作方法:)

+0

+1爲擴展方法。太棒了! =)如果表使用多列PK,該方法將如何改變? – 2009-05-22 23:15:57

+0

您需要重載使用IEnumerable KeyValuePair指定鍵的擴展方法,然後在EntityKey上調用相應的構造函數重載。爲了保持stype安全並避免在你的KeyValuePair中傳入字符串,你可以傳遞一組'key-finding'委託和值以放入關鍵字。 – 2009-05-23 08:30:10

4
public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
     var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 

     T newEntity = Activator.CreateInstance<T>(); 
     newEntity.EntityKey = db.CreateEntityKey(entitySetName, entity); 

     Type t = typeof(T); 
     foreach(EntityKeyMember keyMember in newEntity.EntityKey.EntityKeyValues) { 
      PropertyInfo p = t.GetProperty(keyMember.Key); 
      p.SetValue(newEntity, keyMember.Value, null); 
     } 

     db.Attach(newEntity); 
     //db.AcceptAllChanges(); 

     db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 
2

嘗試使用下面的代碼,並讓我知道它是否適合你。

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    using (var context = new EntityContext()) 
    { 
     try 
     { 
      Object entity = null; 
      IEnumerable<KeyValuePair<string, object>> entityKeyValues = 
       new KeyValuePair<string, object>[] { 
        new KeyValuePair<string, object>("DepartmentID", id) }; 

      // Create the key for a specific SalesOrderHeader object. 
      EntityKey key = new EntityKey("EntityContext.Deparment", 
                    entityKeyValues); 

      // Get the object from the context or the persisted store by its key. 
      if (context.TryGetObjectByKey(key, out entity)) 
      { 
       context.ApplyPropertyChanges(key.EntitySetName, Model); 
       context.SaveChanges(); 
      } 
      else 
      { 
       // log message if we need 
       //"An object with this key could not be found." 
      }     
     } 
     catch (EntitySqlException ex) 
     { 
      // log message 
     } 
    } 
}  
1

我在使用其他頁面時遇到同樣的問題。我不知道這個評論是否有用......但是我終於發現,在我的objet(相當於你的部門)中,我的「Id」沒有出現在表格中......

只是事實添加它(雖然我已經刪除它...)在我的形式已經解決了我的問題。 (與style =「visibility:hidden」,爲了不見他...)

2

改進Steve Willcock的偉大實施,這是我的建議。

它使用反射(.NET的一部分)的方式比原來的例子,以節省您的一些代碼。它也自動支持任何類型的實體類,而不僅僅是一個「部門」。

此外,它擺脫了過時的ApplyPropertyChanges方法,並使用新的ApplyCurrentValues方法。

的方法基本上只是使用反射來動態獲取「ID」屬性的值的方法,太設置它。這可以節省委託人的所有麻煩。

public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity) where T : EntityObject 
{ 
    PropertyInfo idProperty = typeof(T).GetProperty("Id"); 

    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = idProperty.GetValue(entity, null); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    Type type = entity.GetType(); 
    EntityObject obj = (EntityObject)Activator.CreateInstance(type); 

    idProperty.SetValue(obj, id, null); 
    obj.EntityKey = entityKey; 

    db.Attach(obj); 
    db.AcceptAllChanges(); 

    db.ApplyCurrentValues(entitySetName, entity); 
} 

使用

使用它相當簡單爲好。

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    db.ApplyDetachedPropertyChanges(Model); 
    db.SaveChanges(); 
} 
相關問題