2012-12-18 74 views
2

我有這個簡單的類:迭代聰明的方法在列表中使用LINQ

public class JDEItemLotAvailability 
    { 
     public string Code { get; set; } 
     public int ShortCode { get; set; } 
     public string Description { get; set; } 
     public string PrimaryUnitCode { get; set; } 
     public string BranchPlant { get; set; } 
     public string Location { get; set; } 
     public string Lot { get; set; } 
     public int AvailableQuantity { get; set; } 
    } 

在我BLL這個DAL方法返回它們的列表:

var returnedLotList = _JDE8dal.GetLotAvailabilityAsList(_lot); 

我要做到以下幾點在返回列表中,我想用最「優雅」的LINQ方式來完成它。

我想檢查列表中是否有符合特定條件的記錄。我想到的是這樣的:

var query = 
     returnedLotList.Where(l => l.AvailableQuantity != 0 && l.BranchPlant == _mcu && l.Location == _locn) 
           .OrderByDescending(l => l.AvailableQuantity); 

但我想說的是,如果上述查詢不返回結果我想拿第一列表條目的其餘部分。

我該怎麼做?

+0

「如果上面的查詢沒有返回結果,我想採取列表的其餘部分的第一個」。這似乎是一個尷尬的要求。其他開發人員將繼續絆倒這一點。你確定這是一件好事嗎? – Steven

+0

這背後的邏輯是這樣的:如果在特定的倉庫和位置我沒有找到很多數量的物品,那麼我不在意將其中一個記錄作爲指示。我可以走得更遠,做出某種過程而不是第一次,但我想首先得到一般想法...... – e4rthdog

+0

你怎麼知道你得到的記錄是一個「未找到」的記錄而不是正確的記錄?最好返回一個空的列表,然後讓邏輯做一些事情,如果是的話。 – Bobson

回答

3

你可以使用DefaultIfEmpty

//your first query, unaltered 
var query = 
     returnedLotList.Where(l => l.AvailableQuantity != 0 && l.BranchPlant == _mcu && l.Location == _locn) 
           .OrderByDescending(l => l.AvailableQuantity); 

var query2 = query.DefaultIfEmpty(returnedLotList.Take(1)); 
+0

請注意,如果過濾列表爲空,則會多次迭代查詢。一次用於'DefaultIfEmpty',一次用於運行'Take(1)'。迭代(即使只有一個項目)涉及到數據庫的往返時,這是一個問題。 :-) – Steven

+0

@Steven實際上,如果'where'過濾了很大比例的物品,那麼它是可取的*。您首先執行一個在數據庫端進行過濾的查詢。如果它沒有返回任何項目,則對一個項目執行另一個查詢。在單個查詢中執行此操作的唯一方法是獲取所有項目,即使是那些不符合條件的項目。這是一個*顯着的*網絡開銷。另外,考慮到問題描述,常見的情況是文件管理器返回一些內容,所以常見情況只會迭代一次結果。 – Servy

+0

不要忘記,這個問題是關於LINQ to Objects的。 「Where」總是在.NET中執行,在這種情況下永遠不會在數據庫中執行。但是當我們談論表達式樹和「Queryable」的使用時,那麼這是最有效的。 – Steven

0

您可以擴展方法做這個:

public static IEnumerable<T> WhereOrFirstOfRest<T>(
    this IEnumerable<T> collection, Func<T, bool> predicate) 
{ 
    var filtered = collection.Where(predicate); 

    return filtered.Any() ? filtered : collection.Take(1); 
} 

下行這種擴展方法的是,它遍歷集合多次。當你處理流時(例如從數據庫),這可能是一個問題。一個更有效的方法是如下:

public static IEnumerable<T> WhereOrFirstOfRest<T>(
    this IEnumerable<T> collection, Func<T, bool> predicate) 
{ 
    // Materialize the complete collection. 
    collection = collection.ToArray(); 

    // Filter the collection. ToArray prevents calling the predicate 
    // twice for any item. 
    var filtered = collection.Where(predicate).ToArray(); 

    return filtered.Any() ? filtered : collection.Take(1); 
} 

雖然這樣可以節省您從任何可能的額外調用數據庫,這將創建一個掩護下幾個新的陣列。因此,最有效的方法是對子級以下:

public static IEnumerable<T> WhereOrFirstOfRest<T>(
    this IEnumerable<T> collection, Func<T, bool> predicate) 
{ 
    T firstItem = default(T); 
    bool firstStored = false; 
    bool predicateReturnedItems = false; 

    foreach (var item in collection) 
    { 
     if (!firstStored) 
     { 
      firstItem = item; 
      firstStored = true; 
     } 

     if (predicate(item)) 
     { 
      yield return item; 
      predicateReturnedItems = true; 
     } 
    } 

    if (!predicateReturnedItems && !first) 
    { 
     yield return firstItem; 
    } 
} 
+0

請注意,如果該查詢非空,它會多次迭代查詢。一次用於「任何」,一次用於實際結果。迭代(即使只有一個項目)涉及到數據庫的往返時,這是一個問題。 – Servy

+0

@Servy:這是絕對正確的。在閱讀您的評論之前,我已經在撰寫改進版。我們在同一頁面上:-) – Steven

+0

你正在努力做到一次。正如我的答案所顯示的那樣,DefaultIfEmpty已經爲你做了大部分工作,你主要是重新實現它,並且在'Where'中混合,而不是單獨調用兩個現有的函數。如果這實際上足以保證使它成爲自己的功能(我懷疑),您仍然可以重新使用這些方法。整個實現可以是'collection.Where(predicate).DefaultIfEmpty(collection.Take(1));'給定定義。 – Servy

0

我不知道我的理解,但也許是這樣的:

var firstMatch = returnedLotList.FirstOrDefault(l => l.AvailableQuantity != 0 && 
                l.BranchPlant == _mcu && 
                l.Location == _locn); 
if (firstMatch != null) 
    return firstMatch; 
int max = returnedLotList.Max(l => l.AvailableQuantity); 
return returnedLotList.First(l => l.AvailableQuantity == max); 
  • 如果不匹配,FirstOrDefault將返回null
  • I thi對於將發生的事情更清晰地分解查詢是個好主意。