資料分組:使用 HashSet 或 GroupBy + SelectMany 取得各分組的前 N 筆資料

前言

最近遇到一個需求,要將多欄位的資料分組,再將各分組的資料排序後,取出前 N 筆資料出來。

舉例假想情境如下:

  • 有兩個隊伍:Blue Team 和 Red Team
  • 比賽項目有三種:Black Jack、Poker Hand 和 Solitaire
  • 需求:取出每一種比賽項目,每個隊伍最高分的資料

需求圖例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void Main()
{
var list = GetSampleData();
}

public List<Player> GetSampleData()
{
return new List<Player>
{
new Player { PlayerId="001", PlayerName="Sam", Team="Blue Team", Game="Black Jack", Score=798 },
new Player { PlayerId="002", PlayerName="Jack", Team="Blue Team", Game="Poker Hand", Score=823 },
new Player { PlayerId="003", PlayerName="Tiffany", Team="Red Team", Game="Black Jack", Score=627 },
new Player { PlayerId="004", PlayerName="Betty", Team="Red Team", Game="Poker Hand", Score=803 },
new Player { PlayerId="005", PlayerName="Jessica", Team="Red Team", Game="Solitaire", Score=858 },
new Player { PlayerId="006", PlayerName="Mia", Team="Red Team", Game="Poker Hand", Score=943 },
new Player { PlayerId="007", PlayerName="Tom", Team="Blue Team", Game="Black Jack", Score=661 },
new Player { PlayerId="008", PlayerName="Cindy", Team="Red Team", Game="Solitaire", Score=735 },
new Player { PlayerId="009", PlayerName="Jenny", Team="Red Team", Game="Black Jack", Score=513 },
new Player { PlayerId="010", PlayerName="Ken", Team="Blue Team", Game="Solitaire", Score=672 },
new Player { PlayerId="011", PlayerName="Joey", Team="Blue Team", Game="Poker Hand", Score=957 },
new Player { PlayerId="012", PlayerName="Mary", Team="Red Team", Game="Black Jack", Score=759 },
new Player { PlayerId="013", PlayerName="John", Team="Blue Team", Game="Solitaire", Score=724 },
};
}

public class Player
{
public string PlayerId { get; set; }
public string PlayerName { get; set; }
public string Team { get; set; }
public string Game { get; set; }
public int Score { get; set; }
}

印象中,三年前回鍋軟體開發,自學 C# 時有用 LINQ 做過類似的練習,之後一直沒機會遇到這種使用情境,時間久便忘記要怎麼寫了。

想不起來 LINQ 要怎麼寫,當下先用想到的方式解決,假日把 LINQ 的寫法也研究回來,現在整理成筆記,將來就不用再花時間重做功課了。

按照時間序,總共研究出三種寫法:

  • 方法一:使用 HashSet 處理

  • 方法二:使用 LINQ - ToHashSet() 處理

  • 方法三:使用 LINQ - GroupBy() 與 SelectMany() 處理

閱讀全文 »

工作雜記:ASP.NET Core 5 MVC 解決檔案上傳損壞問題

問題描述

昨天收到朋友的求救,他在其他駐點的工程師遇到一個奇怪的問題,已經處理三、四天還是解決不了,請我幫忙處理。

對方負責的專案是用 ASP.NET Core 5 MVC 開發的,有一個上傳 Excel 匯入資料的功能,在 IDE 將站台執行起來,可正常上傳並匯入資料,但發佈到測試機的 IIS 站台,檔案上傳完在匯入資料時會發生例外。

為了修正問題,在匯入 Excel 的部份,他嘗試換過各種套件,但全都發生例外,各套件出現的訊息如下:

  • ClosedXML:FileFormatException: File contains corrupted data
  • NPOI:ZipException: EOF in header
  • ExcelDataReader:Offset to Central Directory cannot be held in an Int64

後來他發現上傳到 Server 的 Excel 檔案都無法開啟,似乎檔案都壞了。

也嘗試修改檔案上傳的寫法,但都無效,寫入的檔案就是會損壞。

閱讀全文 »

C# 覆寫 Equals 方法,為何要覆寫 GetHashCode 方法

前置知識

在看本文之前,要先知道什麼是 Value Type (實質型別) 和 Reference Type (參考型別),不熟的朋友可以參考這篇文章

[C#][筆記] Value Type (實值型別) vs. Reference Type (參考型別)

隱含繼承 Object 類別

微軟官方教學文件有提到,所有的型別都隱含繼承自 Object 類別。

C# 中的繼承 Microsoft Docs

因此用所有型別建立出來的物件都可以使用從 Object 繼承來的方法,如下圖

隱含繼承 Object 類別的方法

Object 的 Equals 方法

宣告一個 Data 類別,並分別建立出兩個物件給 data1 和 data2 變數,這裡刻意將欄位都填入相同的值,再用繼承自 Object 的 Equals 方法比較兩個物件是否相等。

Object 的 Equals 方法

因為是 Reference Type (參考型別),雖然 data1 和 data2 所有欄位的值都相同,但兩個變數在 Stack 內儲存的記憶體位址的值是不同的,因此 Equals 回傳的結果為 false。

覆寫 Equals 方法

如果我們希望將比對條件改為物件內欄位的值,可以覆寫 Equals 方法,自訂比對的條件,例如希望這個物件的 Equals 要同時比對 Id 和 Name 兩個欄位的值,可以這樣寫:

覆寫 Equals 方法

自訂比對的條件,覆寫 Equals 方法:

  • 若傳入 Equals 的物件參數(要比對的對象)不是 Data 型別,回傳 false。
  • 若傳入的物件參數是 Data 型別,比對 Id 和 Name 兩個欄位的值是否相等,並回傳比對結果。

此時 IDE 在 Data 類別的宣告處出現綠色波浪提示訊息,提示我們覆寫 Equals 方法時,也要覆寫 GetHashCode 方法。

什麼是 GetHashCode?為什麼要覆寫這個方法?在說明前,先科普什麼是「雜湊表」。

閱讀全文 »

模擬情境

公司有 Sam 和 Jack 兩位開發者,用公司內部 Git 版控儲存庫進行開發。

簡述版控流程,開發者先在自己的分支開發,再將結果合併回 Develop 分支驗測功能,驗證無誤再透過 PR 程序合併至 master 分支上線。

公司內部版控儲存庫不提供任何管道給外部網路連線使用,但有開放 Webex 可遠端連線進去控制公司內部的電腦。

如下圖,假設公司在 commit e3251c8 時開始實施 Work From Home,從這裡開始就無法再連線至公司內的遠端儲存庫。

image001.png

實施 Work From Home 後,Sam 開始修改本地 Develop_Sam 分支的 README.md 檔案內容,並且合併回本地的 Develop 分支。

image003.png

同時,Jack 也在修改本地 Develop_Jack 分支的 README.md 檔案內容,也合併回本地的 Develop 分支。

image005.png

前兩張圖可看到 Sam 與 Jack 在本地 Develop 分支的版控記錄 (Commit ID) 已經不一致了,雙方修改過的 README.md 檔案內容如下,之後若要合併肯定會發生衝突。

image007.png

接下來 Sam 和 Jack 要同步彼此的開發進度,但是連不到公司的版控儲存庫了,此時有以下幾種選擇。

可用的方法

  1. 使用外部私有 Git 遠端儲存庫同步彼此的版控記錄 (公司沒有禁止的情況適用)

    • 此做法無法解決上版至公司內部儲存庫的問題,只能解決開發者間的版控同步問題 (但光是解決這個問題,就可省下大半的時間了)

    • 公司不一定允許 Source Code 放在外部的私有儲存庫

  2. 將 Source Code (包含 .git 目錄) 通通打包傳給其他開發者或公司 (你瘋了嗎? 😆)

    • 每次交付的檔案都很大包,就算只改一個字,要上版都要整包傳輸,我實在想不到這個做法的優點…
  3. git bundle 產生版控記錄檔 (★ 推薦)

    • 會產生一個容量很小的 binary 檔案利於傳輸,檔案大小取決於要包含的版控記錄多寡,裡面會有修改的檔案內容,以及 commit 版控記錄

    • 本地端可透過 git pull 將記錄檔的版控內容 pull 合併至本地分支,並且保持開發的版控記錄,也可以送入公司同步版控記錄

    • 可把 git bundle 當成原本 git push 的替代操作,只是變成離線版

  4. git format-patch 產生內容修補檔 (只適合交付修補程式用,不適合開發過程同步版控用)

    • 開發者可套用修補檔以更新彼此修改過的檔案內容,也可送入公司上版,但是在開發過程中,每個人的版控記錄會不一致

    • 修補檔記錄的是 git diff 的差異內容,檔案也不大,但不會保留版控記錄,套用修補檔時,背後是透過 git commit 在處理,會產生新的版控記錄 (commit hash)

    • 版控記錄脫鉤,會造成查找問題或溝通上的困難,將來連回公司內部的版控儲存庫時,有可能要再面臨大量合併衝突的問題

    • git format-patch 只適合交付修補程式用,不適合開發過程同步版控用,若不打算同步版控記錄就沒差了

為了節省大家寶貴的時間,先說結論

  • 如果公司禁止使用私有儲存庫

    • 開發過程的版控同步
      每位開發者的修改告一段落,就用 git bundle 產生 Develop 分支的版控記錄檔,交付給其他開發者同步 Develop 分支的版控記錄

    • 功能開發完成,要回傳公司上版
      git bundle 產生 Develop 分支的版控記錄檔,只是這次要傳進公司,遠端遙控公司內部電腦操作 git pull 將版控記錄檔合併到 Develop 分支,驗測無誤後再 PR 合併至 master 分支

  • 如果公司同意使用私有儲存庫 (★ 個人認為這是最佳狀況)

    • 開發過程的版控同步
      使用私有 Git 儲存庫進行開發,只有在這種非常時期才深刻體會到可以 git push 是幸福的 😆

    (安全性要記得做好,否則 Source Code 外流就……會變成 Open Source 😆 )

    • 功能開發完成,要回傳公司上版
      git bundle 產生 Develop 分支的版控記錄檔,只是這次要傳進公司,遠端遙控公司內部電腦操作 git pull 將版控記錄檔合併到 Develop 分支,驗測無誤後再 PR 合併至 master 分支

接下來說實作,有興趣的人請繼續看下去

閱讀全文 »

需求

目前工作的開發環境有用 CI/CD,且會執行 Fortify 掃描網站弱點,PM 要手動將 Fortify 產出的 PDF 表格數據整理到 Word 文件,他希望這種重覆性質的工作可以做成自動化,本文主要是記錄如何將 PDF 文件內表格數據解析出來的過程。

概念

  1. 使用 IFilterTextReader (NuGet 套件) 讀取 PDF 文件
  2. 使用正規表達式將表格數據解析成資料集合的物件
  3. 將資料集合填入到 Word 文件 (本文不會實作)
閱讀全文 »

今早在復習 SQL 語法,看到十年前的 JOIN 筆記,決定把他圖形化,以後若要用到就一目瞭然了
既然都做了,就貼上來分享,有需要的人請自取 🙂

閱讀全文 »