【问题标题】:Slow LINQ query for .ToArray().ToArray() 的慢速 LINQ 查询
【发布时间】:2013-05-10 11:58:09
【问题描述】:

我正在使用以下查询

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() 这么慢?

【问题讨论】:

  • 阅读this question的第一个答案
  • .Distinct() 是否不运行查询??
  • 不。它只是添加了一个事实,即您只希望每个元素一次到可查询。
  • 请记住,在 Distinct() 之后可能还有更多语句,所有这些语句都可以转换为实际的 SQL(例如)。

标签: c# winforms performance linq


【解决方案1】:

这个查询很快就会运行到这里。

到这里为止,它实际上并没有任何事情,除了构建一个代表待处理查询的延迟执行模型。它不会开始迭代,直到您在迭代器上调用 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

请注意,这项工作不会同时发生 - 它既是延迟的(a 位于 got data 之后)和假脱机(我们没有得到 a,...,d,@ 987654330@,...2)。


相关:这就是Distinct() 的大致工作方式,来自 cmets:

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(",");

【讨论】:

  • 我不知道即使Console.WriteLine("a");方法的第一行也不会在foreach之前打印。所以我会认为a 出现在got data 之前。 Linq 有时仍然令人惊讶。
【解决方案2】:

因为在您迭代它之前,查询什么都不做,.ToArray() 会这样做。

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

如果你这样做了:

d.First()

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

因此,如果连接中的一个序列比另一个大得多,则将大序列放在连接的左侧会更有效(在内存方面)。连接右侧的整个序列将缓存在内存中。

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

【讨论】:

  • ^^ 他说的。当您定义查询时,您就是在这样做 - 定义查询。你不是为了得到结果而运行它。当您调用.ToArray() 时,查询将执行并进行所有必需的处理(这需要时间)。然后你有一个结果数组。
  • 有什么方法可以有效地获取逗号分隔字符串中的不同值
  • .Distinct() 是否不运行查询??
  • @RajeevKumar string s = string.Join(",", d); 怎么样?
  • @RajeevKumar 确实,.Distinct() 不运行查询;它设置了一个执行的迭代器块-我将实现Distinct()并将其添加到我的答案中以获取信息
【解决方案3】:

您需要阅读linq 语句的延迟评估。除非您明确调用结果,否则查询不会完成 - 例如迭代 foreach、调用 ToArrayToListSumFirst 或其他评估查询的方法之一。

所以是您的查询需要这么多时间才能完成,而不是ToArray 调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多