2012-12-18 2 views
2

j'ai cette classe simple:Itération façon intelligente dans une liste avec 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; } 
    } 

Cette méthode DAL dans mon BLL retourne une liste:

var returnedLotList = _JDE8dal.GetLotAvailabilityAsList(_lot); 

Je veux faire ce qui suit dans la liste retournée et je veux le faire de la manière la plus "élégante" LINQ.

Je veux vérifier s'il existe dans la liste un enregistrement qui correspond à des critères spécifiques. Je l'ai pensé à quelque chose comme ceci:

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

mais je veux dire que si la requête ci-dessus ne retourne pas que je veux prendre le premier du reste des entrées de la liste.

Comment puis-je faire cela?

+0

"si la requête ci-dessus ne renvoie pas les résultats je veux prendre le premier du reste de la liste". Cela semble être une exigence gênante. D'autres développeurs vont continuer à trébucher dessus. Es-tu sûr que c'est une bonne chose? – Steven

+0

la logique derrière ceci va comme: Si un lot n'est pas trouvé ayant la quantité dans l'entrepôt spécifique et l'endroit que je veux qu'il ait, alors je ne m'inquiète pas de m'apporter un des autres disques comme indication. Je pourrais aller plus loin et faire une sorte de processus au lieu d'obtenir le premier, mais je veux d'abord avoir l'idée générale ... – e4rthdog

+0

Comment saurez-vous que l'enregistrement que vous obtenez est un enregistrement "non trouvé" au lieu du bon ? Il vaut mieux retourner une liste vide, puis avoir une logique qui fait quelque chose si c'est le cas. – Bobson

Répondre

3

Vous pouvez simplement utiliser 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

Notez que ceci itére la requête plusieurs fois si la liste filtrée est vide. Une fois pour 'DefaultIfEmpty', et une fois pour' Take (1) '. Lorsque l'itération (même pour obtenir un seul élément) implique un aller-retour à la base de données, c'est un problème. :-) – Steven

+0

@Steven En fait, si le 'where' filtre un grand pourcentage des éléments c'est * souhaitable *. Vous effectuez d'abord une requête qui effectue le filtrage du côté de la base de données. S'il ne renvoie aucun élément, vous effectuez une autre requête pour un élément. La seule façon de le faire dans une seule requête est d'obtenir tous les éléments, même ceux qui ne répondent pas aux critères. C'est un surcoût réseau * significatif *. En outre, étant donné la description du problème, le cas courant est que le fichier renvoie quelque chose, de sorte que le cas commun n'illumine le résultat qu'une seule fois. – Servy

+0

N'oubliez pas que cette question concerne LINQ to Objects. Le 'Where' sera toujours exécuté dans .NET, jamais dans la base de données dans ce cas. Mais quand nous parlons d'arbres d'expression et de l'utilisation de 'Queryable', dans ce cas, vous avez raison de dire que ce serait le plus efficace. – Steven

0

Vous pouvez faire une méthode d'extension qui fait cela:

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

Inconvénient de cette méthode d'extension est qu'elle parcourt la collection à plusieurs reprises. Cela peut être un problème lorsque vous traitez des flux (à partir de la base de données par exemple). Une méthode plus efficace serait la suivante:

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

Bien que cela vous évite de tout possible des appels supplémentaires à la base de données, cela ne crée plusieurs nouveaux tableaux sous le couvercle. La façon la plus efficace whould donc les suivantes:

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

Notez que ceci itére la requête plusieurs fois si elle n'est pas vide. Une fois pour 'Any', et une fois pour les résultats réels. Lorsque l'itération (même pour obtenir un seul élément) implique un aller-retour à la base de données, c'est un problème. – Servy

+0

@Servy: C'est absolument vrai. J'écrivais déjà une version améliorée avant de lire votre commentaire. Nous sommes sur la même page :-) – Steven

+0

Vous essayez de faire beaucoup à la fois. Comme ma réponse le montre, DefaultIfEmpty fait déjà la plupart de tout cela pour vous, vous ne faites que le ré-implémenter et aussi mixer dans 'Where', au lieu de simplement appeler les deux fonctions existantes indépendamment. Si cela est effectivement fait assez pour justifier sa propre fonction (dont je doute), vous pouvez toujours réutiliser ces méthodes. L'implémentation entière pourrait simplement être 'collection.Where (predicate) .DefaultIfEmpty (collection.Take (1));' étant donné la définition telle que vous l'avez définie. – Servy

0

Je ne suis pas sûr que je comprends, mais peut-être quelque chose comme ceci:

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 renverra null si aucune correspondance
  • Je thi nk c'est une bonne idée de diviser les requêtes pour clarifier ce qui se passe.
Questions connexes