IEnumerable对其进行过滤时处理时间过长

时间:2018-07-18 18:04:53

标签: c# linq

我有一种感觉,知道这种行为的原因是什么,但我不知道解决该问题的最佳方法是什么。

我建立了一个LinqToSQL查询:

public IEnumerable<AllConditionByCountry> GenerateConditions(int paramCountryId)
{
    var AllConditionsByCountry =
            (from cd in db.tblConditionDescriptions...
             join...
             join...
             select new AllConditionByCountry
             {
                 CountryID = cd.CountryID,
                 ConditionDescription = cd.ConditionDescription,
                 ConditionID = cd.ConditionID,
             ...
             ...
            }).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();

    return AllConditionsByCountry;
}

此查询返回大约9500多行数据。

我从控制器中这样调用:

svcGenerateConditions generateConditions = new svcGenerateConditions(db);
IEnumerable<AllConditionByCountry> AllConditionsByCountry;
AllConditionsByCountry = generateConditions.GenerateConditions(1);

然后我要遍历:

foreach (var record in AllConditionsByCountry)
{
    ...
    ...
    ...

我认为这是问题所在:

var rList = AllConditionsByCountry
           .Where(x => x.ConditionID == conditionID)
           .Select(x => x)
           .AsEnumerable();

我正在基于从上述查询中收集的数据进行嵌套循环(利用从AllConditionByCountry获得的原始数据。我认为这就是我的问题所在。对数据进行过滤时,它的速度大大降低。

基本上,此过程会写出一堆文件(.json,.html) 首先,我仅使用ADO.Net对其进行了测试,要遍历所有这些记录大约需要4秒钟。使用EF(存储过程或LinqToSql)需要花费几分钟。

我要使用的列表类型是否应该做任何事情,还是使用LinqToSql的代价?

我尝试从我的List<AllConditionByCountry>方法返回IQueryableIEnumerableGenerateConditions。 List花费了很长时间(类似于我现在所看到的)。 IQueryable尝试做第二个过滤器时出错(查询结果不能被多次枚举)。

我已经在LinqPad中运行了相同的Linq语句,它在不到一秒钟的时间内返回。

我很高兴添加任何其他信息。

请让我知道。

编辑:

foreach (var record in AllConditionsByCountry)
{
    ...
    ...
    ...
    var rList = AllConditionsByCountry
               .Where(x => x.ConditionID == conditionID)
               .Select(x => x)
               .AsEnumerable();                        
    conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
    id = conditionDescriptionTypeID + "_" + count.ToString();              
    ...
    ...
}

2 个答案:

答案 0 :(得分:6)

TL; DR:您正在对数据库进行9895个查询,而不是一个。您需要重写查询,以便仅执行一个查询。请查看IEnumerable的工作方式,以获取执行此操作的一些提示。

是的,for循环是您的问题。

foreach (var record in AllConditionsByCountry)
{
  ...
  ...
  ...
  var rList = AllConditionsByCountry.Where(x => x.ConditionID == conditionID).Select(x => x).AsEnumerable();                        
  conditionDescriptionTypeID = item.ConditionDescriptionTypeId;
  id = conditionDescriptionTypeID + "_" + count.ToString();              
  ...
  ...
}

Linq-to-SQL与Linq的工作方式类似,因为它(宽松地讲)将函数追加到要在枚举可迭代时执行的链上,例如,

Enumerable.FromResult(1).Select(x => throw new Exception());

这实际上不会导致代码崩溃,因为从不迭代可枚举。 Linq-to-SQL的运行原理类似。因此,当您定义此代码时:

var AllConditionsByCountry =
        (from cd in db.tblConditionDescriptions...
         join...
         join...
         select new AllConditionByCountry
         {
             CountryID = cd.CountryID,
             ConditionDescription = cd.ConditionDescription,
             ConditionID = cd.ConditionID,
         ...
         ...
        }).OrderBy(x => x.CountryID).AsEnumerable<AllConditionByCountry>();

您没有对数据库执行任何操作,您只是在指示C#建立一个在迭代时执行此操作的查询。这就是为什么只声明此查询很快。

当您进入for循环时,您的问题就来了。遇到for循环时,表示您想开始迭代AllConditionsByCountry迭代器。这将导致.NET关闭并执行初始查询,这需要时间。

在for循环中调用AllConditionsByCountry.Where(x => x.ConditionID == conditionID)时,您正在构造另一个实际上不做任何事情的迭代器。大概您实际上在该循环中使用了rList的结果,但是,您实际上是在构造要针对数据库执行的N个查询(其中N是AllConditionsByCountry的大小)。

这导致一种情况,在该情况下,您将有效地对数据库执行大约9501个查询-初始查询为1个,然后对原始查询中的每个元素执行一个查询。与ADO.NET相比,速度急剧下降是因为您可能比原来多进行了9500次查询。

理想情况下,您应该更改代码,以便针对数据库执行一个查询,只有一个查询。您有两种选择:

  • 重写Linq-to-SQL查询,以使所有工作都由SQL数据库完成
  • 重写Linq-to-SQL查询,使其看起来像这样

    var条件= AllConditionsByCountry.ToList(); foreach(条件记录) {     var rList = Conditions.Where(....); }

请注意,在该示例中,我搜索的是conditions而不是AllConditionsByCountry-.ToList()将返回已经被迭代的列表,因此您无需再创建任何数据库查询。这仍然会很慢(因为您正在对9500条记录进行O(N ^ 2)条记录),但是由于它全部在内存中完成,因此它仍然比创建9500条查询要快。 >

  • 如果您对原始SQL比对Linq-to-SQL更满意,只需在ADO.NET中重写查询。这没什么问题。

我想我应该指出导致IEnumerable迭代的方法,而不是导致迭代的方法。

任何名为As*(例如AsEnumerable<T>())的方法都不会 引起可枚举的迭代。从本质上讲,这是一种从一种类型转换为另一种类型的方法。

任何名为To*(例如ToList<T>())的方法都将导致枚举被迭代。如果使用Linq-to-SQL,它还将执行数据库查询。任何还会导致您从可枚举中获取值的方法也会引起迭代。您可以通过创建查询并使用ToList()强制迭代,然后搜索该列表来发挥自己的优势,这将导致比较在内存中完成,这就是我上面演示的内容

答案 1 :(得分:1)

$(By.xpath("//div[contains(text(), 'My Tasks')]"))

关于这部分:

//Firstly: IEnumerable<> should be List<>, because you need to massage result later
public IEnumerable<AllConditionByCountry> GenerateConditions(int paramCountryId)
{
    var AllConditionsByCountry =
            (from cd in db.tblConditionDescriptions...
             join...
             join...
             select new AllConditionByCountry
             {
                 CountryID = cd.CountryID,
                 ConditionDescription = cd.ConditionDescription,
                 ConditionID = cd.ConditionID,
             ...
             ...
            })

            .OrderBy(x => x.CountryID)
            .ToList() //return a list, so only 1 query is executed
            //.AsEnumerable<AllConditionByCountry>();//it's useless code, anyway.

    return AllConditionsByCountry;
}

顺便说一句,

  1. 您应该对结果进行分页,没有页面将需要100多个结果。 10K的回报本身就是问题。

    GenerateConditions(int paramCountryId,int page = 0,int pagesize = 50)

  2. 很奇怪,您必须使用子查询,通常这意味着GenerateConditions没有返回所需的数据结构,您应该对其进行更改以提供正确的数据,而不再需要子查询

  3. 使用编译后的查询来改进更多内容:https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/compiled-queries-linq-to-entities
  4. 我们看不到完整的查询,但通常情况下,这是您应该改进的部分,特别是当您有很多条件要进行过滤,加入和分组时...稍作更改可能会带来不同。