代码之家  ›  专栏  ›  技术社区  ›  Felix K.

在加入内存时,LINQ查询中的“where”位置重要吗?

  •  15
  • Felix K.  · 技术社区  · 6 年前

    情况: 假设我们正在执行一个连接两个内存中列表的LINQ查询(因此不涉及DBSET或SQL查询生成),并且该查询还具有 where 哪里 仅筛选原始集合中包含的特性( from

    在它执行 join ,无论我是否写 哪里 在会议之前或之后 ? 因此,它不必对以后不包括的元素执行联接。

    例如,我有一个 categories 我想加入一个 products 列表然而,我只是对 category 具有 ID 1.无论我是否写入,linq解释器是否在内部执行完全相同的操作:

    from category in categories
    join prod in products on category.ID equals prod.CategoryID
    where category.ID == 1 // <------ below join
    select new { Category = category.Name, Product = prod.Name };
    

    from category in categories
    where category.ID == 1 // <------ above join
    join prod in products on category.ID equals prod.CategoryID
    select new { Category = category.Name, Product = prod.Name };
    

    以前的研究: 我已经看到了 this question stated 他/她的问题只针对具有生成SQL的非内存案例。我对LINQ在内存中的两个列表上执行连接非常感兴趣。

    更新:这不是一个副本 "Order execution of chain linq query"

    更新2:虽然非常相似,但这也不是 "Is order of the predicate important when using LINQ?" 当我明确询问内存中的情况时,我看不到引用的问题明确地解决了这个问题。此外,这个问题有点老了,我实际上对.NET核心环境下的linq很感兴趣(2012年不存在),所以我更新了这个问题的标签以反映第二点。

    请注意: 有了这个问题,我的目标是linq查询解释器是否在后台以某种方式优化了这个查询,并希望获得一段文档或源代码的参考,说明linq是如何完成这项工作的。我是 对诸如“这不重要,因为两个查询的性能大致相同”之类的答案感兴趣。

    2 回复  |  直到 6 年前
        1
  •  9
  •   dymanoid    6 年前

    LINQ查询语法将编译为方法链。有关详细信息,请阅读例如。 in this question .

    categories
        .Join(
            products,
            category => category.ID,
            prod => prod.CategoryID,
            (category, prod) => new { category, prod })
        .Where(t => t.category.ID == 1)
        .Select(t => new { Category = t.category.Name, Product = t.prod.Name });
    

    categories
        .Where(category => category.ID == 1)
        .Join(
            products,
            category => category.ID,
            prod => prod.CategoryID,
            (category, prod) => new { Category = category.Name, Product = prod.Name });
    

    如您所见,第二个查询将导致更少的分配(注意第一个查询中只有一个匿名类型vs 2,并且注意在执行查询时将创建多少匿名类型的实例)。

    因此,第二个版本更可取。

        2
  •  3
  •   Derviş Kayımbaşıoğlu    6 年前

    对于内存中的列表(IEnumerables),不应用任何优化,并且对内存中的列表按链式顺序执行查询。

    我也试过了 result 先把它投给 IQueryable 然后应用过滤,但显然这个大表的施法时间非常长。

    我为这个案子做了一个快速测试。

    Console.WriteLine($"List Row Count = {list.Count()}"); 
    Console.WriteLine($"JoinList Row Count = {joinList.Count()}"); 
    
    var watch = Stopwatch.StartNew();
    var result = list.Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
       .Where(t => t.inner.Prop3 == "Prop13")
       .Select(t => new { t.inner.Prop4, t.lst.Prop2}); 
    result.Dump();
    watch.Stop();
    
    Console.WriteLine($"Result1 Elapsed = {watch.ElapsedTicks}");
    
    watch.Restart();
    var result2 = list
       .Where(t => t.Prop3 == "Prop13")
       .Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
       .Select(t => new { t.inner.Prop4, t.lst.Prop2});
    
    result2.Dump();
    watch.Stop();
    Console.WriteLine($"Result2 Elapsed = {watch.ElapsedTicks}"); 
    
    watch.Restart();
    var result3 = list.AsQueryable().Join(joinList, l => l.Prop3, i=> i.Prop3, (lst, inner) => new {lst, inner})
       .Where(t => t.inner.Prop3 == "Prop13")
       .Select(t => new { t.inner.Prop4, t.lst.Prop2}); 
    result3.Dump();
    watch.Stop();
    Console.WriteLine($"Result3 Elapsed = {watch.ElapsedTicks}"); 
    

    调查结果:

    List Count = 100
    JoinList Count = 10
    Result1 Elapsed = 27
    Result2 Elapsed = 17
    Result3 Elapsed = 591
    
    List Count = 1000
    JoinList Count = 10
    Result1 Elapsed = 20
    Result2 Elapsed = 12
    Result3 Elapsed = 586
    
    List Count = 100000
    JoinList Count = 10
    Result1 Elapsed = 603
    Result2 Elapsed = 19
    Result3 Elapsed = 1277
    
    List Count = 1000000
    JoinList Count = 10
    Result1 Elapsed = 1469
    Result2 Elapsed = 88
    Result3 Elapsed = 3219