[译]如何在C#中调试LINQ查询
LINQ是我在C#中最喜歡的功能之一。它讓代碼看起來更漂亮美觀。我們得到了一個易于編寫和理解的簡潔函數(shù)式語法。好吧,至少我們可以使用LINQ方法的語法風(fēng)格。
LINQ很難進(jìn)行調(diào)試。我們無法知道該查詢內(nèi)部發(fā)生了什么。我們可以看到輸入和輸出,但這就是它的全部。出現(xiàn)問題時會發(fā)生什么?我們只是盯著代碼,試圖獲得某種洞察力?必須有一個更好的方式……
調(diào)試LINQ
雖然很難,但可以使用一些技術(shù)來調(diào)試LINQ。
首先,我們創(chuàng)建一個小場景。假設(shè)我們想要一份按年齡排序的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); }數(shù)據(jù)集為:
| 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 |
當(dāng)運行此查詢時,我得到的結(jié)果為: PeterClaus,61000,40
這似乎不對…… 應(yīng)改有3名員工的。而平均工資約為56400,因此結(jié)果中應(yīng)包括薪水為62000的“Jose Mond”和薪水為82000的“Mike Mockson”。
所以,我的LINQ查詢中有一個錯我,該怎么辦呢?好吧,我可以盯著代碼,直到我弄明白,這甚至可能適用于這種特殊情況。或者,我可以以某種方式調(diào)試它。讓我們看看如何調(diào)試它。
1. 在快速監(jiān)視中評估查詢的各個部分
你可以做的最簡單的事情之一就是在快速監(jiān)視中分析各個查詢。你可以從第一個操作開始,然后繼續(xù)第一個和第二個操作,以此類推。
這里有一個例子:
你可以使用OzCode的顯示功能來顯示你感興趣的字段,這樣可以輕松找到問題。
我們可以看到即使在第一次查詢之后,就出現(xiàn)了問題。“Jose Mond” 一個男性,貌似沒有查詢到。現(xiàn)在,我可以盯著一小段代碼找出錯誤。我想我明白了,Jose的性別寫成了“male”,而不是“Male”。 我現(xiàn)在可以對查詢做一個小的修復(fù):
var res = employees
.Where(e => e.Gender.ToLower() == "male") // added "ToLower()"
.Take(3)
.Where(e => e.Salary > avgSalary)
.OrderBy(e => e.Age);
修復(fù)后,執(zhí)行代碼得到結(jié)果為:
Jose Mond, 62000, 35
Peter Claus, 61000, 40
現(xiàn)在包括了Jose,所以修復(fù)了第一個錯誤。還有另一個錯誤,“Mike Mockson”仍然缺失,我們將用下一個技術(shù)解決。 這種技術(shù)有其缺點。如果你需要在大集合中查找特定項目,則可能需要在快速監(jiān)視窗口中話費大量時間。
另請注意,某些查詢可以更改應(yīng)用程序狀態(tài)。例如,你可以在lambda函數(shù)中調(diào)用一個可以改變瞬時值的方法,像 varres=source.Select(x=>x.Age++) 。通過在快速監(jiān)視窗口運行,將改變應(yīng)用程序狀態(tài)并危及調(diào)試會話。通過在表達(dá)式中添加 ,nse 無副作用后綴(no-side-effects postfix )避免這種情況。要使用它,首先將表達(dá)式復(fù)制到剪貼板,打開一個空的快速監(jiān)視窗口,然后使用 ,nse后綴手動粘貼表達(dá)式。
2. 將斷點放入lambda表達(dá)式中
另一個調(diào)試LINQ的好方法是在lambda表達(dá)式中放置一個斷點。這允許評估單個項目。對應(yīng)大型集合,你可以將其與條件斷點功能結(jié)合使用。 在我們的例子中,我們發(fā)現(xiàn)“Mike Mockson”不是第一個Where操作結(jié)果的一部分。你可以在 .Where(e=>e.Gender=="Male")lambda表達(dá)式中放置條件斷點,條件為: e.Name=="Mike Mockson。
運行查詢后,我們將看到:
只打印了3個名字,那是因為我們的查詢條件中有 .Take(3),在前3次匹配后停止評估。我們確實想要一份按年齡排序的3名男性員工的名單,這些員工薪水高于平均水平。所以我們可能應(yīng)該在檢查薪水后才使用 Take運算符。將查詢改為一下內(nèi)容:
var res = employees
.Where(e => e.Gender.ToLower() == "male")
.Where(e => e.Salary > avgSalary)
.Take(3)
.OrderBy(e => e.Age);
正確的結(jié)果是:Jose Mond,Peter Claus 和 Mike Mockson。
在LINQ to SQL中,這種技術(shù)不起作用。
3. 使用日志中間件方法
讓我們回到錯誤尚未修復(fù)的初始狀態(tài),面對看似正確的查詢,我們都傻眼了。
調(diào)試查詢的另一個方法是使用以下擴(kuò)展方法:
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方法。它可以選擇打印通過此操作的所有項目和總數(shù)。
logName是每個輸出的前綴,可以輕松查看編寫它的查詢步驟。我喜歡將其命名為之后操作相同的名稱。
Fun<T,string>printMethod允許打印給定項目的任何內(nèi)容。在上面的示例中,我選擇使用?e=>e.Name打印員工的姓名,當(dāng)為?null時,除總數(shù)外,不會打印任何內(nèi)容。
為了優(yōu)化,此方法盡在調(diào)試模式下有效(?#if DEBUG)。在發(fā)布模式下,它什么都不做。
每個項目都按順序打印,無需等待操作結(jié)束,這是因為LINQ的?lazy?特性。以下是查看單個操作結(jié)果的提示:將整個輸出復(fù)制到?notepad++。然后使用Ctrl+Shift+F(Find)并查找日志前綴(例如?logWhere2)。在查找對話框,點擊Find All in Current Document。這將僅顯示與日志名稱前綴匹配的行。
查看輸出窗口,可以看到以下幾點:
源中包括“Jose Mond”,但?logWhere沒有,這是因為我們之前看到的區(qū)分大小寫的錯誤。
由于提前使用?Take方法,“Mike Mockson”從未在源中進(jìn)行評估。事實上,源的計數(shù)日志完全丟失,因為它永遠(yuǎn)不會到達(dá)集合的末尾。
對應(yīng) LINQ to SQL以及可能的其他LINQ程序,此技術(shù)存在問題。它將 IQueryable轉(zhuǎn)換為 IEnumerable,更改查詢并可能強(qiáng)制進(jìn)行早期評估。最好不要將它用于任何LINQ程序(如Entity Framework)。
4. 使用OzCode的LINQ功能
如果你需要有效工具調(diào)試LINQ,可以使用OzCode Visual Studio擴(kuò)展。
免責(zé)聲明:我目前是OzCode員工。然而,這是我個人博客,這篇文章只是我的專業(yè)推薦。
OzCode將可視化你的LINQ查詢,以準(zhǔn)確顯示每個項目的行為方式。首先,它將顯示每次操作后的項目數(shù):
然后,你可以點擊任何編號按鈕以查看項目以及它們在操作中的進(jìn)度。
我們可以看到“Jo Parker”在源中排名第4,在第一次 Where操作之后排名第3。它沒有通過第二次的 Where操作。它甚至沒有在最后兩次操作 OrderBy 和 Take中處理。
如果這還不夠,你可以按右上角的“l(fā)ambda”按鈕查看完整的LINQ分析。以下是它的樣子:
因此,在調(diào)試LINQ方面,你幾乎可以充滿希望和夢想。
總結(jié)
調(diào)試LINQ不是很直觀,但可以通過一些技術(shù)很好的完成。
我沒有提到LINQ查詢語法,因為他沒有被使用太多。只有技術(shù)#2 (lambda斷點)和技術(shù)#4 (OzCode)愛使用了查詢語法。
我希望你能使用本文的一些技巧,請繼續(xù)關(guān)注以后的帖子。
總結(jié)
以上是生活随笔為你收集整理的[译]如何在C#中调试LINQ查询的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 也读《人月神话》:没有银弹的软件工程
- 下一篇: c# char unsigned_dll