程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 使用LINQ to SQL更新數據庫(中):幾種解決方案

使用LINQ to SQL更新數據庫(中):幾種解決方案

編輯:關於.NET

在前一篇文章中,我提出了在使用LINQ to SQL進行更新操作時可能會遇到的幾種問題。其實這並不是 我一個人遇到的問題,當我在互聯網上尋找答案時,我發現很多人都對這個話題發表過類似文章。但另我 無法滿足的是,他們盡管提出了問題,卻沒有進行詳細的剖析,只給出了解決方案(如添加RowVersion列 、去除關聯等),但卻沒有說明為什麼必須這麼做。這也是我寫上篇的初衷,希望通過對LINQ to SQL源 代碼的分析,來一步一步找出解決問題的辦法。本文將對這些方法一一進行討論。

方案一:重新賦值

在TerryLee、Anytao和Ding Xue等人的開源框架Ezsocio中,有些地方采取了重新賦值的方法。在 Update方法內部,根據主鍵獲取數據庫中的實體,然後與參數中的實體對其屬性一一賦值。

public void UpdateProfile(Profile p)
{
   using (RepositoryContext db = new RepositoryContext())
   {
     var profile = db.GetTable<Profile>().First<Profile>(u => u.ID  == p.ID);
     profile.Birthday = p.Birthday;
     profile.Gender = p.Gender;
     profile.Hometown = p.Hometown;
     profile.MSN = p.MSN;
     profile.NickName = p.NickName;
     profile.PhoneNumber = p.PhoneNumber;
     profile.QQ = p.QQ;
     profile.State = p.State;
     profile.TrueName = p.TrueName;
     profile.StateRefreshTime = p.StateRefreshTime;
     profile.Avatar = p.Avatar;
     profile.Website = p.Website;
     db.SubmitChanges();
   }
}

楊過兄也同樣給出了該方案的反射方法,實現屬性值的自動拷貝。

但我個人認為這是一種避實就虛的方案,沒有使用LINQ to SQL提供的用於更新操作的API,而采取了 一種迂回的策略。這其實是一種妥協,難道因為Attach方法“不好用”,我們就不用了嗎?呵呵。

方案二:禁用對象跟蹤

對此,lea提出可以通過將DataContext的ObjectTrackingEnabled屬性設置為false,來達到正確更新 的目的。

public Product GetProduct(int id)
{
   NorthwindDataContext db = new NorthwindDataContext();

db.ObjectTrackingEnabled =
false
;

   return db.Products.SingleOrDefault(p => p.ProductID == id);
}

其他的代碼沒有任何變化。

為什麼禁用對象跟蹤之後,就能正常更新了呢?我們還是從源代碼中來尋找答案吧。

public bool ObjectTrackingEnabled 
{
   get
   {
     this.CheckDispose();
     return this.objectTrackingEnabled;
   }
   set
   {
     this.CheckDispose();
     if (this.Services.HasCachedObjects)
     {
       throw System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
     }
     this.objectTrackingEnabled = value;
     if (!this.objectTrackingEnabled)
     {

this.deferredLoadingEnabled = false
;

     }
     this.services.ResetServices();
   }
}

原來設置ObjectTrackingEnabled為false時,會同時將DeferredLoadingEnabled設置為false。這樣, 在執行查詢時,將不會為實體加載任何需延遲查詢的數據,因此Attach時也不會拋出異常(見上篇的分析 )。

在MSDN中我們還得到下面這條有用的信息:

將ObjectTrackingEnable屬性設置為false,可以提高檢索時的性能,因為這樣可以減少要跟蹤的項目 。這真是一個很有誘惑的特性。

但禁用對象跟蹤時,要特別注意兩點:(1)必須在執行查詢前禁用。(2)禁用之後不能再調用 Attach和SubmitChanges方法。否則都將引發異常。

方案三:移除關聯

在前一篇文章中已經介紹一個蹩腳的方法,即在GetProduct方法中手動設置與Product關聯的Category 為null。我們可以把這部分代碼提取出來,放入一個Detach方法中。因為這個Detach是實體的方法,可以 使用分部類:

public partial class Product
{
   public void Detach()
   {
     this._Category = default(EntityRef<Category>);
   }
}

public partial class Category
{
   public void Detach()
   {
     foreach (var product in this.Products)
     {
       product.Detach();
     }
   }
}

但是這種對每個實體都定義Detach的方法過於繁瑣。隨著實體的增多,關系越來越復雜,很容易出現 漏掉的屬性。張逸提出了一個非常優雅的方法,利用反射對該邏輯進行抽象:

private void Detach(TEntity entity)
{
   foreach (FieldInfo fi in entity.GetType().GetFields(BindingFlags.NonPublic |  BindingFlags.Instance))
   {
     if (fi.FieldType.ToString().Contains("EntityRef"))
     {
       var value = fi.GetValue(entity);
       if (value != null)
       {
         fi.SetValue(entity, null);
       }
     }
     if (fi.FieldType.ToString().Contains("EntitySet"))
     {
       var value = fi.GetValue(entity);
       if (value != null)
       {
         MethodInfo mi = value.GetType().GetMethod("Clear");
         if (mi != null)
         {
           mi.Invoke(value, null);
         }
         fi.SetValue(entity, value);
       }
     }
   }
}

也有人認為在Detach時應該把PropertyChanging和PropertyChanged事件設置為null,但總體的思路是 一樣的。

方案四:使用委托

這是ZC29同學在我上一篇文章的評論裡給出的方法,我個人認為非常值得借鑒。

public void UpdateProductWithDelegate(Expression<Func<Product,  bool>> predicate, Action<Product> action)
{
   NorthwindDataContext db = new NorthwindDataContext();
   var product = db.Products.SingleOrDefault(predicate);
   action(product);
   db.SubmitChanges();
}
// Client code
ProductRepository repository = new ProductRepository();
repository.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
   {
     p.ProductName = "Changed";
   });

使用Lambda表達式將GetProduct的邏輯植入UpdateProduct中,並且使用委托將更新邏輯也延緩執行, 這樣巧妙地將查找和更新放進了一個DataContext裡,從而繞開了Attach。但是這種方法API有些過於復雜 ,對客戶端編程人員的水平要求過高。而且在Update 裡還要執行一遍Get的邏輯,盡管性能上的損失微乎 其微,但看上去總多多少少給人一種不夠DRY的感覺。

方案五:使用UPDATE語句

在Ezsocio的源代碼中,我發現了RepositoryBase.UpdateEntity方法。在方法內部進行SQL語句的拼接 ,並且將只更新發生更改的列。由於此處已經不再使用ITable,並且需要完整的框架支持,因此不再進行 過多的評述。詳情請參考Ezsocio的源代碼。

總結

本文列舉了近幾天我在互聯網上找到的幾種解決方案,它們各有利弊,孰優孰劣,見仁見智。在下篇 中,我將對這幾種方法進行性能上的比較,從而找出最優方案。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved