C# LINQ GroupBy非常慢

C# LINQ GroupBy非常慢,c#,linq,mono,C#,Linq,Mono,以下代码在100000行上运行需要5分钟。我觉得这很疯狂。我做错了什么 var query = from foo in fooStuff.AsEnumerable() group foo by foo.Field<Int64>("FooID") into g select new { FooID = g.Key,

以下代码在100000行上运行需要5分钟。我觉得这很疯狂。我做错了什么

        var query =
            from foo in fooStuff.AsEnumerable()
            group foo by foo.Field<Int64>("FooID") into g
            select new
            {
                    FooID = g.Key,
                    FooTier = g.Min(foo => foo.Field<int>("Tier"))
            };
var查询=
来自fooStuff.AsEnumerable()中的foo
按foo.Field(“FooID”)将foo分组为g
选择新的
{
FooID=g.键,
FooTier=g.Min(foo=>foo.Field(“层”))
};

注意:在Mono上。

当您调用AsEnumerable()时,您正在具体化所有实体,因此您的分组是在内存中完成的。尝试删除该部分,以便在数据库级别完成分组:

var query =
        from foo in fooStuff
        group foo by foo.FooID into g
        select new
        {
                FooID = g.Key,
                FooTier = g.Min(foo => foo.Tier)
        };

这不是一个直接的比较,也不是Mono上的比较,但我有一些代码,类似于我读入数据集中的6MB xml文件,它有30000行,需要0.5秒,所以我不认为是groupby本身造成了问题

为了进一步诊断,我建议

  • 测试将信息读入列表需要多长时间,即

    var fooList = fooStuff.AsEnumerable().ToList(); 
    
  • 测试如果您将查询更改为使用愚人而不是FootStuff,需要多长时间

  • 测试从select中删除FooTier=g.Min(foo=>foo.Tier)所需的时间

  • 将.Field反射与groupby和time每个部分分开,即首先将数据表中的信息读取到列表中,例如

    var list2 =
    (from foo in fooStuff.AsEnumerable()
    select new { 
        FooID = foo.Field<Int64>("FooID") 
        Tier  = foo.Field<int>("Tier")
    }).ToList();
    
如果这个查询很慢,则表明mono的GroupBy实现有问题。您可以通过使用类似这样的方法来验证这一点

    public static Dictionary<TKey, List<TSrc>> TestGroupBy<TSrc, TKey>
     (this IEnumerable<TSrc> src, Func<TSrc,TKey> groupFunc)
    {
        var dict= new Dictionary<TKey, List<TSrc>>();

        foreach (TSrc s in src)
        {
            TKey key = groupFunc(s);
            List<TSrc> list ;

            if (!dict.TryGetValue(key, out list))
            {
                list = new List<TSrc>();
                dict.Add(key, list);
            }       
            list.Add(s);        
            }

        return dict;
}

注意,这并不意味着取代groupby,也不处理空键,但应该足以确定它们是否与groupby有关(假设mono的Dictionary和List实现正常)。

之后我使用query.CopyToDataTable()。什么是FootStuff?是延期查询吗?如果在这行代码之前执行fooStuff.ToList(),该ToList是否需要5分钟?在
.Field
中有什么内容?对于足够慢的实现,5分钟是不难相信的(特别是如果它包含任何反射),您是这里唯一可以在代码上运行探查器的人。您的配置文件运行表明什么是瓶颈?如果(如注释中所述)fooStuff是一个dataTable,这不意味着您需要AsEnumerable()?DataTables需要AsEnumerable()。我们不想访问数据库来执行此操作。此外,为什么“在数据库级别”这样做会提高速度?内存应该更快,事实上,这就是问题所在。如果fooStuff是一个SQL表,阿方索的答案将适用,因为使用AsEnumerable()可以大大降低速度,就像它从fooStuff执行select*返回整个表一样,而没有它,生成的SQL将类似于select fooid,Min(层)作为FoodStuff组的footier,由fooid完成所有这些。还有5分钟。你是说每一个常规动作,包括上一个常规动作的两个部分,都需要相同的时间吗?只是分组需要5分钟。这看起来像是一个单一问题。我已经修改了我的答案,提供了一种方法,可以通过避免使用groupby来证实这一点
    public static Dictionary<TKey, List<TSrc>> TestGroupBy<TSrc, TKey>
     (this IEnumerable<TSrc> src, Func<TSrc,TKey> groupFunc)
    {
        var dict= new Dictionary<TKey, List<TSrc>>();

        foreach (TSrc s in src)
        {
            TKey key = groupFunc(s);
            List<TSrc> list ;

            if (!dict.TryGetValue(key, out list))
            {
                list = new List<TSrc>();
                dict.Add(key, list);
            }       
            list.Add(s);        
            }

        return dict;
}
  var results = list2.TestGroupBy(r=>r.FooID)
      .Select(r=>  new { FooID = r.Key, FooTier = r.Value.Min(r1=>r1.Tier)} );