linq结果转换object_[译]如何在C#中调试LINQ查询

aef4c3fe1f49c36c9be4f32ac3dfea6f.pngLINQ是我在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 Claus40“Male”61000
Jose Mond35"male"62000
Helen Gant38"Female"38000
Jo Parker42"Male"52000
Alex Mueller22"Male"39000
Abbi Black53"female"56000
Mike Mockson51"Male"82000

当运行此查询时,我得到的结果为:PeterClaus,61000,40

这似乎不对…… 应改有3名员工的。而平均工资约为56400,因此结果中应包括薪水为62000的“Jose Mond”和薪水为82000的“Mike Mockson”。

所以,我的LINQ查询中有一个错我,该怎么办呢?好吧,我可以盯着代码,直到我弄明白,这甚至可能适用于这种特殊情况。或者,我可以以某种方式调试它。让我们看看如何调试它。

1. 在快速监视中评估查询的各个部分

你可以做的最简单的事情之一就是在快速监视中分析各个查询。你可以从第一个操作开始,然后继续第一个和第二个操作,以此类推。

这里有一个例子:

36a94a2a7040e6539ed864f4a9a4f4bd.png

你可以使用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后缀手动粘贴表达式。

fa84b5197a431bb9e71de4c90c1ea202.png

2. 将断点放入lambda表达式中

另一个调试LINQ的好方法是在lambda表达式中放置一个断点。这允许评估单个项目。对应大型集合,你可以将其与条件断点功能结合使用。在我们的例子中,我们发现“Mike Mockson”不是第一个Where操作结果的一部分。你可以在 .Where(e=>e.Gender=="Male")lambda表达式中放置条件断点,条件为:e.Name=="Mike Mockson

4618353fd3d626592d5c450fddcc1677.png

运行查询后,我们将看到:

83332e1700308cd7c07cbf0aeaadf0c7.png

只打印了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 MondPeter ClausMike 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);

输出为:

15edef47e28566731f254b42b45f146e.png

说明和解释:

  • 在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查询,以准确显示每个项目的行为方式。首先,它将显示每次操作后的项目数:

2f93f798ff499baf92f1c876c2d4aec3.png

然后,你可以点击任何编号按钮以查看项目以及它们在操作中的进度。

a9b4de33a34df93e3db31131720e2996.png

我们可以看到“Jo Parker”在源中排名第4,在第一次 Where操作之后排名第3。它没有通过第二次的 Where操作。它甚至没有在最后两次操作 OrderByTake中处理。

如果这还不够,你可以按右上角的“lambda”按钮查看完整的LINQ分析。以下是它的样子:

5567b9d7d7855d5055420535944c208a.png

因此,在调试LINQ方面,你几乎可以充满希望和梦想。

总结

调试LINQ不是很直观,但可以通过一些技术很好的完成。

我没有提到LINQ查询语法,因为他没有被使用太多。只有技术#2 (lambda断点)和技术#4 (OzCode)爱使用了查询语法。

我希望你能使用本文的一些技巧,请继续关注以后的帖子。

原文


180250b1d0b546d57993effc95bb092a.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值