2009-08-26 6 views
12

Considérer l'exigence à modifier un membre de données sur une ou plusieurs propriétés d'un objet qui est 5 ou 6 niveaux de profondeur.Remplacement de foreach imbriqué avec LINQ; modifier et mettre à jour une propriété au sein de

Il existe des sous-collections qui doivent être itérées pour accéder à la propriété nécessitant une inspection & modification.

Ici, nous appelons une méthode qui nettoie l'adresse d'un employé. Étant donné que nous changeons les données dans les boucles, l'implémentation actuelle a besoin d'une boucle for pour empêcher l'exception:

ne peut pas affecter à « somevariable » parce qu'il est un « foreach itération variable »

Voilà l'algorithme actuel (obfusqué) avec foreach imbriqué et un for.

foreach (var emp in company.internalData.Emps) 
{ 
    foreach (var addr in emp.privateData.Addresses) 
    { 
     int numberAddresses = addr.Items.Length; 

     for (int i = 0; i < numberAddresses; i++) 
     { 
      //transform this street address via a static method 
      if (addr.Items[i].Type =="StreetAddress") 
       addr.Items[i].Text = CleanStreetAddressLine(addr.Items[i].Text); 
     } 
    } 
} 

Question: Peut-être cet algorithme réimplémentées LINQ? L'exigence est que la collection d'origine ait ses données modifiées par cet appel de méthode statique.

Mise à jour: Je pensais/me penchais vers une solution de type jQuery/selector. Je n'ai pas spécifiquement formulé cette question de cette façon. Je me rends compte que je dépassais cette idée (pas d'effets secondaires). Merci à tout le monde! S'il existe un moyen de réaliser un sélecteur jQuery, voyons!

Répondre

11

LINQ ne vise pas à modifier des ensembles d'objets. Vous ne vous attendez pas à une instruction SQL SELECT pour modifier les valeurs des lignes sélectionnées, n'est-ce pas? Il est utile de se souvenir de ce que LINQ signifie - Language Integrated Natural Query. Modifier des objets dans une requête linq est, à mon humble avis, un anti-pattern.

La réponse de Stan R. serait une meilleure solution en utilisant une boucle foreach, je pense.

+0

merci. Les vôtres et les réponses de Merhdad étaient fonctionnellement identiques, mais j'ai vu le vôtre en premier :) Je pensais qu'il serait inutile de retaper ce code, mais je pensais qu'il était important de faire le point sur les effets secondaires –

+0

LINQ n'est pas un SQL Server, bien que la mise en œuvre pour SQL diffère de celui à la. NET - Vous pouvez toujours changer les propriétés avec les deux ensembles (LINQ à SQL) et juste C# LINQ. – ppumkin

18
foreach(var item in company.internalData.Emps 
         .SelectMany(emp => emp.privateData.Addresses) 
         .SelectMany(addr => addr.Items) 
         .Where(addr => addr.Type == "StreetAddress")) 
    item.Text = CleanStreetAddressLine(item.Text); 
+0

va SelectMany améliorer les performances sur foreach imbriquées? – ManirajSS

+0

LINQ Performance est négligeable à quel point cette réponse est belle. (mais non ... LINQ est sacrément rapide) – ppumkin

1

LINQ n'offre pas la possibilité d'effets secondaires. Cependant, vous pouvez faire:

company.internalData.Emps.SelectMany(emp => emp.Addresses).SelectMany(addr => Addr.Items).ToList().ForEach(/*either make an anonymous method or refactor your side effect code out to a method on its own*/); 
12
var dirtyAddresses = company.internalData.Emps.SelectMany(x => x.privateData.Addresses) 
               .SelectMany(y => y.Items) 
               .Where(z => z.Type == "StreetAddress"); 

    foreach(var addr in dirtyAddresses) 
    addr.Text = CleanStreetAddressLine(addr.Text); 
0

Vous pouvez le faire, mais vous ne le voulez pas vraiment. Plusieurs blogueurs ont parlé de la nature fonctionnelle de Linq, et si vous regardez toutes les méthodes Linq fournies par MS, vous constaterez qu'elles ne produisent pas d'effets secondaires. Ils produisent des valeurs de retour, mais ils ne changent rien d'autre. Recherchez les arguments sur une méthode Linq ForEach, et vous obtiendrez une bonne explication de ce concept.

Avec cela à l'esprit, ce que vous voulez probaly est quelque chose comme ceci:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
      addr => addr.Items 
    ) 
); 
foreach (var item in addressItems) 
{ 
    ... 
} 

Cependant, si vous ne voulez faire exactement ce que vous avez demandé, alors c'est la direction que vous aurez besoin d'aller:

var addressItems = company.internalData.Emps.SelectMany(
    emp => emp.privateData.Addresses.SelectMany(
      addr => addr.Items.Select(item => 
      { 
       // Do the stuff 
       return item; 
      }) 
    ) 
); 
10

I don't like mixing "query comprehension" syntax and dotted-method-call syntax dans la même déclaration.

J'aime l'idée de séparer la requête de l'action . Ceux-ci sont sémantiquement distincts, donc les séparer dans le code a souvent un sens.

var addrItemQuery = from emp in company.internalData.Emps 
        from addr in emp.privateData.Addresses 
        from addrItem in addr.Items 
        where addrItem.Type == "StreetAddress" 
        select addrItem; 

foreach (var addrItem in addrItemQuery) 
{ 
    addrItem.Text = CleanStreetAddressLine(addrItem.Text); 
} 

Quelques notes de style sur votre code; ceux-ci sont personnelles, donc je vous peuvent ne pas convenir:

  • En général, j'évitez les abréviations (Emps, emp, addr)
  • noms sont inconsistantes plus confus (addr contre Addresses): choisir un et le bâton avec
  • Le mot "nombre" est ambigu. Il peut s'agir soit d'une identité ("numéro de prisonnier 378 s'il vous plaît avancez".) Soit d'un compte ("le nombre de moutons dans ce champ est de 12."). Puisque nous utilisons beaucoup les deux concepts dans le code, il est utile de le savoir. J'utilise souvent "index" pour le premier et "count" pour le second. Si le champ type est une chaîne, c'est une odeur de code. Si vous pouvez en faire un enum votre code sera probablement mieux.
+0

Merci pour vos commentaires Jay. Le code n'a pas été copié-collé du projet réel, et a été obscurci pour la consommation publique. –

+0

Je seconde ceci. Séparer la requête de la mise à jour permet d'obtenir une solution beaucoup plus propre et plus facile à maintenir. C'est beaucoup mieux que les autres solutions d'expression lambda "tout en un" énumérées ici. – SeeMoreGain

2

Dirty one-liner.

company.internalData.Emps.SelectMany(x => x.privateData.Addresses) 
    .SelectMany(x => x.Items) 
    .Where(x => x.Type == "StreetAddress") 
    .Select(x => { x.Text = CleanStreetAddressLine(x.Text); return x; }); 
0

Pour mettre à jour le résultat en utilisant LINQ boucle foreach, je crée d'abord variable list locale puis effectuer la mise à jour en utilisant foreach boucle. Les valeurs sont mises à jour de cette façon. Lire la suite ici:

How to update value of LINQ results using FOREACH loop

Questions connexes