关于c#:为什么IF语句会影响我的LINQ语句的结果?

Why does an IF Statement effect the outcome of my LINQ Statement?

最近,我在Linq工作了一段时间,在一些StackOverflowers的帮助下,我能够使这个语句正常工作:

1
2
3
4
5
6
7
8
9
10
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语句的结果,对吗?有人能告诉我为什么我看到这种行为吗?


不,不应该。我猜你遇到了这样一种情况:枚举确实是空的,通过检查一个大于0的计数,First()不会失败。

作为补充说明,这里的EDOCX1[1]可能是一个更好的检查,因为(取决于存储库的底层存储)它可能比EDOCX1[2]更快:

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


1
2
3
4
var traceJob =
    (from jobDefinition in service.JobDefinitions
    where jobDefinition.Id == traceGuid
    select jobDefinition).SingleOrDefault();

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

这包括您的tracejob == nulltracejob.count == 1条件。

MSDN文章


我认为您的问题是如何在调用LINQ语句时执行它们,而不是从它们被声明的地方执行。

所以你的声明:

1
2
3
4
var traceJob =
    from jobDefinition in service.JobDefinitions
    where jobDefinition.Id == traceGuid
    select jobDefinition;

这在功能上大致相当于:

1
2
3
4
5
6
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,这可能不是问题。但是,如果结果随时间变化(例如,如果在执行时添加作业),或者如果后续运行的结果不同,则会出现问题。

在任何情况下,最好只执行一次循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

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

1
var traceJobsExecuted = traceJobs.ToList();

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

Whenever you use SingleOrDefault, you clearly state that the query
should result in at most a single result. On the other hand, when
FirstOrDefault is used, the query can return any amount of results but
you state that you only want the first one.

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

1
2
3
4
5
6
7
8
9
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);
    }

我不知道您的"服务"的实际实现,但通常LINQ查询实际上只在被请求时填充其结果。因此count()确实更改了traceJob的状态,最可能的情况是填充内部集合。它看起来像first()没有填充内部集合,或者没有正确地执行它,即使通常它应该这样做。