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

我們常用的 **LINQ to SQL** 就是一種簡單、易用的 [ORM](http://en.wikipedia.org/wiki/Object-relational_mapping) 技術。我們也都知道若是拿**採用 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](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.aspx) 類別的定義，並指定給 [DataContext](http://msdn.microsoft.com/zh-tw/library/system.data.linq.datacontext.aspx) 物件的 [LoadOptions](http://msdn.microsoft.com/zh-tw/library/system.data.linq.datacontext.loadoptions.aspx) 屬性，如下程式範例：

```csharp
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](http://msdn.microsoft.com/zh-tw/library/system.data.linq.datacontext.aspx) 物件的 [LoadOptions](http://msdn.microsoft.com/zh-tw/library/system.data.linq.datacontext.loadoptions.aspx) 屬性，當查詢到 Customer 資料表的資料時，就會自動連帶將 Order 資料表中的關連資料也一併取回，若你透過 SQL Server Profiler 查詢 LINQ to SQL 到底傳什麼 SQL Command 到 DB 的話，你就會發現他會以 JOIN 的方式將兩個表格的資料一次取回。

你可以看到我們使用了 [LoadWith](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.loadwith.aspx) 方法可以設定立即載入相關的資料，另外還有一個 [AssociateWith](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.associatewith.aspx) 方法可以讓你在進行查詢時篩選(Filter)相關連的資料，讓你取得特定條件下的關連資料，如下範例：

```csharp
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](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.loadwith.aspx) 方法與 [AssociateWith](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.associatewith.aspx) 方法的差別，而且也可以一起搭配使用，而這就是所謂的**查詢計畫**(**Query Plan**)。如果你用的是其他的 [ORM](http://en.wikipedia.org/wiki/Object-relational_mapping) 技術 ( 例如 [Entity Framework](http://zh.wikipedia.org/wiki/ADO.NET_Entity_Framework), [OpenAccess ORM](http://www.telerik.com/products/orm.aspx), ... )，通常也都一定會有各自的 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 程式真是好寫太多了」。

**相關連結**

-   [DataLoadOptions 類別](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.aspx)
-   [DataLoadOptions.LoadWith 方法](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.loadwith.aspx)
-   [DataLoadOptions.AssociateWith 方法](http://msdn.microsoft.com/zh-tw/library/system.data.linq.dataloadoptions.associatewith.aspx)
-   [HOW TO：在 DataContext 層級篩選 (LINQ to SQL)](http://msdn.microsoft.com/zh-tw/library/bb386915.aspx)
-   [LINQ to SQL 的查詢概念](http://msdn.microsoft.com/zh-tw/library/bb387006.aspx)
-   [查詢資料庫 (LINQ to SQL)](http://msdn.microsoft.com/zh-tw/library/bb882680.aspx)
-   [自訂作業：概觀 (LINQ to SQL)](http://msdn.microsoft.com/zh-tw/library/bb882664.aspx)
-   [延後和立即載入的比較 (LINQ to SQL)](http://msdn.microsoft.com/zh-tw/library/bb399393.aspx)
-   [LINQ to SQL](http://msdn.microsoft.com/zh-tw/library/bb386976.aspx) ( 完整的 LINQ to SQL 文件 )
-   [Dissecting LINQ to SQL](http://www.codeproject.com/KB/linq/LINQ_to_SQL.aspx)
