使用者建立 LINQ 查詢之後,它會轉換成命令樹狀結構。 命令樹是與 Entity Framework 相容的查詢表示法。 接著對數據源執行指令樹。 在查詢運行時間,會評估所有查詢表達式(也就是查詢的所有元件),包括用於結果具體化的表達式。
執行查詢表達式的時間點可能會有所不同。 LINQ 查詢總是在查詢變數被逐一查看時才會執行,而不是在建立查詢變數的時候執行。 這稱為 延後執行。 您也可以強制查詢立即執行,這對於快取查詢結果很有用。 本主題稍後會說明這一點。
執行 LINQ to Entities 查詢時,查詢中的某些表示式可能會在伺服器上執行,而某些部分可能會在用戶端本機上執行。 在伺服器上執行查詢之前,就會進行表達式的客戶端評估。 如果在用戶端上評估表達式,該評估的結果會取代查詢中的表達式,然後在伺服器上執行查詢。 由於查詢是在數據源上執行,因此數據源配置會覆寫用戶端中指定的行為。 例如,Null 值處理和數值精確度取決於伺服器設定。 伺服器上查詢執行期間擲回的任何例外狀況都會直接傳遞至用戶端。
小提示
如需數據表格式查詢運算符的便利摘要,可讓您快速識別運算符的執行行為,請參閱依執行方式分類標準查詢運算元(C#)。
延遲查詢執行
在傳回值序列的查詢中,查詢變數本身絕不會保存查詢結果,而且只會儲存查詢命令。 查詢的執行會延後,直到在foreach或For Each迴圈中逐一查看查詢變數為止。 這稱為 延後執行;也就是說,查詢執行會在建構查詢之後一段時間發生。 這表示您可以盡可能頻繁地執行查詢。 例如,當您有其他應用程式正在更新的資料庫時,這會很有用。 在您的應用程式中,您可以建立查詢來擷取最新資訊,並重複執行查詢,每次傳回更新的資訊。
延後執行可合併多個查詢或擴充查詢。 擴充查詢時,會修改它以包含新的作業,而最終執行會反映變更。 在下列範例中,第一個查詢會傳回所有產品。 第二個查詢會使用 Where 來擴充第一個 ,以傳回大小為 “L” 的所有產品:
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<Product> productsQuery =
from p in context.Products
select p;
IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");
Console.WriteLine("Products of size 'L':");
foreach (var product in largeProducts)
{
Console.WriteLine(product.Name);
}
}
Using context As New AdventureWorksEntities()
Dim productsQuery = _
From p In context.Products _
Select p
Dim largeProducts = _
productsQuery.Where(Function(p) p.Size = "L")
Console.WriteLine("Products of size 'L':")
For Each product In largeProducts
Console.WriteLine(product.Name)
Next
End Using
執行查詢之後,所有後續查詢都會使用記憶體內部 LINQ 運算符。 使用 foreach 或 For Each 語句或呼叫其中一個 LINQ 轉換運算子來遍歷查詢變數,將會導致立即執行。 這些轉換運算子包括: ToList、 ToArray、 ToLookup和 ToDictionary。
立即執行查詢
相較於那些需要延遲執行以產生值序列的查詢,返回單一值的查詢會立即執行。 單一查詢的一些範例包括 Average、 Count、 First和 Max。 這些會立即執行,因為查詢必須產生序列來計算單一結果。 您也可以強制立即執行。 當您想要快取查詢的結果時,這會很有用。 若要強制立即執行不會產生單一值的查詢,您可以在查詢或查詢變數上呼叫 ToList 方法、 ToDictionary 方法或 ToArray 方法。 下列範例會使用 ToArray 方法,立即將序列評估為陣列。
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
ObjectSet<Product> products = context.Products;
Product[] prodArray = (
from product in products
orderby product.ListPrice descending
select product).ToArray();
Console.WriteLine("Every price from highest to lowest:");
foreach (Product product in prodArray)
{
Console.WriteLine(product.ListPrice);
}
}
Using context As New AdventureWorksEntities
Dim products As ObjectSet(Of Product) = context.Products
Dim prodArray As Product() = ( _
From product In products _
Order By product.ListPrice Descending _
Select product).ToArray()
Console.WriteLine("The list price from highest to lowest:")
For Each prod As Product In prodArray
Console.WriteLine(prod.ListPrice)
Next
End Using
您也可以在查詢表達式後面立即放置 foreach 或 For Each 迴圈,但藉由呼叫 ToList 或 ToArray 快取單一集合物件中的所有數據,來強制執行。
商店執行
一般而言,LINQ to Entities 中的表達式會在伺服器上進行評估,並且不應期待表達式的行為遵循 Common Language Runtime (CLR)語意,而是數據來源的規則。 不過,有例外狀況,例如在用戶端上執行表達式時。 這可能會導致非預期的結果,例如當伺服器和客戶端位於不同的時區時。
查詢中的某些表達式可能會在用戶端上執行。 一般而言,大部分的查詢執行應該發生在伺服器上。 除了針對對應至數據源的查詢元素執行的方法之外,查詢中通常會有表達式可在本機執行。 查詢表達式的本機執行會產生可用於查詢執行或結果建構的值。
某些作業一律會在客戶端上執行,例如值繫結、子表達式、閉包中的子查詢,以及將物件具體化到查詢結果中。 這的凈效果是,這些元素(例如參數值)在執行期間無法更新。 匿名型別可以在數據源上內嵌建構,但不應該假設這麼做。 內嵌群組也可以建構在數據源中,但這不應該在每個實例中假設。 一般而言,最好不要對伺服器上建構的內容進行任何假設。
本節說明在用戶端本機執行程序代碼的案例。 如需在本機執行哪些表達式類型的詳細資訊,請參閱 LINQ to Entities 查詢中的表達式。
常值和參數
在用戶端上評估局部變數,例如 orderID 下列範例中的變數。
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in context.SalesOrderHeaders
where s.SalesOrderID == orderID
select s;
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In context.SalesOrderHeaders _
Where s.SalesOrderID = orderID _
Select s
方法參數也會在用戶端上進行評估。
orderID傳遞至 MethodParameterExample 方法的參數如下,就是一個範例。
public static void MethodParameterExample(int orderID)
{
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<SalesOrderHeader> salesInfo =
from s in context.SalesOrderHeaders
where s.SalesOrderID == orderID
select s;
foreach (SalesOrderHeader sale in salesInfo)
{
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
}
}
}
Function MethodParameterExample(ByVal orderID As Integer)
Using context As New AdventureWorksEntities()
Dim salesInfo = _
From s In context.SalesOrderHeaders _
Where s.SalesOrderID = orderID _
Select s
Console.WriteLine("Sales order info:")
For Each sale As SalesOrderHeader In salesInfo
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue)
Next
End Using
End Function
在客戶端上進行字面值的型別轉換
從 null 轉換成 CLR 類型是在用戶端上執行:
IQueryable<Contact> query =
from c in context.Contacts
where c.EmailAddress == (string)null
select c;
Dim query = _
From c In context.Contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
轉換至某類型,例如可為 Null 的Decimal,會在用戶端上執行:
var weight = (decimal?)23.77;
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Dim weight = CType(23.77, Decimal?)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
文字常數建構函式
新的 CLR 類型可以對應至概念模型類型,會在用戶端上執行:
var weight = new decimal(23.77);
IQueryable<Product> query =
from product in context.Products
where product.Weight == weight
select product;
Dim weight = New Decimal(23.77)
Dim query = _
From product In context.Products _
Where product.Weight = weight _
Select product
新的陣列也會在用戶端上執行。
儲存例外狀況
查詢執行期間遇到的任何存放區錯誤都會傳遞至用戶端,且不會對應或處理。
商店設定
當查詢在存放區上執行時,存放區組態會覆寫所有客戶端行為,並針對所有作業和表達式表示存放區語意。 這可能會導致 CLR 與儲存執行之間的行為差異,例如 null 比較、GUID 排序、涉及非精確數據類型(如浮點類型或 DateTime)之操作的精確性和準確性,以及字串操作。 檢查查詢結果時,請務必記住這一點。
例如,下列是CLR與 SQL Server 之間行為的一些差異:
SQL Server 會以不同於 CLR 的方式訂購 GUID。
處理 SQL Server 上的十進位類型時,結果精確度也有差異。 這是因為 SQL Server 十進位類型的固定精確度需求。 例如,用戶端記憶體中,數值 0.0、0.0 和 1.0 的平均值 Decimal 是 0.3333333333333333333333333333,但在存放區中則是 0.333333(根據 SQL Server 小數型別的預設精確度)。
某些字串比較作業在 SQL Server 中也會與 CLR 中的處理方式不同。 字串比較行為取決於伺服器上的定序設定。
在 LINQ to Entities 查詢中包含函式或方法呼叫時,會對應至 Entity Framework 中的標準函式,然後轉譯成 Transact-SQL 並在 SQL Server 資料庫上執行。 在某些情況下,這些對應函式所呈現的行為可能與基類連結庫中的實作不同。 例如,呼叫 `Contains`、`StartsWith` 和 `EndsWith` 方法並以空字串作為參數,會在 CLR 中執行時傳回 `
true`,但在 SQL Server 中執行時會傳回 `false`。 EndsWith方法也可以傳回不同的結果,因為如果兩個字串在尾端空格符不同時,SQL Server 會將兩個字串視為相等,而 CLR 會將兩個字串視為不相等。 下列範例將說明這一點:
using (AdventureWorksEntities context = new AdventureWorksEntities())
{
IQueryable<string> query = from p in context.Products
where p.Name == "Reflector"
select p.Name;
IEnumerable<bool> q = query.Select(c => c.EndsWith("Reflector "));
Console.WriteLine("LINQ to Entities returns: " + q.First());
Console.WriteLine("CLR returns: " + "Reflector".EndsWith("Reflector "));
}
Using context As New AdventureWorksEntities()
Dim query = _
From p In context.Products _
Where p.Name = "Reflector" _
Select p.Name
Dim q = _
query.Select(Function(c) c.EndsWith("Reflector "))
Console.WriteLine("LINQ to Entities returns: " & q.First())
Console.WriteLine("CLR returns: " & "Reflector".EndsWith("Reflector "))
End Using