|
在前一篇文章中,我提出了在使用LINQ to SQL進(jìn)行更新操作時(shí)可能會(huì)遇到的幾種問題。其實(shí)這并不是我一個(gè)人遇到的問題,當(dāng)我在互聯(lián)網(wǎng)上尋找答案時(shí),我發(fā)現(xiàn)很多人都對(duì)這個(gè)話題發(fā)表過類似文章。但另我無法滿足的是,他們盡管提出了問題,卻沒有進(jìn)行詳細(xì)的剖析,只給出了解決方案(如添加RowVersion列、去除關(guān)聯(lián)等),但卻沒有說明為什么必須這么做。這也是我寫上篇的初衷,希望通過對(duì)LINQ to SQL源代碼的分析,來一步一步找出解決問題的辦法。本文將對(duì)這些方法一一進(jìn)行討論。
方案一:重新賦值
在TerryLee、Anytao和Ding Xue等人的開源框架Ezsocio中,有些地方采取了重新賦值的方法。在Update方法內(nèi)部,根據(jù)主鍵獲取數(shù)據(jù)庫(kù)中的實(shí)體,然后與參數(shù)中的實(shí)體對(duì)其屬性一一賦值。
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(); }}
楊過兄也同樣給出了該方案的反射方法,實(shí)現(xiàn)屬性值的自動(dòng)拷貝。
但我個(gè)人認(rèn)為這是一種避實(shí)就虛的方案,沒有使用LINQ to SQL提供的用于更新操作的API,而采取了一種迂回的策略。這其實(shí)是一種妥協(xié),難道因?yàn)锳ttach方法“不好用”,我們就不用了嗎?呵呵。
方案二:禁用對(duì)象跟蹤
對(duì)此,lea提出可以通過將DataContext的ObjectTrackingEnabled屬性設(shè)置為false,來達(dá)到正確更新的目的。
public Product GetProduct(int id){ NorthwindDataContext db = new NorthwindDataContext(); db.ObjectTrackingEnabled = false; return db.Products.SingleOrDefault(p => p.ProductID == id);}
其他的代碼沒有任何變化。
為什么禁用對(duì)象跟蹤之后,就能正常更新了呢?我們還是從源代碼中來尋找答案吧。
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(); }}
原來設(shè)置ObjectTrackingEnabled為false時(shí),會(huì)同時(shí)將DeferredLoadingEnabled設(shè)置為false。這樣,在執(zhí)行查詢時(shí),將不會(huì)為實(shí)體加載任何需延遲查詢的數(shù)據(jù),因此Attach時(shí)也不會(huì)拋出異常(見上篇的分析)。
在MSDN中我們還得到下面這條有用的信息:將ObjectTrackingEnable屬性設(shè)置為false,可以提高檢索時(shí)的性能,因?yàn)檫@樣可以減少要跟蹤的項(xiàng)目。這真是一個(gè)很有誘惑的特性。
但禁用對(duì)象跟蹤時(shí),要特別注意兩點(diǎn):(1)必須在執(zhí)行查詢前禁用。(2)禁用之后不能再調(diào)用Attach和SubmitChanges方法。否則都將引發(fā)異常。
方案三:移除關(guān)聯(lián)
在前一篇文章中已經(jīng)介紹一個(gè)蹩腳的方法,即在GetProduct方法中手動(dòng)設(shè)置與Product關(guān)聯(lián)的Category為null。我們可以把這部分代碼提取出來,放入一個(gè)Detach方法中。因?yàn)檫@個(gè)Detach是實(shí)體的方法,可以使用分部類:
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(); } }}
但是這種對(duì)每個(gè)實(shí)體都定義Detach的方法過于繁瑣。隨著實(shí)體的增多,關(guān)系越來越復(fù)雜,很容易出現(xiàn)漏掉的屬性。張逸提出了一個(gè)非常優(yōu)雅的方法,利用反射對(duì)該邏輯進(jìn)行抽象:
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); } } }}
也有人認(rèn)為在Detach時(shí)應(yīng)該把PropertyChanging和PropertyChanged事件設(shè)置為null,但總體的思路是一樣的。
方案四:使用委托
這是ZC29同學(xué)在我上一篇文章的評(píng)論里給出的方法,我個(gè)人認(rèn)為非常值得借鑒。
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 codeProductRepository repository = new ProductRepository();repository.UpdateProductWithDelegate(p => p.ProductID == 1, p => { p.ProductName = "Changed"; });
使用Lambda表達(dá)式將GetProduct的邏輯植入U(xiǎn)pdateProduct中,并且使用委托將更新邏輯也延緩執(zhí)行,這樣巧妙地將查找和更新放進(jìn)了一個(gè)DataContext里,從而繞開了Attach。但是這種方法API有些過于復(fù)雜,對(duì)客戶端編程人員的水平要求過高。而且在Update里還要執(zhí)行一遍Get的邏輯,盡管性能上的損失微乎其微,但看上去總多多少少給人一種不夠DRY的感覺。
方案五:使用UPDATE語句
在Ezsocio的源代碼中,我發(fā)現(xiàn)了RepositoryBase.UpdateEntity方法。在方法內(nèi)部進(jìn)行SQL語句的拼接,并且將只更新發(fā)生更改的列。由于此處已經(jīng)不再使用ITable,并且需要完整的框架支持,因此不再進(jìn)行過多的評(píng)述。詳情請(qǐng)參考Ezsocio的源代碼。
總結(jié)
本文列舉了近幾天我在互聯(lián)網(wǎng)上找到的幾種解決方案,它們各有利弊,孰優(yōu)孰劣,見仁見智。在下篇中,我將對(duì)這幾種方法進(jìn)行性能上的比較,從而找出最優(yōu)方案。
相關(guān)文章
NET技術(shù):使用LINQ to SQL更新數(shù)據(jù)庫(kù)(中):幾種解決方案,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標(biāo)記有誤,請(qǐng)第一時(shí)間聯(lián)系我們修改或刪除,多謝。