IEnumerable在对其进行过滤时需要很长时间才能处理

web*_*ad3 3 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;
}
Run Code Online (Sandbox Code Playgroud)

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

我是这样从我的控制器调用这个:

svcGenerateConditions generateConditions = new svcGenerateConditions(db);
IEnumerable<AllConditionByCountry> AllConditionsByCountry;
AllConditionsByCountry = generateConditions.GenerateConditions(1);
Run Code Online (Sandbox Code Playgroud)

然后我循环:

foreach (var record in AllConditionsByCountry)
{
    ...
    ...
    ...
Run Code Online (Sandbox Code Playgroud)

这是我认为问题所在:

var rList = AllConditionsByCountry
           .Where(x => x.ConditionID == conditionID)
           .Select(x => x)
           .AsEnumerable();
Run Code Online (Sandbox Code Playgroud)

我正在根据我从上面的查询收集的数据做一个嵌套循环(利用我得到的原始数据AllConditionByCountry.我认为这是我的问题所在.当它对数据进行过滤时,它大大减缓了.

基本上这个过程写出了一堆文件(.json,.html)我首先使用ADO.Net测试了这个,并且运行所有这些记录花了大约4秒钟.使用EF(存储过程或LinqToSql)需要几分钟.

我应该对我正在使用的列表类型做什么,或者只是使用LinqToSql的价格?

我试图返回List<AllConditionByCountry>,IQueryable,IEnumerable从我的GenerateConditions方法.列表花了很长时间(类似于我现在看到的). 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();              
    ...
    ...
}
Run Code Online (Sandbox Code Playgroud)

Dan*_*try 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();              
  ...
  ...
}
Run Code Online (Sandbox Code Playgroud)

Linq-to-SQL的工作方式与Linq类似,因为它(松散地说)将函数附加到链,以便在枚举可枚举时执行 - 例如,

Enumerable.FromResult(1).Select(x => throw new Exception());
Run Code Online (Sandbox Code Playgroud)

这实际上并不会导致代码崩溃,因为枚举永远不会被迭代.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>();
Run Code Online (Sandbox Code Playgroud)

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

当你进入你的循环时,问题出现了.当您点击for循环时,表示您想要开始迭代迭代AllConditionsByCountry器.这会导致.NET关闭并执行初始查询,这需要时间.

当您调用AllConditionsByCountry.Where(x => x.ConditionID == conditionID)for循环时,您正在构建另一个实际上没有执行任何操作的迭代器.据推测,您实际上使用了rList该循环中的结果,但是,您实际上构建了针对数据库执行的N个查询(其中N是AllConditionsByCountry的大小).

这导致您有效地对数据库执行大约9501次查询的情况 - 1用于初始查询,然后对原始查询中的每个元素执行一次查询.与ADO.NET相比,大幅放缓是因为您可能比原来多了9500个查询.

理想情况下,您应该更改代码,以便对数据库执行一个且仅执行一个查询.你有几个选择:

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

    var conditions = AllConditionsByCountry.ToList(); foreach(条件中的var记录){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()然后搜索该列表来利用此优势- 这将导致比较在内存中完成,这是我在上面演示的内容