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)愛使用了查詢語法。
我希望你能使用本文的一些技巧,請繼續關註以後的帖子。
朋友會在“發現-看一看”看到你“在看”的內容