[译]如何在C#中调试LINQ查询
LINQ是我在C#中最喜歡的功能之一。它讓代碼看起來更漂亮美觀。我們得到了一個易于編寫和理解的簡潔函數式語法。好吧,至少我們可以使用LINQ方法的語法風格。
LINQ很難進行調試。我們無法知道該查詢內部發生了什么。我們可以看到輸入和輸出,但這就是它的全部。出現問題時會發生什么?我們只是盯著代碼,試圖獲得某種洞察力?必須有一個更好的方式……
調試LINQ
雖然很難,但可以使用一些技術來調試LINQ。
首先,我們創建一個小場景。假設我們想要一份按年齡排序的3名男性員工的名單,這些員工的薪水高于平均水平。這是一個非常常見的查詢類型,對吧?這是我為此編寫的代碼:
public IEnumerable<Employee> MyQuery(List<Employee> employees) { var avgSalary = employees.Select(e=>e.Salary).Average(); return employees .Where(e => e.Gender == "Male") .Take(3) .Where(e => e.Salary > avgSalary) .OrderBy(e => e.Age); }數據集為:
| 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”。 我現在可以對查詢做一個小的修復:
var res = employees
.Where(e => e.Gender.ToLower() == "male") // added "ToLower()"
.Take(3)
.Where(e => e.Salary > avgSalary)
.OrderBy(e => e.Age);
修復后,執行代碼得到結果為:
Jose Mond, 62000, 35
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運算符。將查詢改為一下內容:
var res = employees
.Where(e => e.Gender.ToLower() == "male")
.Where(e => e.Salary > avgSalary)
.Take(3)
.OrderBy(e => e.Age);
正確的結果是:Jose Mond,Peter Claus 和 Mike Mockson。
在LINQ to SQL中,這種技術不起作用。
3. 使用日志中間件方法
讓我們回到錯誤尚未修復的初始狀態,面對看似正確的查詢,我們都傻眼了。
調試查詢的另一個方法是使用以下擴展方法:
public static IEnumerable<T> LogLINQ<T>(this IEnumerable<T> enumerable, string logName, Func<T, string> printMethod)
{
#if DEBUG
int count = 0;
foreach (var item in enumerable)
{
if (printMethod != null)
{
Debug.WriteLine($"{logName}|item {count} = {printMethod(item)}");
}
count++;
yield return item;
}
Debug.WriteLine($"{logName}|count = {count}");
#else
return enumerable;
#endif
}
以下是如何使用它:
var res = employees
.LogLINQ("source", e=>e.Name)
.Where(e => e.Gender == "Male")
.LogLINQ("logWhere", e=>e.Name)
.Take(3)
.LogLINQ("logTake", e=>e.Name)
.Where(e => e.Salary > avgSalary)
.LogLINQ("logWhere2", e=>e.Name)
.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。這將僅顯示與日志名稱前綴匹配的行。
查看輸出窗口,可以看到以下幾點:
源中包括“Jose Mond”,但?logWhere沒有,這是因為我們之前看到的區分大小寫的錯誤。
由于提前使用?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操作。它甚至沒有在最后兩次操作 OrderBy 和 Take中處理。
如果這還不夠,你可以按右上角的“lambda”按鈕查看完整的LINQ分析。以下是它的樣子:
因此,在調試LINQ方面,你幾乎可以充滿希望和夢想。
總結
調試LINQ不是很直觀,但可以通過一些技術很好的完成。
我沒有提到LINQ查詢語法,因為他沒有被使用太多。只有技術#2 (lambda斷點)和技術#4 (OzCode)愛使用了查詢語法。
我希望你能使用本文的一些技巧,請繼續關注以后的帖子。
總結
以上是生活随笔為你收集整理的[译]如何在C#中调试LINQ查询的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 也读《人月神话》:没有银弹的软件工程
- 下一篇: WebSocket数据加密——AES与R