The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

採用 ORM 技術需瞭解何謂查詢計畫 ( 以 LINQ to SQL 為例 )

我們常用的 LINQ to SQL 就是一種簡單、易用的 ORM 技術。我們也都知道若是拿採用 ORM 技術直接存取 Database 這兩件事來比較,ORM 因為多卡了一層一定會比較慢一些些,但是採用 ORM 技術有很多機會可以調校(Tuning),例如資料快取(Caching)、定義查詢計畫(Query Plan)等。

基本上當您透過 LINQ to SQL 查詢資料時,實際上只取得你所要求的第一層資料而已,對於相關連的資料並不會自動取得。例如:你有兩張表,一個是 Customer 顧客資料表,另一個是 Order 訂單資料表,兩者是一對多的關係 ,當你透過 LINQ to SQL 取得 Customer 顧客資料表時,事實上預設不會同時也取得 Order 訂單資料表,而是當你在取得 Customer 資料時若有需要才會在去資料庫取得 Order 訂單資料,我們稱這種行為叫做「延後載入」、「延遲載入」或英文的「deferred loading」。

採用這種「延遲載入」的好處就是你不用一次載入完所有相關連的資料,而是當有需要取得關連資料時才去資料庫抓取,但是缺點就是可能你的程式會不斷的到資��庫取得關連的資料,導致一些效能上的問題。

如果你確定這次查詢所取回的 Customer 資料一定會需要載入 Order 訂單資料,那麼你就需要訂定所謂的「查詢計畫(Query Plan)」。

在 LINQ to SQL 中,定義查詢計畫是透過 DataLoadOptions 類別的定義,並指定給 DataContext 物件的 LoadOptions 屬性,如下程式範例:

NorthwndDataContext db = new NorthwndDataContext();
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Customer>(c => c.Orders);
db.LoadOptions = dlo;

var londonCustomers =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

foreach (var custObj in londonCustomers)
{
    Console.WriteLine(custObj.Orders.ID);
}

若你在透過 LINQ to SQL 查詢資料前先定義 DataContext 物件的 LoadOptions 屬性,當查詢到 Customer 資料表的資料時,就會自動連帶將 Order 資料表中的關連資料也一併取回,若你透過 SQL Server Profiler 查詢 LINQ to SQL 到底傳什麼 SQL Command 到 DB 的話,你就會發現他會以 JOIN 的方式將兩個表格的資料一次取回。

你可以看到我們使用了 LoadWith 方法可以設定立即載入相關的資料,另外還有一個 AssociateWith 方法可以讓你在進行查詢時篩選(Filter)相關連的資料,讓你取得特定條件下的關連資料,如下範例:

NorthwndDataContext db = new NorthwndDataContext();

DataLoadOptions ds = new DataLoadOptions();
// 預先載入 Orders 的資料
ds.LoadWith<Customer>(c => c.Orders);

// 取得 Customer 關連的 Orders 資料時,過濾掉 ShippedDate 等於今天的紀錄
ds.AssociateWith<Customer>
    (c => c.Orders.Where(p => p.ShippedDate != DateTime.Today));

db.LoadOptions = ds;

var custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

從上述範例,你應該也可以看出 LoadWith 方法與 AssociateWith 方法的差別,而且也可以一起搭配使用,而這就是所謂的查詢計畫(Query Plan)。如果你用的是其他的 ORM 技術 ( 例如 Entity Framework, OpenAccess ORM, ... ),通常也都一定會有各自的 Query Plan 可用。

後記

當我用 LINQ to SQL 之後,就很少再用到 LINQ 中的 join 功能了,因為 LINQ to SQL 的 點標記法(dot notation) 好用到一整個爆炸,當你要資料的時候,只要一個「點」就可以將資料取出了,再搭配 Query Plan 的定義,就可以完美的搭配 LINQ to SQL 進行 AP 開發,同時兼顧便利性與效能。

有一次我看到同事辛苦的寫著 join 語法試圖要結合兩張表的資料,我就教他如何透過 LINQ to SQL 的 點標記法(dot notation) 進行資料取得,當我親自展示何謂 點標記法(dot notation) 之後,他也是茅塞頓開,感覺向打通任督二脈一般,並說:「LINQ to SQL 程式真是好寫太多了」。

相關連結