【问题标题】:Why does an IF Statement effect the outcome of my LINQ Statement?为什么 IF 语句会影响我的 LINQ 语句的结果?
【发布时间】:2012-11-14 04:44:31
【问题描述】:

我最近一直在使用 LINQ,感谢一些 *ers 的帮助,我能够让这个语句正常工作:

var traceJob =
    from jobDefinition in service.JobDefinitions
    where jobDefinition.Id == traceGuid
    select jobDefinition;

if (traceJob != null && traceJob.Count() == 1)
{
 traceJob.First().RunNow();
 Console.WriteLine(traceJob.First().DisplayName + "  Last Run Time: " + traceJob.First().LastRunTime);
}

但是,我很困惑,因为使它工作的部分是if(traceJob.Count() ==1)。如果我删除该部分,则会收到 ObjectNullRef 错误,指出 traceJob 的枚举没有产生任何结果。

现在,据我所知,检查计数的 if 语句实际上不应该改变 Linq 语句的结果,对吧?谁能向我解释为什么我会看到这种行为?

【问题讨论】:

  • 旁注:如果您只是检查存在性,将traceJob.Count() == 1 的调用替换为traceJob.Any() 会执行得更好;因为如果找到一个项目,它会立即返回,而不是枚举所有项目来获取计数。

标签: c# linq sharepoint


【解决方案1】:

我不知道您的“服务”的实际实现,但通常 linq 查询实际上仅在请求时才会填充其结果。所以 Count() 确实改变了 traceJob 的状态,很可能填充了内部集合。看起来 First() 没有填充内部集合,或者即使正常情况下也没有正确执行。

【讨论】:

  • 好的,有道理。因此,通过调用 Count() 检查它正在填充结果,所以当我到达 RunNow 时,它有一个要运行的对象而不是空的?
  • 是的。但是 First() 应该做同样的事情,所以应该没有区别。为什么它不这样做我不知道。这可能取决于服务的实现方式。
  • 有趣。服务来自foreach(SPService service in centralAdmin.Farm.Services) 所以可能与 SharePoint 对象的行为方式有关。
  • 要检查它(但只是检查),您可以将 ToList() 语句添加到您选择的末尾。然后 traceJob 将是一个通常的列表,而 linq 操作将是从 linq 到对象的实现。那么在 First() 之前调用 Count() 应该没有区别。这是我的猜测:-)
【解决方案2】:

不,不应该。我的猜测是您遇到了枚举确实为空的情况,通过检查计数 > 0,First() 不会失败。

附带说明,Any() 在这里可能是更好的检查,因为(取决于存储库的底层存储)它可能比 Count() 更快:

if (traceJob != null && traceJob.Any())
{
 traceJob.First().RunNow();
 Console.WriteLine(traceJob.First().DisplayName + "  Last Run Time: " + traceJob.First().LastRunTime);
}

【讨论】:

  • 此代码将枚举service.JobDefinitions 4 次,每次在.Id == traceGuid 所在的第一项上停止。由于这是在循环中寻找单个项目,因此更好的答案是只执行一次循环。
【解决方案3】:
var traceJob =
    (from jobDefinition in service.JobDefinitions
    where jobDefinition.Id == traceGuid
    select jobDefinition).SingleOrDefault();

您可以使用 singleOrDefault 检查单个结果。它将返回与where 条件匹配的结果,如果未找到匹配项,则返回 null。如果为您的查询找到多个匹配项,则会引发异常。

这涵盖了您的tracejob == null 以及tracejob.count == 1 条件。

MSDN Article

【讨论】:

  • SingleOrDefault 不会抛出异常;如果不完全是一个元素,它会返回一个默认值(null 用于引用类型)。
  • 我认为您必须将 linq 语句括在括号中(例如下面的@NET3 的答案)或在集合变量上调用SingleOrDefault 扩展方法。您在 jobDefinition 上调用它,这只是枚举中的一项。
【解决方案4】:

关于https://*.com/a/1745716/1289709
我认为在这里使用FirstOrDefault 更合理。

当您使用 SingleOrDefault 时,您清楚地声明查询 应该最多产生一个结果。另一方面,当 使用 FirstOrDefault,查询可以返回任意数量的结果,但 你说你只想要第一个。

所以把你的代码改成这样:

var traceJob = (from jobDefinition in service.JobDefinitions
    where jobDefinition.Id == traceGuid
    select jobDefinition).FirstOrDefault();

    if (traceJob != null)
    {
       traceJob.RunNow();
       Console.WriteLine(traceJob.DisplayName + "  Last Run Time: " + traceJob.LastRunTime);
    }

【讨论】:

    【解决方案5】:

    我认为您的问题出在调用 linq 语句时的执行方式,而不是在声明它们的位置。

    所以你的陈述:

    var traceJob =
        from jobDefinition in service.JobDefinitions
        where jobDefinition.Id == traceGuid
        select jobDefinition;
    

    这在功能上大致相当于:

    IEnumerable<JobDefinition> GetJobDefinitions(YourService service, Guid traceGuid) 
    {
        foreach(var jobDefinition in service.JobDefinitions) 
            if(jobDefinition.Id == traceGuid) 
                yield return jobDefinition;
    }
    

    因此,当您调用 traceJob.Count() 时,您所做的相当于调用 GetJobDefinitions(service, traceGuid).Count() 并且每次调用 traceJob.First() 时,您都会再次进入循环。

    如果可以一遍又一遍地调用service.JobDefinitions,这可能不是问题。但是,如果结果随时间变化(例如,如果在您执行时添加了作业)或后续运行有不同的结果,您就会遇到问题。

    无论如何,最好只执行一次循环:

    var traceJobs =
        from jobDefinition in service.JobDefinitions
        where jobDefinition.Id == traceGuid
        select jobDefinition;
    
    // This is where the loop above actually executes - if it's empty it will return null
    var firstJob = traceJobs.FirstorDefault();
    
    if(firstJob != null)
    {
        firstJob.RunNow();
        Console.WriteLine(firstJob.DisplayName + "  Last Run Time: " + firstJob.LastRunTime);
    }
    

    或者,您可以通过将循环转换为列表或数组来强制执行循环:

    var traceJobsExecuted = traceJobs.ToList();
    

    【讨论】: