iT邦幫忙

DAY 28
4

分享一些學習心得系列 第 28

LINQ自學筆記-語法應用-聚合資料-Join-2

Join 運算子的第二篇文章,重點是驗證輸出序列的項目順序,以及怎麼做多重 Join。
自學筆記這系列是我自己學習的一些心得分享,歡迎指教。這系列的分享,會以 C# + 我比較熟的 Net 3.5 環境為主。
另外本系列預計至少會切成【打地基】和【語法應用】兩大部分做分享。打地基的部分,講的是 LINQ 的組成元素,這部分幾乎和 LINQ 無關,反而是 C# 2.0、C# 3.0 的一堆語言特性,例如:型別推斷、擴充方法、泛型、委派等等,不過都會把分享的範圍限制在和 LINQ 應用有直接相關功能。
PS. LINQ 自學筆記幾乎所有範例,都可直接複製到 LINQPad 4 上執行(大多是用 Statements 和 Program 模式)。因為它輕巧好用,功能強大,寫範例很方便,請大家自行到以下網址下載最新的 LINQPad:http://www.LINQpad.net/。
在前一篇「LINQ自學筆記-語法應用-聚合資料-Join-1」,我們提過 Join 運算子一個有趣的特性:
「5. 兩個序列 Join 時,會以 outer 中的項目索引順序加入輸出序列中,例如:outer 有 B、A、C,inner 是 A、B、C,最後 Join 結果是三個項目都要輸出,則預設是以 outer 的 B、A、C 順序輸出。」

現在我們準備來驗證這件事情,不過驗證之前,要先說明一個還沒分享過的運算子,也就是 Reverse 運算子:

public static IEnumerable<TSource> Reverse<TSource>( 
    this IEnumerable<TSource> source 
)

Reverse 運算子非常單純,直接在序列上調用,它會把序列中所有項目反轉過來,請注意,是把索引順序反過來,例如:BCAD 會變成 DACB。Reverse 方法在很多集合中都有出現,包含 List、Array、ArrayList,用途都一樣,就是反轉索引值。在下面的範例中,我們因為要驗證輸出序列會保持 outer 中的順序,所以刻意把 Curstomers 這個 List<Customer> 反轉過來。請注意,使用的是 List<T>.Reverse,並非上述的擴充方法,但因為功能完全相同,所以就一併說明。

下面範例的資料來源 Customers、Orders 請參閱前一篇「LINQ自學筆記-語法應用-聚合資料-Join-1」文章的程式,不想用重覆的程式碼佔篇幅,敬請見諒:

void Main()
{
    var Customers = DataProvider.getCustomers();
    var Orders = DataProvider.getOrders();
    
    var query1 =
        from c in Customers
        join o in Orders on c.ID equals o.CustomerID
        select c.Name + " 買 " + o.Description;
    query1.Dump("Standard Customers");
    Customers.Reverse();
    query1.Dump("Reverse Customers");
        
    var query2 =
        from o in Orders
        join c in Customers on o.CustomerID equals c.ID
        select c.Name + " 買 " + o.Description;
    query2.Dump("Orders is outer");
}




上述程式碼,我們定義了兩個 LINQ 查詢,第一個查詢(query1)之 outer 是 Customers,inner 是 Orders,因為 Customers 和 Orders 中的項目之顧客編號都是從小排到大,所以一開始 Dump 查詢結果時,我們可以看到是顧客 Leo 排到最前面,接著我們對 Customers 序列調用 Reverse 方法,然後再 Dump 一次查詢結果,我們可以發現輸出的順序變成顧客 Emy 最先出現,顧客 Leo 排在最後。

接著我們再定義第二個 LINQ 查詢(query2),把 outer 變成 Orders,inner 是 Customers,因為 Orders 序列中的訂單顧客,預設是由 Leo 排到 Emy,所以雖然 Customers 已經執行 Reverse 變成 Emy 排最前面,但是 Join 運算子的特性是以 outer 的順序為優先,所以結果還是從 Leo 排到 Emy。故可證明 Join 運算子預設是以 outer 序列中項目的順序輸出。

本文第二個重點,就是我們一開始描述 Join 運算子時說過:「Join 運算子,可以讓我們把兩個來源序列聚合為一個輸出序列。」但是實務上,Join 三、四個 Table 的狀況挺常見,所以該如何透過 Join 運算子來處理這件事?

答案其實一點都不意外,就是連續調用多個 Join 運算子囉:

public class OrderItem
{
    public int ID { get; set; }
    public int OrderID { get; set; }
    public string Detail { get; set; }
}
void Main()
{
    var Customers = DataProvider.getCustomers();
    var Orders = DataProvider.getOrders();
    List<OrderItem> OrderItems = new List<OrderItem>
    {
        new OrderItem {ID = 1, OrderID = 4, Detail = "AAA"},
        new OrderItem {ID = 2, OrderID = 4, Detail = "BBB"},
        new OrderItem {ID = 3, OrderID = 2, Detail = "CCC"},
        new OrderItem {ID = 3, OrderID = 2, Detail = "DDD"},
        new OrderItem {ID = 3, OrderID = 5, Detail = "EEE"}
    };

    var query = from c in Customers
                join o in Orders on c.ID equals o.CustomerID
                join oi in OrderItems on o.ID equals oi.OrderID
                select new
                {
                    c.Name, o.Description, oi.Detail
                };
    query.Dump();
}


上述程式碼,我們為了要展示如何聯結第二組 Join 運算子,所以我們又定義了一個 OrderItem 類別,其中最重要的就是 OrderID 這個屬性,用來和 Order.ID 做對應。我們在程式碼中建立三個資料來源序列,分別是 Customers、Orders、OrderItems,然後用查詢表達式撰寫一個連續 Join 的語法,並指定輸出顧客名稱(c.Name)、訂單描述(o.Description)和訂單項目明細(oi.Detail),最後我們可以看到輸出結果如上圖所示。

請注意,上述程式除了展示多重 Join 的查詢表達式外,我刻意在建立 OrderItems 的項目資料時,把訂單編號打亂,但最後輸出結果,仍然是顧客 Leo 的訂單先顯示,再次呼應本文第一個重點,也就是輸出序列預設會以 outer 序列項目的順序來呈現。

多重 Join,若利用方法架構查詢撰寫,會繁雜許多,不建議使用,但還是把上述的查詢表達式翻譯為方法架構查詢供大家參考:

Customers
   .Join (
      Orders, 
      c => c.ID, 
      o => o.CustomerID, 
      (c, o) => 
         new  
         {
            c = c, 
            o = o
         }
   )
   .Join (
      OrderItems,
      ret => ret.o.ID, 
      oi => oi.OrderID, 
      (ret, oi) => 
         new  
         {
            Name = ret.c.Name, 
            Description = ret.o.Description, 
            Detail = oi.Detail
         }
   )

簡單說明一下上述程式碼在幹嘛:

  1. 先把 Customers 當 outer,Orders 當 inner,用 Customer.ID 和 OrderCustomerID 做相等聯結,然後輸出包含 Customer 和 Order 項目的序列,假命名為 ret。
  2. 以步驟 1 的輸出結果 ret 當 outer,和 OrderItems 做聯結,並用 ret 中的 Order.ID 和 OrderItem.OrderID 做相等聯結,輸出一個新的包含三個序列指定的欄位。

下一篇文章,我們將繼續說明 Join 運算子如何設定多個欄位來做相等聯結的比較,以及 GroupJoin 運算子。

PS. Join 第二個多載方法的應用,因為和先前 Distinct、Contains 運算子的第二個多載方法應用方式相同,不再贅述。


上一篇
LINQ自學筆記-語法應用-聚合資料-Join-1
下一篇
LINQ自學筆記-語法應用-聚合資料-Join-3、GroupJoin
系列文
分享一些學習心得30

尚未有邦友留言

立即登入留言