代码之家  ›  专栏  ›  技术社区  ›  Robert Rossney

Linq是否使用DataRelations优化联接?

  •  6
  • Robert Rossney  · 技术社区  · 16 年前

    我在任何地方都找不到这个问题的答案,在我开始用Reflector浏览生成的代码之前,我认为应该问一下:

    假设我对数据集中的数据表运行了以下LINQ查询:

    var list = 
       from pr in parentTable.AsEnumerable()
       join cr in childTable.AsEnumerable() on cr.Field<int>("ParentID") equals pr.Field<int>("ID")
       where pr.Field<string>("Value") == "foo"
       select cr;
    

    如果父表和使用所示关键字段的子表之间存在数据关系,Linq会使用它吗?也就是说,它会在父表中找到值为“foo”的行,然后调用 GetChildRows 要投影子行吗?

    或者这是我必须明确指定的?(如果是,我该怎么做?)

    3 回复  |  直到 16 年前
        1
  •  2
  •   Marc Gravell    16 年前

    我不这么认为。在这种情况下,linq-to对象可能只将两边作为常规的可枚举对象处理,并手动进行连接(而不查看 DataRelation )

        2
  •  3
  •   Robert Rossney    16 年前

    在Reflector中深入研究似乎并没有给出Linq使用DataRelations的任何迹象,但这段代码非常难以阅读。所以我写了一个小的性能测试,除非我错过了一些愚蠢的测试,否则结果是非常确定的:不,Linq不使用DataRelations和getChildRows()来投影加入的行。如果要在子行上迭代,则必须构造LINQ查询以显式调用getchildrows()。而且,这两种方法的性能都不如编写代码来遍历getchildrows()返回的数组。

    有点不幸,因为大型数据集上的性能差异非常显著,以至于用显式实现的代码替换Linq通常是值得的,而对于Linq,这通常是不正确的。

    我的测试代码如下。使用join的LINQ迭代的时间保持不变(在我的机器上大约580-590 ms),而不管数据关系是在它之前还是之后创建的。使用getchildrows()的linq迭代大约需要280毫秒,

    直接在getchildrows()上的迭代需要不到一毫秒的时间。这对我来说是相当令人惊讶的——足够让我假设在第一次运行测试时代码中有一个bug。(这就是为什么我每次都要写出计数-以确保编译器没有将循环优化为不存在的循环。)

    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            DataSet ds = new DataSet();
            DataTable t1 = new DataTable();
            t1.Columns.Add(new DataColumn
                               {
                                   ColumnName = "ID",
                                   DataType = typeof (int),
                                   AutoIncrement = true
                               });
            t1.PrimaryKey = new [] { t1.Columns["ID"]};
            ds.Tables.Add(t1);
    
            DataTable t2 = new DataTable();
            t2.Columns.Add(new DataColumn
            {
                ColumnName = "ID",
                DataType = typeof(int),
                AutoIncrement = true
            });
            t2.Columns.Add("ParentID", typeof(int));
            t2.PrimaryKey = new[] { t2.Columns["ID"] };
            ds.Tables.Add(t2);
    
            sw.Reset();
            sw.Start();
            PopulateTables(t1, t2);
            sw.Stop();
            Console.WriteLine("Populating tables took {0} ms.", sw.ElapsedMilliseconds);
            Console.WriteLine();
    
            var list1 = from r1 in t1.AsEnumerable()
                       join r2 in t2.AsEnumerable()
                           on r1.Field<int>("ID") equals r2.Field<int>("ParentID")
                       where r1.Field<int>("ID") == 1
                       select r2;
    
            sw.Reset();
            sw.Start();
            int count = 0;
            foreach (DataRow r in list1)
            {
                count += r.Field<int>("ID");
            }
            sw.Stop();
            Console.WriteLine("count = {0}.", count);
            Console.WriteLine("Completed LINQ iteration in {0} ms.", sw.ElapsedMilliseconds);
            Console.WriteLine();
    
            sw.Reset();
            sw.Start();
            ds.Relations.Add(new DataRelation("FK_t2_t1", t1.Columns["ID"], t2.Columns["ParentID"]));
            sw.Stop();
            Console.WriteLine("Creating DataRelation took {0} ms.", sw.ElapsedMilliseconds);
    
            sw.Reset();
            sw.Start();
            var list2 =
                from r1 in t1.AsEnumerable()
                from r2 in r1.GetChildRows("FK_t2_t1")
                where r1.Field<int>("ID") == 1
                select r2;
    
            count = 0;
            foreach (DataRow r in list2)
            {
                count += r.Field<int>("ID");
            }
            sw.Stop();
            Console.WriteLine("count = {0}.", count);
            Console.WriteLine("Completed LINQ iteration using nested query in {0} ms.", sw.ElapsedMilliseconds);
            Console.WriteLine();
    
            sw.Reset();
            sw.Start();
            DataRow parentRow = t1.Select("ID = 1")[0];
            count = 0;
            foreach (DataRow r in parentRow.GetChildRows("FK_t2_t1"))
            {
                count += r.Field<int>("ID");
            }
            sw.Stop();
            Console.WriteLine("count = {0}.", count);
            Console.WriteLine("Completed explicit iteration of child rows in {0} ms.", sw.ElapsedMilliseconds);
            Console.WriteLine();
    
            Console.ReadLine();
        }
    
        private static void PopulateTables(DataTable t1, DataTable t2)
        {
            for (int count1 = 0; count1 < 1000; count1++)
            {
                DataRow r1 = t1.NewRow();
                t1.Rows.Add(r1);
                for (int count2 = 0; count2 < 1000; count2++)
                {
                    DataRow r2 = t2.NewRow();
                    r2["ParentID"] = r1["ID"];
                    t2.Rows.Add(r2);
                }
            }
        }
    } 
    
        3
  •  1
  •   Andrew Theken Tan Li Hau    16 年前

    这是一个解释: http://msdn.microsoft.com/en-us/library/bb386969.aspx

    但在上面的示例中,您使用“join”语法向Linq介绍父子关系。如果可能的话,我建议您创建一个强类型的数据集,并且将为您处理所有关系和联接。