慢的LINQ查询用于.ToArray()

5

我正在使用下面的查询

foreach (var callDetailsForNode_ReArrange in callDetailsForNodes_ReArrange)
{
    var test = from r1 in dtRowForNode.AsEnumerable()
               join r2 in dtFileRowForNode.AsEnumerable()
               on r1.Field<int>("Lng_Upload_Id") equals r2.Field<int>("Lng_Upload_Id")
               where ((r1.Field<string>("Txt_Called_Number") == callDetailsForNode_ReArrange.caller2.ToString()) || r1.Field<string>("Txt_Calling_Number") == callDetailsForNode_ReArrange.caller2.ToString())
               select r2.Field<string>("Txt_File_Name");

    var d = test.Distinct();
}

到目前为止,这个查询运行得非常快。但是当我添加了

时:

string[] str =d.ToArray();
strFileName = string.Join(",", str);

运行需要差不多4-5秒钟。是什么原因让添加.ToArray()这么慢?


1
阅读这个问题的第一个答案。 - gdoron
1
.Distinct() 不会运行查询吗? - Rajeev Kumar
1
不是的。它只是将您只想要每个元素一次的事实添加到可查询对象中。 - George Duckett
请记住,Distinct() 后面可能还有更多语句,这些语句都可能被翻译成实际的 SQL(例如)。 - Jacek Gorgoń
3个回答

15

到这里,查询还没有实际执行。它只是建立了一个表示待处理查询的延迟执行模型。直到你在迭代器上调用MoveNext()(例如通过foreach 或者在你的情况下通过.ToArray()),它才开始迭代执行。

所以:它需要时间,因为它正在执行工作

请考虑:

static IEnumerable<int> GetData()
{
    Console.WriteLine("a");
    yield return 0;
    Console.WriteLine("b");
    yield return 1;
    Console.WriteLine("c");
    yield return 2;
    Console.WriteLine("d");
}
static void Main()
{
    Console.WriteLine("start");
    var data = GetData();
    Console.WriteLine("got data");
    foreach (var item in data)
        Console.WriteLine(item);
    Console.WriteLine("end");
}

这会输出:

start
got data
a
0
b
1
c
2
d
end

请注意工作并非一次性完成——它既被推迟了(agot data之后)又被缓冲(我们不会立即得到a,...,d0,...,2)。


相关内容:这大致是Distinct()的工作方式,来自评论:

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source) {
    var seen = new HashSet<T>();
    foreach(var item in source) {
        if(seen.Add(item)) yield return item;
    }
}

并新增了一个Join操作:

public static string Join(this IEnumerable<string> source, string separator) {
    using(var iter = source.GetEnumerator()) {
        if(!iter.MoveNext()) return "";
        var sb = new StringBuilder(iter.Current);
        while(iter.MoveNext())
            sb.Append(separator).Append(iter.Current);
        return sb.ToString();
    }
}

并使用:

string s = d.Join(",");

2
我不知道即使在方法 Console.WriteLine("a"); 中的第一行也会在 foreach 之前打印出来。所以我本以为 a 会在 got data 之前出现。Linq 有时仍然令人惊讶。 - Tim Schmelter

12

由于查询在迭代之前什么也不做,而.ToArray()则执行了迭代。

需要注意的一点是,连接的右侧(在您的示例中为r2 in dtFileRowForNode.AsEnumerable())将在开始迭代查询时完全枚举,即使只访问结果的第一个元素-但直到这时才会进行。

因此,如果你执行以下操作:

d.First()

那么r2 in dtFileRowForNode.AsEnumerable()序列将被完全枚举(并缓存在内存中),但只会评估r1 in dtRowForNode.AsEnumerable()序列的第一个元素。

因此,如果连接中的一个序列比另一个序列大得多,则将大序列放在连接的左侧更加高效(从内存角度而言)。连接右侧的整个序列都将被缓存到内存中。

(我应该指出,这仅适用于Linq-to-objects。Linq-to-SQL将在数据库中运行这些查询,因此它处理缓冲。)


当您定义查询时,您正在执行的是定义查询的操作。您并不会运行它来获取结果。当您调用.ToArray()时,查询将被执行并进行所有必要的处理(这需要时间)。然后您就有了一组结果数组。 - theyetiman
有没有一种有效的方法可以高效地获取逗号分隔字符串中的不同值? - Rajeev Kumar
.Distinct() 不会运行查询吗? - Rajeev Kumar
@MarcGravell 我正在使用 .netFramework 3.5。它给我编译时错误。 - Rajeev Kumar
我在dtFileRowForNode中只有3条记录。 - Rajeev Kumar
显示剩余4条评论

2

您需要了解延迟评估的linq语句。除非您明确调用结果(例如在foreach中迭代,调用ToArrayToListSumFirst或其他方法来评估查询),否则查询不会完成。

因此,耗时较长的是您的查询,而不是ToArray调用。


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