很多朋友都向我提過,希望我寫一下關於Linq to SQL 或者 VS 插件方面的文章。盡管市面上有很多 Linq to SQL 的書籍,但是都是介紹怎麼用,缺乏深度。關於 VS 插件方面的書籍也是很顯淺,按書籍做出來的東西,只能是學生級別的東西,根本拿不出手。他們覺得我有這個能力寫好。
從技術能力的角度來說,的確是不存在什麼問題,但是,要把一門技術講精講透,是花很時間的事情。自己付出了很多,如果不能得到讀者的認同,那這個專題寫下去也沒什麼意義了。這個專題不是教你怎麼使用Linq to SQL,而是讓你明白Linq to SQL的原理,對於想寫ORM的朋友,絕對不可錯過。寫完《深入了解 Linq to SQL》這個系列後,下一個系列就是《VS 插件開發了》,所以,大家如果希望我繼續寫下去,請記得點推薦。你們的推薦,就是我寫下去的動力。
概述
關於對象的標識,簡單點說,就是主鍵相同的對象,在數據上下文的緩存中,只有一個。數據上下文,在加載數據,創建對象之後,接著對所創建的每個實體類的對象,都會克隆一份對象副本(淺復制)記住這點,我們在後面要用到,用來保存對象的初始值,當對象的屬性值修改後,副本的屬性值是不改的。注意,只有實體類對象才會創建對象副本,而匿名類對象是不會生成副本,也只實體。我們看下面一段代碼:
例一
var c1 = db.Categories.Single(o => o.CategoryId == 1); var c2 = db.Categories.Single(o => o.CategoryId == 1);
在例一這個例子中,c1、c2 是相等的,我們再來看下面一個例子:
例二
var c1 = db.Categories.Select(o => new { o.CategoryId, o.CategoryName }).Single(o => o.CategoryId == 1); var c2 = db.Categories.Select(o => new { o.CategoryId, o.CategoryName }).Single(o => o.CategoryId == 1);
這個例二這個例子中,c1、c2 是不相等的。
為什麼匿名對象不支持對象標識呢?因為匿名對象,不一定有主鍵。而 Linq to SQL ,是通過實體的主鍵來標識一個實體對象的,當從數據庫加載完數據,會先根據主鍵檢索緩存中是否有已經存在的對象,沒有才會創建對象。這樣會引起一個什麼樣的問題呢?執行下面的代碼。
var c1 = db.Categories.Single(o => o.CategoryId == 1);
假設 CategoryId 為 1 的 CategoryName 為 “Beverages”,打開數據庫,把該值改為“New Name”後,如下圖:
圖一
然後再執行下面的代碼:
var c2 = db.Categories.Single(o => o.CategoryId == 1);
這時候 c2 修改後的 c2.CategoryName 的值是什麼呢?仍然為“Beverages”!因為第二次加載完數據的時候,由於已經存在了主鍵(CategoryID)為“1”的Category,所以在第二次的加載中,不再創建新的對象,以是使用之前所創建的Category對象。那怎麼樣才能使用 c2 的 CategoryName 的值為最新值“New Name”呢?調用數據上下文的 Refresh 方法即可。
db.Refresh(RefreshMode.OverwriteCurrentValues, c2);
我們先來看一個更新的例子:
var c1 = db.Categories.Single(o => o.CategoryId == 1); c1.CategoryName = "xxx"; db.SubmitChanges(); c1.CategoryName = "xxx"; db.SubmitChanges();
通過查看生成的SQL,我們可以發現,盡管 SubmitChagens 方法執行了兩次,但是實際上SQL只執行了一次,因為第二次的 CategoryName 並沒有修改,那麼Linq to SQL如何得知某一個屬性是修改了或者沒有呢?我們看Category實體類的定義,為了節省篇幅,只選取一部份。
[Table(Name="Categories")] public partial class Category : INotifyPropertyChanging, INotifyPropertyChanged { [Column(Storage="_CategoryName", DbType="VarChar(15)", CanBeNull=false, UpdateCheck=UpdateCheck.Never)] public string CategoryName { get { return this._CategoryName; } set { if ((this._CategoryName != value)) { this.OnCategoryNameChanging(value); this.SendPropertyChanging(); this._CategoryName = value; this.SendPropertyChanged("CategoryName"); this.OnCategoryNameChanged(); } } } protected virtual void SendPropertyChanged(String propertyName) { if ((this.PropertyChanged != null)) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
我們可以看得到,實體類Category實現了 INotifyPropertyChanged 的接口,通過這個接口,就可以得知某個屬性是否已經更改了,但是,事實上Category實體類不實現上面的接口,也是沒有問題的,如下所示:
[Table(Name = "Categories")] public partial class Category { [Column(Storage = "_CategoryName", DbType = "VarChar(15)", CanBeNull = false, UpdateCheck = UpdateCheck.Never)] public string CategoryName { get { return this._CategoryName; } set { this._CategoryName = value; } } }
為什麼這樣也可以呢?記得我們剛才說到對象副本嗎?當 Category 沒有實現 INotifyPropertyChanged 這個接口時,Linq to SQL會將Category實體對象和原始的對象副本作比較來得知Category的屬性值是否更改了。
但是下面的定義,會導致 CategoryName 的值即使用修改了,也不會更新到數據庫。因為,當 CategoryName 的值發生變化的,並沒有通知到接口 INotifyPropertyChanged,而當實體類繼承於INotifyPropertyChanged時,Linq to SQL是依賴INotifyPropertyChanged來獲取值被更改的屬性。
[Table(Name = "Categories")] public partial class Category: INotifyPropertyChanged { [Column(Storage = "_CategoryName", DbType = "VarChar(15)", CanBeNull = false, UpdateCheck = UpdateCheck.Never)] public string CategoryName { get { return this._CategoryName; } set { this._CategoryName = value; } } }
由上面我們可以知道,通過對象標識,可以使得Model的定義最為簡潔,在Linq to SQL的設計裡,你會常常可以看到,很多地方都遵循簡潔模型這個原則,這個原則,在後面的內容裡會給大家介紹。同時你會發現,如果對於沒有定義好主鍵的實體,是不能進行添加、更新、刪除的操作,因為這些操作都依賴於對象標識,而對象標識又需要實體的主鍵。又因為主鍵是用來標識對象的,所以主鍵是不能何改的。
本欄目