linq中的連接操作符

linq中的連接操作符主要包括Join()和GroupJoin()兩個。

一、Join()操作符

Join()操作符非常類似於T-SQL中的inner join,它將兩個數據源進行連接,根據兩個數據源中相等的值進行匹配。例如:可以將產品表和產品類別表進行連接,得到產品名稱和與其對應的類型名稱。下面看看Join()方法的定義:

public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector);
public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector, Func<TInner, TKey> innerKeySelector, Func<TOuter, TInner, TResult> resultSelector, IEqualityComparer<TKey> comparer);

從Join()方法的定義中可以看到:Join操作符的方法原型非常復雜,從方法原型可以看到,參數outer和inner是需要連接的兩個輸入集合。其中outer代表的是調用的集合。當Join操作符被調用時,首先列舉inner序列中的所有元素,為序列中每一個類型為U的元素調用委托InnerKeySelector,生成一個類型為K的的對象innerKey作為連接關鍵字。(相當於數據庫中的外鍵),將inner序列中的每一個元素和其對應的連接關鍵字innerKey存儲在一個臨時哈希表中;其次列舉outer序列中的所有元素,為每一個類型為T的元素調用委托outerKeySelector,生成一個類型為K的對象outKey用作連接關鍵字,在第一步生成的臨時哈希表中查找與outKey相等的對應的innerKey對象,如果找到對應的記錄,會將當前outer序列中的類型為T的元素和對應的inner序列中類型為U的元素作為一組參數傳遞給委托resultSelector,resultSelector會根據這兩個參數返回一個類型為V的對象,此類型為V的對象會被添加到Join操作符的輸出結果序列中去。Join操作符返回一個類型為IEnumerable<T>的序列。來看下面的例子:

1、新建Category和Product兩個類,其類定義分別如下:

Category:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    public class Category
    {
        public int Id { get; set; }
        public string CategoryName { get; set; }

        public DateTime CreateTime { get; set; }
    }
}

Product:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    public class Product
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public string Name { get; set; }
        public double Price { get; set; }
        public DateTime CreateTime { get; set; }
    }
}

2、在Main()方法中調用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化數據
            List<Category> listCategory = new List<Category>()
            {
              new Category(){ Id=1,CategoryName="計算機",CreateTime=DateTime.Now.AddYears(-1)},
              new Category(){ Id=2,CategoryName="文學",CreateTime=DateTime.Now.AddYears(-2)},
              new Category(){ Id=3,CategoryName="高校教材",CreateTime=DateTime.Now.AddMonths(-34)},
              new Category(){ Id=4,CategoryName="心理學",CreateTime=DateTime.Now.AddMonths(-34)}
            };
            List<Product> listProduct = new List<Product>()
            {
               new Product(){Id=1,CategoryId=1, Name="C#高級編程第10版", Price=100.67,CreateTime=DateTime.Now},
               new Product(){Id=2,CategoryId=1, Name="Redis開發和運維", Price=69.9,CreateTime=DateTime.Now.AddDays(-19)},
               new Product(){Id=3,CategoryId=2, Name="活著", Price=57,CreateTime=DateTime.Now.AddMonths(-3)},
               new Product(){Id=4,CategoryId=3, Name="高等數學", Price=97,CreateTime=DateTime.Now.AddMonths(-1)},
               new Product(){Id=5,CategoryId=6, Name="國傢寶藏", Price=52.8,CreateTime=DateTime.Now.AddMonths(-1)}
            };

            // 1、查詢表達式
            var queryExpress = from c in listCategory
                               join p in listProduct on c.Id equals p.CategoryId
                               select new { Id = c.Id, CategoryName = c.CategoryName, ProductName = p.Name, PublishTime = p.CreateTime };
            Console.WriteLine("查詢表達式輸出:");
            foreach (var item in queryExpress)
            {
                Console.WriteLine($"id:{item.Id},CategoryName:{item.CategoryName},ProductName:{item.ProductName},PublishTime:{item.PublishTime}");
            }
            Console.WriteLine("方法語法輸出:");
            // 方法語法
            var queryFun = listCategory.Join(listProduct, c => c.Id, p => p.CategoryId, (c, p) => new { Id = c.Id, CategoryName = c.CategoryName, ProductName = p.Name, PublishTime = p.CreateTime });
            foreach (var item in queryFun)
            {
                Console.WriteLine($"id:{item.Id},CategoryName:{item.CategoryName},ProductName:{item.ProductName},PublishTime:{item.PublishTime}");
            }

            Console.ReadKey();
        }
    }
}

結果:

從結果中可以看出:Join()操作符隻會輸出兩個結合中相同的元素,和T-SQL中的inner join類似。

在T-SQL中除瞭內連接以外,還有左連接和右連接,那麼使用Join()是不是也可以實現左連接和右連接呢?請看下面的例子。

使用Join()實現左連接。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化數據
            List<Category> listCategory = new List<Category>()
            {
              new Category(){ Id=1,CategoryName="計算機",CreateTime=DateTime.Now.AddYears(-1)},
              new Category(){ Id=2,CategoryName="文學",CreateTime=DateTime.Now.AddYears(-2)},
              new Category(){ Id=3,CategoryName="高校教材",CreateTime=DateTime.Now.AddMonths(-34)},
              new Category(){ Id=4,CategoryName="心理學",CreateTime=DateTime.Now.AddMonths(-34)}
            };
            List<Product> listProduct = new List<Product>()
            {
               new Product(){Id=1,CategoryId=1, Name="C#高級編程第10版", Price=100.67,CreateTime=DateTime.Now},
               new Product(){Id=2,CategoryId=1, Name="Redis開發和運維", Price=69.9,CreateTime=DateTime.Now.AddDays(-19)},
               new Product(){Id=3,CategoryId=2, Name="活著", Price=57,CreateTime=DateTime.Now.AddMonths(-3)},
               new Product(){Id=4,CategoryId=3, Name="高等數學", Price=97,CreateTime=DateTime.Now.AddMonths(-1)},
               new Product(){Id=5,CategoryId=6, Name="國傢寶藏", Price=52.8,CreateTime=DateTime.Now.AddMonths(-1)}
            };
            // 1、使用查詢表達式實現左連接
            var listLeft = from c in listCategory
                           join p in listProduct on c.Id equals p.CategoryId
                           into cpList
                           from cp in cpList.DefaultIfEmpty()
                           select new
                           {
                               Id = c.Id,
                               CategoryName = c.CategoryName,
                               ProductName = cp == null ? "無產品名稱" : cp.Name,
                               PublishTime = cp == null ? "無創建時間" : cp.CreateTime.ToString()
                           };
            foreach (var item in listLeft)
            {
                Console.WriteLine($"id:{item.Id},CategoryName:{item.CategoryName},ProductName:{item.ProductName},PublishTime:{item.PublishTime}");
            }

            Console.ReadKey();
        }
    }
}

結果:

從結果中可以看出:左表listCategory中的數據全部輸出瞭,listCategory中分類為4的在listProduct中沒有對應的產品記錄,所以該項的ProductName和PublishTime輸出為空。

註意:

在左連接中,有可能右表中沒有對應的記錄,所以使用Select投影操作符的時候要註意判斷是否為null,否則程序會報錯,看下面的例子:

使用方法語法實現左連接要使用下面要講的GroupJoin(),所以使用方法語法實現左連接放到GroupJoin()中進行講解。

右連接

其實實現右連接隻需要將上面例子中的左表和右邊換一下順序即可。看下面的例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化數據
            List<Category> listCategory = new List<Category>()
            {
              new Category(){ Id=1,CategoryName="計算機",CreateTime=DateTime.Now.AddYears(-1)},
              new Category(){ Id=2,CategoryName="文學",CreateTime=DateTime.Now.AddYears(-2)},
              new Category(){ Id=3,CategoryName="高校教材",CreateTime=DateTime.Now.AddMonths(-34)},
              new Category(){ Id=4,CategoryName="心理學",CreateTime=DateTime.Now.AddMonths(-34)}
            };
            List<Product> listProduct = new List<Product>()
            {
               new Product(){Id=1,CategoryId=1, Name="C#高級編程第10版", Price=100.67,CreateTime=DateTime.Now},
               new Product(){Id=2,CategoryId=1, Name="Redis開發和運維", Price=69.9,CreateTime=DateTime.Now.AddDays(-19)},
               new Product(){Id=3,CategoryId=2, Name="活著", Price=57,CreateTime=DateTime.Now.AddMonths(-3)},
               new Product(){Id=4,CategoryId=3, Name="高等數學", Price=97,CreateTime=DateTime.Now.AddMonths(-1)},
               new Product(){Id=5,CategoryId=6, Name="國傢寶藏", Price=52.8,CreateTime=DateTime.Now.AddMonths(-1)}
            };
            // 使用查詢表達式實現右連接
            var listRight = from p in listProduct
                            join c in listCategory on p.CategoryId equals c.Id
                            into pcList
                            from pc in pcList.DefaultIfEmpty()
                            select new
                            {
                                Id = p.Id,
                                CategoryName = pc == null ? "無分類名稱" : pc.CategoryName,
                                ProductName = p.Name,
                                PublishTime = p.CreateTime
                            };
            foreach (var item in listRight)
            {
                Console.WriteLine($"id:{item.Id},CategoryName:{item.CategoryName},ProductName:{item.ProductName},PublishTime:{item.PublishTime}");
            }

            Console.ReadKey();
        }
    }
}

結果:

從結果中可以看出:listProduct中的數據全部輸出瞭,listCategory中如果沒有相應的記錄就輸出空值。

註意:

在右連接中也需要和左連接一樣進行null值的判斷。

二、GroupJoin()操作符

GroupJoin()操作符常用於返回“主鍵對象-外鍵對象集合”形式的查詢,例如“產品類別-此類別下的所有產品”。

GroupJoin操作符也用於連接兩個輸入序列,但與Join操作符不同稍有不同,Join操作符在列舉outer序列元素時,會將一個outer序列元素和其對應的inner序列元素作為一組參數傳遞給委托resultSelector委托,這就意味著如果某一個outer序列元素有多個對應的inner序列元素,Join操作符將會分多次將outer序列元素和每一個對應的inner序列元素傳遞給委托resultSelector。使用GroupJoin操作符時,如果某一個outer序列元素有多個對應的inner序列元素,那麼這多個對應的inner序列元素會作用一個序列一次性傳遞給委托resultSelecotr,可以針對此序列添加一些處理邏輯。下面看看GroupJoin()操作符的定義:

public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector,
 Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector);
public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter, TKey> outerKeySelector,
Func<TInner, TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>, TResult> resultSelector, IEqualityComparer<TKey> comparer);

留意變紅的那個委托,註意,與Join操作符的不同點就是一個outer序列中如果有多個對應的inner序列元素,會作為一個集合IEnumerable<TInner>傳遞到此委托。來看下面的例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化數據
            List<Category> listCategory = new List<Category>()
            {
              new Category(){ Id=1,CategoryName="計算機",CreateTime=DateTime.Now.AddYears(-1)},
              new Category(){ Id=2,CategoryName="文學",CreateTime=DateTime.Now.AddYears(-2)},
              new Category(){ Id=3,CategoryName="高校教材",CreateTime=DateTime.Now.AddMonths(-34)},
              new Category(){ Id=4,CategoryName="心理學",CreateTime=DateTime.Now.AddMonths(-34)}
            };
            List<Product> listProduct = new List<Product>()
            {
               new Product(){Id=1,CategoryId=1, Name="C#高級編程第10版", Price=100.67,CreateTime=DateTime.Now},
               new Product(){Id=2,CategoryId=1, Name="Redis開發和運維", Price=69.9,CreateTime=DateTime.Now.AddDays(-19)},
               new Product(){Id=3,CategoryId=2, Name="活著", Price=57,CreateTime=DateTime.Now.AddMonths(-3)},
               new Product(){Id=4,CategoryId=3, Name="高等數學", Price=97,CreateTime=DateTime.Now.AddMonths(-1)},
               new Product(){Id=5,CategoryId=6, Name="國傢寶藏", Price=52.8,CreateTime=DateTime.Now.AddMonths(-1)}
            };

            // 使用GroupJoin()實現左連接
            var listLeftFun = listCategory.GroupJoin(listProduct, c => c.Id, p => p.CategoryId, (c, listp) => listp.DefaultIfEmpty(new Product()).Select(z =>
                 new
                 {
                     Id = c.Id,
                     CategoryName = c.CategoryName,
                     ProduceName = z.Name,
                     ProductPrice = z.Price
                 })).ToList();
            foreach (var item in listLeftFun)
            {
                foreach (var p in item)
                {
                    Console.WriteLine($"CategoryId:{p.Id},CategoryName:{p.CategoryName},ProduceName:{p.ProduceName},ProductPrice:{p.ProductPrice}");
                }
            }
            Console.ReadKey();
        }

    }
}

結果:

結果可以看出:使用GroupJoin()操作符可以用Lambda表達式實現左連接。

右連接

隻需要調整上面例子中兩張表的順序即可。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConnectOperation
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化數據
            List<Category> listCategory = new List<Category>()
            {
              new Category(){ Id=1,CategoryName="計算機",CreateTime=DateTime.Now.AddYears(-1)},
              new Category(){ Id=2,CategoryName="文學",CreateTime=DateTime.Now.AddYears(-2)},
              new Category(){ Id=3,CategoryName="高校教材",CreateTime=DateTime.Now.AddMonths(-34)},
              new Category(){ Id=4,CategoryName="心理學",CreateTime=DateTime.Now.AddMonths(-34)}
            };
            List<Product> listProduct = new List<Product>()
            {
               new Product(){Id=1,CategoryId=1, Name="C#高級編程第10版", Price=100.67,CreateTime=DateTime.Now},
               new Product(){Id=2,CategoryId=1, Name="Redis開發和運維", Price=69.9,CreateTime=DateTime.Now.AddDays(-19)},
               new Product(){Id=3,CategoryId=2, Name="活著", Price=57,CreateTime=DateTime.Now.AddMonths(-3)},
               new Product(){Id=4,CategoryId=3, Name="高等數學", Price=97,CreateTime=DateTime.Now.AddMonths(-1)},
               new Product(){Id=5,CategoryId=6, Name="國傢寶藏", Price=52.8,CreateTime=DateTime.Now.AddMonths(-1)}
            };
            var listRightFun = listProduct.GroupJoin(listCategory, p => p.CategoryId, c => c.Id, (p, listc) => listc.DefaultIfEmpty(new Category()).Select(z =>
            new
            {
                id = p.Id,
                ProductName = p.Name,
                ProductPrice = p.Price,
                CategoreName = z.CategoryName
            }));
            foreach (var item in listRightFun)
            {
                foreach (var p in item)
                {
                    Console.WriteLine($"id:{p.id},ProduceName:{p.ProductName},ProductPrice:{p.ProductPrice},CategoryName:{p.CategoreName}");
                }
            }
        }
    }
}

結果:

以上就是本文的全部內容,希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: