《深入了解 Linq to SQL》之對象的標識 —— 麥叔叔嘔心嚦血之作


序言

很多朋友都向我提過,希望我寫一下關於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的設計里,你會常常可以看到,很多地方都遵循簡潔模型這個原則,這個原則,在后面的內容里會給大家介紹。同時你會發現,如果對於沒有定義好主鍵的實體,是不能進行添加、更新、刪除的操作,因為這些操作都依賴於對象標識,而對象標識又需要實體的主鍵。又因為主鍵是用來標識對象的,所以主鍵是不能何改的。

備注

ALinq 提供了 Insert、Update、Delete 三個方法,這三個方法,只是簡單地將 Linq 的 Lamdba 表達式轉成 SQL 語句然后再執行。因此,它不依賴於對象標識。所以它可以:

1、沒有主鍵也能更新

2、可以更新主鍵的值

總之,SQL 能操作的,它都可以。關於這幾個方法的使用,ALinq 的文檔中都有介紹,看文檔即可。

它不可以:

1、該方法不能被擴展方法置換。

例如:如果你使用的是 InsertOnSubmit(customer), 你可以通過重寫 InsertCustomer 方法,替換原有的方法。當然你需要將一個表拆分成兩個時,非常有用。如果你用的是 Insert 方法,則無效。

2、ALinq Inject 注入框架無法對其注入。

 Insert方法的應用實例 

 http://cn.alinq.org/blog/InsertMethodSample

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM