LINQ查询调试

22

我们最近一直在使用LINQ进行工作,主要是使用LINQ-to-Objects。不幸的是,有些查询可能会有点复杂,特别是当它们开始涉及多个序列的组合时。当你遇到像这样的查询时,很难准确地知道发生了什么:

IEnumerable<LongType> myCompanies =       relevantBusiness.Children_Companies
            .Select(ca => ca.PR_ContractItemId)
            .Distinct()
            .Select(id => new ContractedItem(id))
            .Select(ci => ci.PR_ContractPcrId)
            .Distinct()
            .Select(id => new ContractedProdCompReg(id))
            .Select(cpcr => cpcr.PR_CompanyId)
            .Distinct();

var currentNewItems = myCompanies 
                .Where(currentCompanyId => !currentLic.Children_Appointments.Select(app => app.PR_CompanyId).Any(item => item == currentCompanyId))
                .Select(currentId => new AppointmentStub(currentLic, currentId))
                .Where(currentStub=>!existingItems.Any(existing=>existing.IsMatch(currentStub)));


Items = existingItems.Union(newItems).ToList();

即使在调试时,有时也很难确定是谁在干什么以及何时发生。除了在序列上恣意地调用 "ToList" 以便更轻松地检查事情外,是否有人有关于如何调试“复杂”的LINQ的好建议?


这篇博客文章介绍了一些非常有前途的技术,用于调试LINQ to Objects。 - Joel Mueller
https://oz-code.com/ 是一个付费的编程工具网站。 - ihebiheb
6个回答

17

我知道我的回答有点晚,但我必须分享这个:

刚刚发现LinqPad,它非常棒(更不用说是免费的了)。
真不敢相信我写了那么长时间的Linq,居然不知道这个工具。

据我所知,这是O'Reilly的"C# 3.0 简明教程""C# 4.0 简明教程"的作者(们?)开发的。


15

像这样的查询似乎表明您在选择适当的数据结构或处理封装和任务分离方面工作不是很出色。我建议您查看并将其拆分。

总的来说,如果我想调试一个不明显正确的LINQ查询,我会将其拆分为子查询,并在调试器中逐个检查结果。


非常同意。使用LinqToObjects时,链中的每个步骤实际上都会给你一个对象来处理和检查。与LinqToSQL不同的是,表达式评估被延迟到最后,而LinqToObjects中的扩展方法就像其他方法一样立即评估。 - tvanfosson
1
LINQ to Object 查询也使用延迟评估。但是您可以在调试时对它们进行评估。 - Daniel Brückner
1
有没有一种方法可以让对象“立即评估”?例如,如果我有一个延迟的 IEnumerable<T>,在调试器中是否有一些“ .Go()”函数可以调用以检查其当前状态? - GWLlosa
关于对象结构,最好的描述词语是“我无法控制的遗留代码”。 - GWLlosa
GW: 在 Visual Studio 2008 调试器中,如果在范围内有 IEnumerable,则可以将鼠标悬停在引用上并转到菜单中的“结果”以评估枚举。或者,您也可以直接调用 ToArray()。 - mqp
LINQ可能是一种邀请恶性的开放方式——放弃关注点分离和封装,增加耦合等等。要抵制过度使用它的方式。 - dkretz

9
当我最近寻找答案时,发现一些有趣的线索,但没有一个连贯的叙述真正深入回答这个问题。所以我自己写了一篇文章并发表在Simple-Talk.com上(LINQ Secrets Revealed: Chaining and Debugging)。您可能需要注册才能阅读文章(该网站似乎在最近几天经历了一些变化),因此以下是文章的亮点:

(1)在LINQPad中:使用其非凡的Dump()方法。您可以将其注入到LINQ链的一个或多个点中,以以惊人的干净和清晰的方式查看您的数据。

(2)在Visual Studio中:在LINQ链的中间嵌入nop语句,以便设置断点。请注意,返回语句必须在它自己的行上才能在Visual Studio中设置断点。(感谢Eric White的博客文章Debugging LINQ Queries提供了这个提示。)

.Select(z =>
{return z;}
)

(3) 在Visual Studio中:注入我在文章中介绍的Dump()扩展方法以允许日志记录。我从Bart De Smet在他的信息性文章LINQ to Objects - Debugging中开始使用Watch()方法,并添加了一些标签和颜色来增强可视化效果,尽管它仍然无法与LINQPad的Dump输出相比。

(4) 最后,(是的,我很喜欢LINQPad的Dump方法!)使用Robert Ivanc的LINQPad Visualizer插件将LINQPad的可视化直接带入Visual Studio。这不是一个完美的解决方案(还没有支持VS2010,需要类可序列化,有一些渲染问题),但它非常有用。

2016.12.01 更新

刚刚在Simple-Talk.com上发布了上述文章的续集:LINQ Debugging and Visualization。本文全面介绍了OzCode扩展程序在Visual Studio 2015中提供的新LINQ调试功能。OzCode最终使LINQ调试变得简单而强大。(是的,我不是OzCode的员工 :-)。


了解的好技巧! - GWLlosa
1
现在可视化工具支持VS2010和不可序列化的类了! :) - Robert Ivanc

4

我不知道有内置工具可以使用。最好的方法是将查询分成多个子查询,调试时逐个评估这些子查询。一个好的第三方工具是LINQPad


3

我知道这是一个旧问题,但我找到了其他的解决方案,可能对某些人有用。当我调试LINQ查询时,我也遇到了同样的问题。有两种好的方法可以帮助您调试。

一种方法:将lambda表达式更改为主体表达式。这使您可以在其中放置断点。而且,当您右键单击它们时,您可以设置一些条件,因此它可以提供许多可能性。例如:

// Instead of this:
var names = new List<string> { "Anna", "Tom", "Jerry" };
var namesLength = names.Select(name => name.Length);

// Use this:
var names = new List<string> { "Anna", "Tom", "Jerry" };
var namesLength = names.Select(name =>
{
    return name.Length; // Set breakpoint here
});

另一种方法:在Visual Studio中使用快速监视(Quick Watch)。如何操作?在上面的示例中,将断点放置在您的LINQ查询处。当代码停止时,使用鼠标选中整个LINQ查询,右键单击并选择“快速监视...”即可。它会使用程序中实际数据生成任何您在其中放置的查询结果。它还支持智能感知。一个提示:通常在Quick Watch查询的末尾加上.ToList()可以看到结果。


2

马蹄果!

Resharper(我很喜欢)建议我更改这个

foreach (BomObservation initialObservation in initialObservations)
{
    if(initialObservation.IsValid() && !initialObservation.IsStationOnly)
        mappableObservations.Add(initialObservation);
}

到这里

initialObservations.Where(observation => observation.IsValid() && !observation.IsStationOnly).ToList();

是的,它很性感、时尚,但是在步进和调试时?你无法做到。我要回到foreach。

我也喜欢LinqPad,我认为Linq在“一环掌管所有”的方式上非常棒,但在这种情况下,我会失去某些东西。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接