此LINQ代码是否对原始数据执行多次查找?

时间:2012-12-11 15:44:26

标签: c# linq linq-to-objects

我们在使用LINQ的代码段中遇到了轻微的性能问题,并且提出了一个关于LINQ如何在查找方面工作的问题

我的问题是这个(请注意我已经更改了所有代码,因此这是代码的指示性示例,而不是真实场景):

鉴于

public class Person {
 int ID;
 string Name;
 DateTime Birthday; 
 int OrganisationID;
}

如果我有一个说100k Person对象的列表,然后是一个日期列表,比如1000,我运行了这段代码:

var personBirthdays = from Person p in personList
    where p.OrganisationID = 123
    select p.Birthday;

foreach (DateTime d in dateList)
{
    if (personBirthdays.Contains(d))
        Console.WriteLine(string.Format("Date: {0} has a Birthday", d.ToShortDateString()));
}

结果代码将是:

的迭代

100k(用于查找具有organisationID 123的用户需要完成的循环)
乘以
1000(列表中的日期数量)
乘以
x(具有针对该日期检查的organisationID 123的用户数量)

这是很多迭代!

如果我将personBirthdays的代码更改为:

List<DateTime> personBirthdays = 
        (from Person p in personList
        where p.OrganisationID = 123
        select p.Birthday).ToList();

这应该删除100k作为倍数,并且只执行一次?

所以你会有100k +(1000 * x)而不是(100k * 1000 * x)。

问题是,这似乎太容易了,我确信LINQ在某处做了一些聪明的事情,这应该意味着这不会发生。

如果没有人回答,我会进行一些测试并报告回来。

清晰度更新: 我们不考虑数据库查找,personList对象是内存列表对象。这就是所有LINQ到对象。

3 个答案:

答案 0 :(得分:8)

  

这应该将10k作为倍数移除,并且只执行一次?

这意味着,不是迭代personList 100k次,而是为每次迭代执行whereselect次操作迭代生成的List 100k次,并且whereselect操作只会在基础数据源上执行一次。

  

问题是,这似乎太容易了,我确信LINQ在某处做了一些聪明的事情,这应该意味着这不会发生。

不,你的第一个查询只是你不应该使用LINQ做的事情,你应该把查询的结果放在一个数据结构中,如果你打算多次迭代它们(这是什么你改变了。)

您可以使用适当的数据结构进一步改进此查询。搜索List效率相当低,因为它需要进行线性搜索。最好使用HashSet来存储查询结果。 HashSet在平均情况下具有O(1)搜索速度,而不是List的O(n)搜索时间。

var dates = new HashSet<DateTime>(from Person p in personList
                                  where p.OrganisationID = 123
                                  select p.Birthday);

foreach (DateTime d in dateList.Where(date => dates.Contains(date)))
{
    Console.WriteLine(string.Format("Date: {0} has a Birthday", d.ToShortDateString()));
}

答案 1 :(得分:3)

这是典型的select n+1问题,在您应用.ToList()后,您已部分解决了问题。下一步可能如下:您不断迭代personBirthdays列表,将其替换为HashSet,您可以更快地执行Contains(d)并删除重复项:

var personBirthdays = new HashSet<DateTime>((from Person p in personList
    where p.OrganisationID = 123
    select p.Birthday).ToArray());

答案 2 :(得分:0)

我假设您指的是LINQ-to-Objects,因为每个LINQ提供程序都有自己的实现(LINQ-to-SQL,LINQ-to-Entities,LINQ-to-XML,LINQ-to-anything) )。

personBirthdays为例,表达式的创建是为了迭代完整的结果集,因此LINQ不能自动将结果实现为数组或列表。

这些操作非常不同:

personBirthdays.Distinct()
personBirthdays.FirstOrDefault(b => b.Month == 7)
personBirthdays.Select(b => b.Year).Distinct()

作为一种技术的LINQ,“聪明”是允许构造表达式树并推迟执行。这可以防止 - 在上面的第三个例子中 - 100k迭代获得生日,然后另外100k选择年份,然后是最后的,昂贵的传递来组装不同的值。

LINQ消费者(你)必须拥有表达式的命运。如果您知道结果集将被多次迭代,那么您有责任将它们具体化为数组或列表。