歡迎光臨
每天分享高質量文章

[譯]如何在C#中除錯LINQ查詢

LINQ是我在C#中最喜歡的功能之一。它讓代碼看起來更漂亮美觀。我們得到了一個易於編寫和理解的簡潔函式式語法。好吧,至少我們可以使用LINQ方法的語法風格。

LINQ很難進行除錯。我們無法知道該查詢內部發生了什麼。我們可以看到輸入和輸出,但這就是它的全部。出現問題時會發生什麼?我們只是盯著代碼,試圖獲得某種洞察力?必須有一個更好的方式……

除錯LINQ

雖然很難,但可以使用一些技術來除錯LINQ。

首先,我們創建一個小場景。假設我們想要一份按年齡排序的3名男性員工的名單,這些員工的薪水高於平均水平。這是一個非常常見的查詢型別,對吧?這是我為此編寫的代碼:

  1. public IEnumerable<Employee> MyQuery(List<Employee> employees)
  2. {
  3. var avgSalary = employees.Select(e=>e.Salary).Average();
  4.  
  5. return employees
  6. .Where(e => e.Gender == "Male")
  7. .Take(3)
  8. .Where(e => e.Salary > avgSalary)
  9. .OrderBy(e => e.Age);
  10. }

資料集為:

姓名 年齡 性別 收入
Peter Claus 40 “Male” 61000
Jose Mond 35 “male” 62000
Helen Gant 38 “Female” 38000
Jo Parker 42 “Male” 52000
Alex Mueller 22 “Male” 39000
Abbi Black 53 “female” 56000
Mike Mockson 51 “Male” 82000

當運行此查詢時,我得到的結果為: PeterClaus,61000,40

這似乎不對…… 應改有3名員工的。而平均工資約為56400,因此結果中應包括薪水為62000的“Jose Mond”和薪水為82000的“Mike Mockson”。

所以,我的LINQ查詢中有一個錯我,該怎麼辦呢?好吧,我可以盯著代碼,直到我弄明白,這甚至可能適用於這種特殊情況。或者,我可以以某種方式除錯它。讓我們看看如何除錯它。

1. 在快速監視中評估查詢的各個部分

你可以做的最簡單的事情之一就是在快速監視中分析各個查詢。你可以從第一個操作開始,然後繼續第一個和第二個操作,以此類推。

這裡有一個例子:

你可以使用OzCode的顯示功能來顯示你感興趣的欄位,這樣可以輕鬆找到問題。

我們可以看到即使在第一次查詢之後,就出現了問題。“Jose Mond” 一個男性,貌似沒有查詢到。現在,我可以盯著一小段代碼找出錯誤。我想我明白了,Jose的性別寫成了“male”,而不是“Male”。
我現在可以對查詢做一個小的修複:

  1. var res = employees
  2. .Where(e => e.Gender.ToLower() == "male") // added "ToLower()"
  3. .Take(3)
  4. .Where(e => e.Salary > avgSalary)
  5. .OrderBy(e => e.Age);

修複後,執行代碼得到結果為:

  1. Jose Mond, 62000, 35
  2. Peter Claus, 61000, 40

現在包括了Jose,所以修複了第一個錯誤。還有另一個錯誤,“Mike Mockson”仍然缺失,我們將用下一個技術解決。
這種技術有其缺點。如果你需要在大集合中查找特定專案,則可能需要在快速監視視窗中話費大量時間。

另請註意,某些查詢可以更改應用程式狀態。例如,你可以在lambda函式中呼叫一個可以改變瞬時值的方法,像 varres=source.Select(x=>x.Age++) 。通過在快速監視視窗運行,將改變應用程式狀態並危及除錯會話。通過在運算式中添加 ,nse 無副作用後綴(no-side-effects postfix )避免這種情況。要使用它,首先將運算式複製到剪貼板,打開一個空的快速監視視窗,然後使用 ,nse後綴手動粘貼運算式。

2. 將斷點放入lambda運算式中

另一個除錯LINQ的好方法是在lambda運算式中放置一個斷點。這允許評估單個專案。對應大型集合,你可以將其與條件斷點功能結合使用。
在我們的例子中,我們發現“Mike Mockson”不是第一個Where操作結果的一部分。你可以在 .Where(e=>e.Gender=="Male")lambda運算式中放置條件斷點,條件為: e.Name=="Mike Mockson

運行查詢後,我們將看到:

只打印了3個名字,那是因為我們的查詢條件中有 .Take(3),在前3次匹配後停止評估。我們確實想要一份按年齡排序的3名男性員工的名單,這些員工薪水高於平均水平。所以我們可能應該在檢查薪水後才使用 Take運算子。將查詢改為一下內容:

  1. var res = employees
  2. .Where(e => e.Gender.ToLower() == "male")
  3. .Where(e => e.Salary > avgSalary)
  4. .Take(3)
  5. .OrderBy(e => e.Age);

正確的結果是:Jose MondPeter ClausMike Mockson

在LINQ to SQL中,這種技術不起作用。

3. 使用日誌中間件方法

讓我們回到錯誤尚未修複的初始狀態,面對看似正確的查詢,我們都傻眼了。

除錯查詢的另一個方法是使用以下擴展方法:

  1. public static IEnumerable<T> LogLINQ<T>(this IEnumerable<T> enumerable, string logName, Func<T, string> printMethod)
  2. {
  3. #if DEBUG
  4. int count = 0;
  5. foreach (var item in enumerable)
  6. {
  7. if (printMethod != null)
  8. {
  9. Debug.WriteLine($"{logName}|item {count} = {printMethod(item)}");
  10. }
  11. count++;
  12. yield return item;
  13. }
  14. Debug.WriteLine($"{logName}|count = {count}");
  15. #else
  16. return enumerable;
  17. #endif
  18. }

以下是如何使用它:

  1. var res = employees
  2. .LogLINQ("source", e=>e.Name)
  3. .Where(e => e.Gender == "Male")
  4. .LogLINQ("logWhere", e=>e.Name)
  5. .Take(3)
  6. .LogLINQ("logTake", e=>e.Name)
  7. .Where(e => e.Salary > avgSalary)
  8. .LogLINQ("logWhere2", e=>e.Name)
  9. .OrderBy(e => e.Age);

輸出為:

說明和解釋:

  • 在LINQ查詢中的每個操作之後放置 LogLINQ方法。它可以選擇打印通過此操作的所有專案和總數。
  • logName是每個輸出的前綴,可以輕鬆查看編寫它的查詢步驟。我喜歡將其命名為之後操作相同的名稱。
  • Fun<T,string>printMethod允許打印給定專案的任何內容。在上面的示例中,我選擇使用 e=>e.Name打印員工的姓名,當為 null時,除總數外,不會打印任何內容。
  • 為了優化,此方法盡在除錯樣式下有效( #if DEBUG)。在發佈樣式下,它什麼都不做。
  • 每個專案都按順序打印,無需等待操作結束,這是因為LINQ的 lazy 特性。以下是查看單個操作結果的提示:將整個輸出複製到 notepad++。然後使用Ctrl+Shift+F(Find)並查找日誌前綴(例如 logWhere2)。在查找對話框,點擊Find All in Current Document。這將僅顯示與日誌名稱前綴匹配的行。

查看輸出視窗,可以看到以下幾點:

  1. 源中包括“Jose Mond”,但 logWhere沒有,這是因為我們之前看到的區分大小寫的錯誤。
  2. 由於提前使用 Take方法,“Mike Mockson”從未在源中進行評估。事實上,源的計數日誌完全丟失,因為它永遠不會到達集合的末尾。

對應 LINQ to SQL以及可能的其他LINQ程式,此技術存在問題。它將 IQueryable轉換為 IEnumerable,更改查詢並可能強制進行早期評估。最好不要將它用於任何LINQ程式(如Entity Framework)。

4. 使用OzCode的LINQ功能

如果你需要有效工具除錯LINQ,可以使用OzCode Visual Studio擴展。

免責宣告:我目前是OzCode員工。然而,這是我個人博客,這篇文章只是我的專業推薦。

OzCode將可視化你的LINQ查詢,以準確顯示每個專案的行為方式。首先,它將顯示每次操作後的專案數:

然後,你可以點擊任何編號按鈕以查看專案以及它們在操作中的進度。

我們可以看到“Jo Parker”在源中排名第4,在第一次 Where操作之後排名第3。它沒有通過第二次的 Where操作。它甚至沒有在最後兩次操作 OrderByTake中處理。

如果這還不夠,你可以按右上角的“lambda”按鈕查看完整的LINQ分析。以下是它的樣子:

因此,在除錯LINQ方面,你幾乎可以充滿希望和夢想。

總結

除錯LINQ不是很直觀,但可以通過一些技術很好的完成。

我沒有提到LINQ查詢語法,因為他沒有被使用太多。只有技術#2 (lambda斷點)和技術#4 (OzCode)愛使用了查詢語法。

我希望你能使用本文的一些技巧,請繼續關註以後的帖子。

赞(0)

分享創造快樂