搜尋此網誌

2024年5月2日 星期四

C# Linq 的 join 與 Group by

原起:


    不久前,我們 3A 站台有出現資料重複顯示的問題,去查才發現是發生在 LINQ 的 join 上,這邊直接開 Console 範例程式講解,用 class 來代表資料表的欄位架構,有兩個 class,Performance 跟 Review

internal class Program
{
    class Performance
    {
        public int SN { get; set; }
        public string Name { get; set; }

        public Performance(int sN, string name)
        {
            SN = sN;
            Name = name;
        }

        public override string ToString()
        {
            return $"SN:{SN}, Name:{Name}";
        }
    }

    class Review
    {
        public int PerformanceSN { get; set; }
        public string Name { get; set; }

        public bool IsPass { get; set; }

        public Review(int performanceSN, string name, bool isPass)
        {
            PerformanceSN = performanceSN;
            Name = name;
            IsPass = isPass;
        }

        public override string ToString()
        {
            return $"PerformanceSN:{PerformanceSN}, Name:{Name}, IsPass:{IsPass}";
        }
    }
}

    Performance 有 SN,代表流水號,Name 是表演者的名字。Review 會有 PerformanceSN,用來表示它對應到的是哪筆 Performance (SN),Name 是評審的名字,IsPass 代表有無通過。Performance 跟 Review 是一對多的關係。

    再來是加入產測試資料的 method,由於它們再來會在 Main 裡被呼叫,所以是 static

internal class Program
{
    class Performance
    {
        public int SN { get; set; }
        public string Name { get; set; }

        public Performance(int sN, string name)
        {
            SN = sN;
            Name = name;
        }

        public override string ToString()
        {
            return $"SN:{SN}, Name:{Name}";
        }
    }

    class Review
    {
        public int PerformanceSN { get; set; }
        public string Name { get; set; }

        public bool IsPass { get; set; }

        public Review(int performanceSN, string name, bool isPass)
        {
            PerformanceSN = performanceSN;
            Name = name;
            IsPass = isPass;
        }

        public override string ToString()
        {
            return $"PerformanceSN:{PerformanceSN}, Name:{Name}, IsPass:{IsPass}";
        }
    }

    /// <summary>
    /// 產 Performance 資料
    /// </summary>
    /// <returns></returns>
    static List<Performance> _CreatePerformance()
    {
        List<Performance> listReturn = new List<Performance>();
        listReturn.Add(new Performance(1, "鳥旭"));
        listReturn.Add(new Performance(2, "薯泥"));
        listReturn.Add(new Performance(3, "阿祈"));
        listReturn.Add(new Performance(4, "肥伶"));

        return listReturn;
    }

    /// <summary>
    /// 產 Review 資料
    /// </summary>
    /// <returns></returns>
    static List<Review> _CreateReview()
    {
        List<Review> listReturn = new List<Review>();
        listReturn.Add(new Review(1, "評審一", true));
        listReturn.Add(new Review(2, "評審二", true));
        listReturn.Add(new Review(3, "評審三", true));
        listReturn.Add(new Review(4, "評審四", false));
        return listReturn;
    }
}

    假如遊戲規則是,只要有任何一個 Review 有 Pass,對應的 Performance 就可以晉級。現在,如果想從資料中抓出有通過的 Performance,並列印出來,我們可以用 join 來達成

static void Main(string[] args)
{
    List<Performance> listAllPerformance = _CreatePerformance();
    List<Review> listAllReview = _CreateReview();

    List<Performance> listPassedRequest =
        (from performance in listAllPerformance
         join review in listAllReview on performance.SN equals review.PerformanceSN
         where review.IsPass
         select performance).ToList();

    foreach (Performance request in listPassedRequest)
    {
        Console.WriteLine(request);
    }

    Console.ReadKey();
}

    目前資料的情況是一筆 Review 對應到一筆 Performance,輸出的結果在預期中



問題:


    如果修改 _CreateReivew 方法,再多加一位 Review 進來

static List<Review> _CreateReview()
{
    List<Review> listReturn = new List<Review>();
    listReturn.Add(new Review(1, "評審一", true));
    listReturn.Add(new Review(2, "評審二", true));
    listReturn.Add(new Review(3, "評審三", true));
    listReturn.Add(new Review(4, "評審四", false));
    listReturn.Add(new Review(1, "評審五", true)); //新加入的
    return listReturn;
}

    之後再執行程式,會發現 "鳥旭" 變兩個,產生重複的資料 (linq 的 join 是 inner join)


    所以,重複的資料需要自己過濾。


Linq GroupBy:


    SQL 的 SELECT 有 DISTINCT 的功能,但 Linq 似乎沒有,我後來去查,在這邊找到解答,解答就是用 GroupBy,程式改成

static void Main(string[] args)
{
    List<Performance> listAllPerformance = _CreatePerformance();
    List<Review> listAllReview = _CreateReview();

    List<Performance> listPassedRequest =
        (from performance in listAllPerformance
         join review in listAllReview on performance.SN equals review.PerformanceSN
         where review.IsPass
         select performance)
         .GroupBy(o => o.SN)
         .Select(o => o.First())
         .ToList();

    foreach (Performance request in listPassedRequest)
    {
        Console.WriteLine(request);
    }

    Console.ReadKey();
}

    第11行 : 依據 Performance 的 SN 來分群。
    第12行 : 用 Linq 的 Select,還有 IGroup<int, Performance> 的 First() 來取得每群的第一個 Performance。 

    就能達成效果



沒有留言:

張貼留言