查詢執行
使用者建立 LINQ 查詢之後,查詢就會轉換成命令樹。命令樹一種可與 Entity Framework 比較的查詢表示方式。接下來命令樹會針對資料來源執行。查詢執行期間會評估所有查詢運算式 (也就是查詢的所有元件),包括結果具體化中使用的運算式。
查詢運算式執行的時間點可能會變動。LINQ 查詢一定是在重複處理查詢變數時執行,而不是在查詢變數建立後執行。這就是所謂的*「延後執行」*。您也可以強制查詢立即執行,這對於快取查詢結果很有用處。本主題稍後將提供這方面的說明。
執行 LINQ to Entities 查詢時,查詢中的某些運算式可能會在伺服器上執行,而某些部分則可能在本機用戶端上執行。在伺服器上執行查詢以前,會先在用戶端評估運算式。如果運算式是在用戶端上評估,該評估的結果會代替查詢中的運算式,然後會在伺服器上執行查詢。由於查詢會在資料存來源執行,所以資料來源組態會覆寫用戶端中指定的行為。例如,null 值的處理和數值的有效位數都會因伺服器設定而異。在伺服器上執行查詢的期間擲回的任何例外狀況,都會直接傳遞給用戶端。
延後執行
如果查詢是設計成要傳回值的序列,則查詢變數本身只會儲存查詢命令。如果查詢沒有包含將導致立即執行的方法,查詢的實際執行將延後,直到您在 foreach 或 For Each 迴圈中反覆查看查詢變數為止。每當您在 foreach 或 For Each 迴圈中重複處理查詢變數時,就會針對伺服器執行此查詢。這就是所謂的「延後執行」。「延後執行」可以讓多個查詢結合成要擴充的查詢。在這種情況下,查詢將修改成包含新的運算,而最終的執行將會反映這些變更。查詢變數本身並不會保存查詢結果。這意味著您隨時都可以執行查詢。例如,您可以擁有會由個別應用程式持續更新的資料庫。您可以在應用程式中建立一個擷取最新資料的查詢,然後依特定時間間隔反覆執行,每次擷取不同的結果。
LINQ to Entities 查詢會轉換成 Entity Framework 中的命令樹,然後在重複處理結果時針對資料來源執行。在這時,轉換失敗將會導致例外狀況擲回到用戶端。
立即執行
產生一連串值的查詢會延後執行,但是傳回單一值的查詢則是立即執行。單一查詢的例子包括 Average、Count、First 和 Max。這些查詢會立即執行是因為查詢必須產生用來計算單一結果的序列。您也可以用強制方式立即執行。如果您要快取查詢結果,這種方式就很有用處。如果要強制不是產生單一值的查詢立即執行,您可以在查詢或查詢變數上呼叫 ToList 方法、ToDictionary 方法,或 ToArray 方法。以下範例使用 ToArray 方法將序列立即評估為陣列。
Using AWEntities As New AdventureWorksEntities
Dim products As ObjectQuery(Of Product) = AWEntities.Product
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
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
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);
}
}
您也可以將 foreach 或 For Each 迴圈緊接在查詢運算式之後,用這種方式來強制執行,但是呼叫 ToList 或 ToArray 必須快取單一集合物件中的所有資料。
存放區執行
一般而言,LINQ to Entities 中的運算式是在伺服器上評估,而運算式的行為也不會依照 Common Language Runtime (CLR) 語意,而是依照資料來源的語意。不過這種情況也有例外,例如運算式在用戶端上執行時就不是這種情況。舉例來講,如果伺服器與用戶端是在不同的時區,這種情況可能會導致無法預期的結果。
查詢中的某些運算式可能會在用戶端上執行。一般而言,大部分查詢執行應該都是發生在伺服器上。除了針對對應到資料來源的查詢項目執行的方法之外,查詢中也常常有可以在本機執行的運算式。查詢運算式的本機執行會產生可以使用在查詢執行或結果建構中的值。
某些運算永遠是在用戶端執行,例如值的繫結、子運算式、來自結束的子查詢,以及物件具體化成為查詢結果。這種情況的最終效果就是這些項目 (例如參數值) 無法在執行期間更新。匿名型別可以用內嵌方式建構在資料來源上,但不應視為必須如此。內嵌群組也可以建構在資料來源中,但這並不適用於每個執行個體。一般而言,最好不要假設何者是建構在伺服器上。
本節說明程式碼在用戶端上以本機方式執行的案例。如需在本機執行之運算式類型的相關資訊,請參閱 LINQ to Entities 查詢中的運算式。
常值和參數
區域變數 (例如以下範例中的 orderID
變數) 會在用戶端上評估。
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim orderID As Integer = 51987
Dim salesInfo = _
From s In sales _
Where s.SalesOrderID = orderID _
Select s
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
int orderID = 51987;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
方法參數也是在用戶端上評估。以下傳入 MethodParameterExample
方法的 orderID
參數就是這種範例。
Function MethodParameterExample(ByVal orderID As Integer)
Using AWEntities As New AdventureWorksEntities()
Dim sales As ObjectQuery(Of SalesOrderHeader) = AWEntities.SalesOrderHeader
Dim salesInfo = _
From s In sales _
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
public static void MethodParameterExample(int orderID)
{
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<SalesOrderHeader> sales = AWEntities.SalesOrderHeader;
IQueryable<SalesOrderHeader> salesInfo =
from s in sales
where s.SalesOrderID == orderID
select s;
foreach (SalesOrderHeader sale in salesInfo)
{
Console.WriteLine("OrderID: {0}, Total due: {1}", sale.SalesOrderID, sale.TotalDue);
}
}
}
在用戶端上將常值轉型
從 null 轉型為 CLR 型別是在用戶端上執行:
Dim contacts As ObjectQuery(Of Contact) = AWEntities.Contact
Dim query = _
From c In contacts _
Where c.EmailAddress = CType(Nothing, String) _
Select c
ObjectQuery<Contact> contacts = AWEntities.Contact;
IQueryable<Contact> query =
from c in contacts
where c.EmailAddress == (string)null
select c;
轉型為某種型別 (例如何為 null 的 Decimal) 是在用戶端上執行:
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = CType(23.77, Decimal?) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == (decimal?)23.77
select product;
常值的建構函式
可以對應到 EDM 型別的新 CLR 型別是在用戶端上執行:
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From product In products _
Where product.Weight = New Decimal(23.77) _
Select product
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> query =
from product in products
where product.Weight == new decimal(23.77)
select product;
新陣列也是在用戶端上執行。
存放區例外狀況
在查詢執行期間遭遇的任何存放區錯誤都會傳遞到用戶端,不予對應或處理。
存放區組態
查詢在存放區上執行時,存放區組態會取代所有用戶端行為,並且對所有運算和運算式表示存放區語意。這可能會在 null 比較、GUID 順序、涉及非精確資料型別 (例如浮點數型別或 DateTime) 之運算的有效位數和精確度,以及字串運算等範圍造成 CLR 和存放區執行之間的行為差異。檢查查詢結果時請務必留意這些情況。
例如,以下就是在 CLR 和 SQL Server 之間的一些行為差異:
SQL Server 排序 GUID 的方式與 CLR 不同。
在 SQL Server 上處理 Decimal 型別時也會有結果有效位數的差異。這是因為 SQL Server 的 decimal 型別要求固定有效位數所造成。舉例來講,Decimal 值 0.0、0.0 和 1.0 的平均在用戶端的記憶體中為 0.3333333333333333333333333333,但是在存放區則為 0.333333 (依據 SQL Server 之 decimal 型別的預設有效位數)。
某些字串比較運算在 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 AWEntities As New AdventureWorksEntities()
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim query = _
From p In 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
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<string> query = from p in 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 "));
}