簡體   English   中英

IEnumerable在對其進行過濾時需要很長時間才能處理

[英]IEnumerable takes too long to process when filtering on it

我有一種感覺我知道這種行為的原因是什么,但我不知道解決它的最佳方法是什么。

我已經構建了一個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的價格?

我試圖從我的GenerateConditions方法返回List<AllConditionByCountry>IQueryableIEnumerable 列表花了很長時間(類似於我現在看到的)。 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();              
    ...
    ...
}

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循環時,表示你想要開始迭代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 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()強制迭代然后搜索該列表來使用此優勢 - 這將導致比較在內存中完成,這是我在上面演示的內容

//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;
}

關於這部分:

foreach (var record in AllConditionsByCountry) // you can use AllConditionsByCountry.ForEach(record=>{...});
{
  ...
  //AllConditionsByCountry will not query db again, because it's a list, no long a query
  var rList = AllConditionsByCountry.Where(x => x.ConditionID == conditionID);//.Select(x => x).AsEnumerable(); //no necessary to use AsXXX if compilation do not require it.
  ...
}

順便說一句,

  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. 我們沒有看到您的完整查詢,但通常情況下,您應該改進的部分是正確的,特別是當您有許多條件要過濾,加入和分組時...一點點改變可能會產生所有差異。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM