關於第四章中,SQL指令何時執行小方框的補充說明

摘要:關於第四章中,SQL指令何時執行小方框的補充說明

關於第四章中,SQL指令何時執行小方框的補充說明
 
 
 
文/黃忠成
 
 
   在【極意之道- .NET Framework 3.5 資料庫開發聖典 ASP.NET】一書中,我於第四章,4-36頁處放上SQL指令何時執行的小方框,裡面的程式證明了當對同一LINQ To SQL回傳值下達多次LINQ  Expression運算式時,只有第一次會被轉成SQL指令後送往資料庫執行。
 但這點與前面提及的Distinct、Count、Skip、Take的函式之用法形成了概念上的衝突,例如下面的Distinct函式用法。
var result = (from s1 in context.Order_Details
             where s1.ProductID == 42 select s1.OrderID).Distinct().Count();
依據4-B01的程式結果看來,Distinct函式應該會先被執行,而Count函式會進入LINQ To Objects的範圍,但就事實而言,Distinct及Count函式都會被組合為單一SQL指令。
SELECT COUNT(*) AS [value]
FROM (
    SELECT DISTINCT [t0].[OrderID]
    FROM [dbo].[Order Details] AS [t0]
    WHERE [t0].[ProductID] = @p0
    ) AS [t1]',N'@p0 int',@p0=42
那究竟是什麼,讓這兩個程式產生不同的結果呢?
  事實上,這是因為4-B01的程式使用IEnumerable為函式間傳遞之型別所致,對於編譯器來說,並不會以物件的原始型別來推算呼叫 的Extension Method,而是以最終型別,意思是,當你將LINQ To SQL的回傳值轉型為IEnumerable<T>後,那麼呼叫Where時,編譯器將決議成呼叫 System.Linq.Enumerable中的Where函式,而非原本的IQueryable<T>中的Where函式。
 所以,因為明白轉型成IEnumerable後,使得編譯器將後續的LINQ Expression全轉成了LINQ To Objects的呼叫了。
 要正確完成累進式查詢的方式,是將程式4-B01中的IEnumerable<Customer>全換成IQueryable<Customer>。
static void NestedQuery()
{
      NorthwindDataContext context = new NorthwindDataContext ();
      context.Log = Console.Out;
      var fquery = GetFirstQuery(context);
      var ret = DoSecondQuery(context, fquery);
      foreach (var item in ret)
         Console.WriteLine(item.CustomerID);
}
 
static IQueryable<Customer> GetFirstQuery(NorthwindDataContext context)
{
      IQueryable<Customer> result = from s1 in context.Customers select s1;
      return result;
}
 
static IQueryable<Customer> DoSecondQuery(NorthwindDataContext context,
                                          IQueryable<Customer> firstQuery)
{
       var result = from s1 in firstQuery
                    where s1.CustomerID == "VINET"
                      select s1;;
        return result;
}
 這個例子告訴我們一件很重要的事,如果你打算將LINQ的回傳值傳遞到另一個函式,或是回傳,要謹慎選擇型別,因為不同的型別,會影響後續使用LINQ時,編譯器如何決定呼叫的Extension Method,最後造成難以查覺的錯誤。
 如果在撰寫LINQ/LINQ To SQL的通用函式時,不得已需要使用IEnumerable<T>(因為這是LINQ回傳值的通用基礎介面),也要加上依據實體介面來轉型的動作,如下例:
static void NestedQuery()
{
     DataClasses1DataContext context = new DataClasses1DataContext();
     context.Log = Console.Out;
     var fquery = GetFirstQuery(context);
     var ret = DoSecondQuery(fquery);
     foreach (var item in ret)
        Console.WriteLine(item.CustomerID);
}
 
static IEnumerable<Customers> GetFirstQuery(DataClasses1DataContext context)
{
      IEnumerable<Customers> result = from s1 in context.Customers select s1;
      return result;
}
 
 static IEnumerable<Customers> DoSecondQuery(IEnumerable<Customers> firstQuery)
{      
       IEnumerable<Customers> result = null;
       if (firstQuery is IQueryable<Customers>)
           result = from s1 in (IQueryable<Customers>)firstQuery
                    where s1.CustomerID == "VINET"
                    select s1;
        else
           result = from s1 in firstQuery
                    where s1.CustomerID == "VINET"
                    select s1;;
        return result;
}