ADO.Net Entity Framework : (十七) 深入探討,如何對中斷物件執行變更

接續上一篇 ADO.Net Entity Framework : (十五) 深入探討,物件間傳遞,以及EntityKey、EntityState,
探討了如何將 Entity Object 在 ObjectContext 間傳遞,
這篇延續上篇介紹的技巧,來討論如何對已中斷物件執行變更,
適用在使用ObjectDataSource、分散式系統、 WebService等情境

接續上一篇 ADO.Net Entity Framework : (十五) 深入探討,物件間傳遞,以及EntityKey、EntityState
探討了如何將 Entity Object 在 ObjectContext 間傳遞,
這篇延續上篇介紹的技巧,來討論如何對已中斷物件執行變更,
適用在使用ObjectDataSource、分散式系統、 WebService等情境

一樣先來看一下範例資料庫的 ER-Modal ,讓大家對範例的資料結構清楚,接下來看範例比較了解語法

2009-10-12_122043

這邊說明一下需求,
我想建立一個 Class ,命名為 【DataAccessClass】,提供兩個Mothed,

  • 【GetData】:透過 ADO.Net Entity Framework 去 資料庫中取得指定的資料
  • 【Update】 :接受參數,並透過 ADO.Net Entity Framework 將資料更新至資料庫中

在主程式中執行以下步驟

  1. 透過 GetItem Mothed 取得資料
  2. 修改這個資料的內容
  3. 利用 Update Mothed 將資料儲存回資料庫。

因此我用上次介紹的技巧,寫了一個範例 
 

範例一


DataAccessClass 


public class DataAccessClass
{    
    //// 取得資料
    public static User GetData(int userid)
    {
        using (TestEntities te = new TestEntities())
        {
            var u = te.User.Execute(MergeOption.NoTracking).Where(a => a.User_id == userid).FirstOrDefault();
            ////利用EntityObject.Execute(MergeOption.NoTracking),效果相同於使用ObjectContext.Dettach(EntityObject)
            ////如果此時觀察 u.EntityState,其值會為 Dettached

            return u;
        }
    }

    //// 利用Attech方法,將異動寫入資料庫
    public static void Update(User item)
    {
        using (TestEntities te = new TestEntities())
        {
            te.Attach(item);            
            te.SaveChanges();
        }
    }
}

主程式 


        ////取得資料
        var u = DataAccessClass.GetData(2);
        Response.Write("修改前:" + u.User_email);

        ////修改內容
        u.User_email = "test@mail.com";

        ////將資料儲存
        DataAccessClass.Update(u);

        ////重新取得資料並顯示
        u = DataAccessClass.GetData(2);
        Response.Write("修改後:" + u.User_email);

說明

一開始利用GetData Mothed 取得資料後,
將 User 的 Email 修改,再將修改過的 User 資料利用 Update Mothed, 更新至資料庫,
其中有用到  ObjectQuery類別的 Execute(MergeOption.NoTracking) 方法,其效果與【Dettech】相同。

整個程式碼看起來正常,也預期執行完資料會被異動,
但是執行後結果卻發現資料沒有異動@@,
執行結果如下

2009-10-20_180434

這邊說明一下為什麼資料沒有被更新,
當利用【Attech】方法去附加 EntityObject 至 ObjectContext 時, 
EntityObject 的 EntityState 會是 Unchanged
,這表示該資料不會被寫回資料庫,
所以執行完才會發現資料沒有異動,
那我們又需要對 中斷物件(EntityState = Dettech 的 EntityObject) 進行變更的話該怎麼改寫?


下面提供兩種做法
 

範例二 利用ApplyPropertyChanges方法,將異動寫入資料庫


DataAccessClass


public class DataAccessClass
{
    //// 取得資料
    public static User GetData(int userid)
    {
        using (TestEntities te = new TestEntities())
        {
            var u = te.User.Execute(MergeOption.NoTracking).Where(a => a.User_id == userid).FirstOrDefault();
            return u;
        }
    }

    // 利用ApplyPropertyChanges方法,將異動寫入資料庫
    public static void Update(User item)
    {
        using (TestEntities te = new TestEntities())
        {
            te.GetObjectByKey(item.EntityKey);
            te.ApplyPropertyChanges(item.EntityKey.EntitySetName, item);
            te.SaveChanges();
        }
    }
}

主程式


        ////取得資料
        var u = DataAccessClass.GetData(2);
        Response.Write("修改前:" + u.User_email + "<br/>");

        ////修改內容
        u.User_email = "test@mail.com";

        ////將資料儲存
        DataAccessClass.Update(u);

        ////重新取得資料並顯示
        u = DataAccessClass.GetData(2);
        Response.Write("修改後:" + u.User_email);

執行結果

2009-10-20_134022

 這次執行就有順利將異動寫入資料庫囉

說明

針對幾個用到的 Mothed 進行說明

【GetObjectByKey】、【TryGetObjectByKey】

可透過此 Mothed ,利用EntityKey取得 EntityObject。
還記得上一篇有介紹過,一個EntityKey可以定義一筆資料,
因此利用 GetObjectByKey 傳入 EntityKey,
ObjectContext 就可以取得那筆資料,

【GetObjectByKey】 vs 一般查詢

那透過GetObjectByKey 跟 直接下語法去查詢差別在哪?
運作的方式不同,使用 GetObjectByKey 時,你必須先建立 EntityKey,
然後執行 GetObjectByKey 時,會先檢查 ObjectContext 內的快取,是不是有相同的資料,
有的話就,直接傳回,不會進資料庫撈資料,提升效能,
沒有的話,在進資料庫撈資料

【ApplyPropertyChanges】

此Mothed可以將 中斷物件 的值,覆蓋到 ObjectContext 上相同主鍵(EntityKey) 的 EntityObject,
且被覆蓋的 EntityObject 狀態會改為 EntityState.Modified,
因此我們可以利用此方法進行資料變更

【ApplyPropertyChanges】 vs 【Attech】

Attech 可以將中斷連結物件附加至ObjectContext,附加後狀態為 EntityState.Unchanged
ApplyPropertyChanges 可以將中斷連結物件的值,覆蓋到已附加的物件上,並修改已附加物件的狀態為 EntityState.Modified

在說明完以上幾個重要的Mothed後,再回頭來看 DataAccess.Update 流程

  1. 利用參數將異動後的 User 傳入
  2. 執行 te.GetObjectByKey(item.EntityKey)  取得此筆資料
  3. 執行 te.ApplyPropertyChanges(item.EntityKey.EntitySetName, item) ,
    將  傳入的 User 物件 的值,覆蓋,並記錄為 EntityState.modified
  4. 執行 te.SaveChanges() ,將資料寫入資料庫
  5. 完工!!

 

範例二可以解決需求並正確將資料異動,但中間的過程有疑問,
我已經將資料用參數傳入了,
但是在更新之前,會再去跟資料庫要一次資料,做了兩次工,浪費效能,
那如果我想直接以傳入的物件,利用Attech,將異動直接寫入資料庫的話,該怎麼做?

上面有提到 Attech 後的物件 EntityState 會是 Unchanged,
因此我只要把 EntityState 修改成 Modified,是不是就會寫入資料庫?
以此方向著手,下面再提供一個範例

 

範例三 利用 Attech 方法搭配 SetModifiedProperty,將異動寫入資料庫


DataAccessClass


public class DataAccessClass
{
    //// 取得資料
    public static User GetData(int userid)
    {
        using (TestEntities te = new TestEntities())
        {
            var u = te.User.Execute(MergeOption.NoTracking).Where(a => a.User_id == userid).FirstOrDefault();
            return u;
        }
    }

    // 利用Attech方法,將異動寫入資料庫
    public static void Update(User item)
    {
        using (TestEntities te = new TestEntities())
        {
            te.Attach(item);
            te.SetAllModified(item);
            te.SaveChanges();
        }
    }
}

ObjectContextExtension


public static class ObjectContextExtension
{
    public static void SetAllModified(this ObjectContext objectContext, IEntityWithKey item)
    {
        ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(item) as ObjectStateEntry;

        IEnumerable propertyNameList = stateEntry.CurrentValues.DataRecordInfo.FieldMetadata.Select(pn => pn.FieldType.Name);
        foreach (string propName in propertyNameList)
        {
            stateEntry.SetModifiedProperty(propName);
        }

        stateEntry.SetModified();
    }
}

主程式


        ////取得資料
        var u = DataAccessClass.GetData(2);
        Response.Write("修改前:" + u.User_email + "<br/>");

        ////修改內容
        u.User_email = "test2@mail.com";

        ////將資料儲存
        DataAccessClass.Update(u);

        ////重新取得資料並顯示
        u = DataAccessClass.GetData(2);
        Response.Write("修改後:" + u.User_email);

執行結果

2009-10-20_134022

說明

一樣,先來介紹幾個重要的方法

【ObjectStateEntry.SetModifiedProperty】

將指定的屬性標記為已修改。

【ObjectStateEntry.SetModified】

將物件或關聯性的狀態設定為 Modified。
 

這邊說明一下,Attech 方法附加的 EntityObject 其 EntityState = Unchanged,
為了要可以將異動寫入資料庫,
利用了  ObjectStateEntry.SetModifiedProperty 方法,來將屬性標記為已修改
以及 ObjectStateEntry.SetModified 將 EntityObject 的 EntityState 改為 Modified
但是呢,沒有提供可以把所有屬性都標示為已修改的方法,
因此我寫了一個 ObjectContextExtension.SetAllModified
將所有屬性都標示為已修改。

再回頭來看 DataAccess.Update 流程

  1. 利用參數將異動後的 User 傳入
  2. 執行 te.Attach(item); 附加至ObjectContext
  3. 執行 te.SetAllModified(item) ,將物件、物件屬性標示為Modified
  4. 執行 te.SaveChanges() ,將資料寫入資料庫
  5. 完工!!

 

兩種方法比較 【Attech 搭配 SetModifiedProperty】vs【ApplyPropertyChanges 】

Attech 搭配 SetModifiedPropertyApplyPropertyChanges
異動時,不需要去資料庫取回資料,可直接用Attech的物件來進行異動必須在ObjectContext有此資料才可呼叫此方法,因此實作上會先使用 GetObjectByKey 取得資料
可指定要異動的屬性全部屬性都會異動


 

我自己在使用ADO.Net Entity Framework時,因為架構關係,
常使用ObjectDataSource,就會常常碰到這兩篇文章討論的主題,
上面是我到處找資料研究出來的心得,
希望對大家有幫助  ^_^

 

參考連結
ObjectContext..::.ApplyPropertyChanges 方法
ObjectContext..::.GetObjectByKey 方法
ObjectStateEntry 類別
ObjectStateManager..::.TryGetObjectStateEntry 方法 (EntityKey, ObjectStateEntry%)
ObjectStateEntry..::.SetModifiedProperty 方法
ObjectStateEntry..::.SetModified 方法
HOW TO:使用特定物件的索引鍵傳回此物件 (Entity Framework)
HOW TO:套用對中斷連結的物件所做的變更 (Entity Framework)
ADO.NET Entity Framework(3)ObjectContext <--簡體版說明,資料非常詳細




 


 

  • 如果您覺得這篇文章有幫助,請您幫忙推薦一下或按上方的""給予支持,非常感激
  • 歡迎轉載,但請註明出處
  • 文章內容多是自己找資料學習到的心得,如有不詳盡或錯誤的地方,請多多指教,謝謝