2009-08-26 163 views
12

Considere el requisito de cambiar un miembro de datos en una o más propiedades de un objeto que tiene 5 o 6 niveles de profundidad.Reemplazar foreach anidado con LINQ; modificar y actualizar una propiedad en el interior de

Hay subcolecciones que deben repetirse para llegar a la propiedad que necesita una modificación de inspección &.

Aquí estamos llamando a un método que limpia la dirección de la calle de un empleado. Dado que estamos cambiando los datos dentro de los bucles, la implementación actual necesita un bucle for para evitar la excepción:

no se puede asignar a "someVariable" porque es un 'foreach variable de iteración'

Aquí es el algoritmo actual (ofuscado) con foreach anidado y 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); 
     } 
    } 
} 

Pregunta: ¿Puede este algoritmo se Reimplementado usando LINQ? El requisito es que la colección original cambie sus datos mediante esa llamada a un método estático.

Actualización: Estaba pensando/inclinándome en la dirección de una solución tipo jQuery/selector. No dije específicamente esta pregunta de esa manera. Me di cuenta de que estaba exagerando con esa idea (sin efectos secundarios). ¡Gracias a todos! Si hay una forma de realizar un selector de jQuery, ¡vamos a verlo!

Respuesta

11

LINQ no tiene la intención de modificar conjuntos de objetos. No esperaría que una instrucción SELECT sql modifique los valores de las filas seleccionadas, ¿verdad? Es útil recordar lo que significa LINQ - Language Integrated Natural Query. La modificación de objetos dentro de una consulta linq es, en mi humilde opinión, un anti-patrón.

La respuesta de Stan R. sería una mejor solución usando un foreach loop, creo.

+0

gracias. la suya y respuestas de Merhdad eran funcionalmente idénticos, pero vi el suyo primero :) pensé que no tendría sentido volver a escribir ese código, pero sentí que era importante hacer la observación sobre los efectos secundarios –

+0

LINQ no es un servidor SQL Server, aunque la implementación para SQL difiere de eso en .NET one - Aún puede cambiar las propiedades con ambos conjuntos (LINQ to SQL) y solo con 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

se SelectMany mejorar el rendimiento sobre foreach anidados? – ManirajSS

+0

LINQ Performance es insignificante a lo hermosa que es esta respuesta. (pero no ... LINQ es bastante rápido) – ppumkin

1

LINQ no proporciona la opción de tener efectos secundarios. sin embargo, puede hacer:

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

Puede hacerlo, pero realmente no desea hacerlo. Varios bloggers han hablado sobre la naturaleza funcional de Linq, y si nos fijamos en todos los métodos Linq suministrados por MS, descubrirá que no producen efectos secundarios. Producen valores de retorno, pero no cambian nada más. Busque los argumentos sobre un método Linq ForEach y obtendrá una buena explicación de este concepto.

Con esto en mente, lo que probaly quiere decir algo como esto:

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

Sin embargo, si usted quiere hacer exactamente lo que usted pidió, entonces esta es la dirección en la que tendrá que ir:

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 en la misma declaración.

me gusta la idea de separar la consulta de la acción . Éstos son semánticamente distintos, por lo que separarlos en código a menudo tiene sentido.

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

Algunas notas sobre estilo de su código; Son de carácter personal, por lo que es posible que no estén de acuerdo:

  • En general, evito abreviaturas (Emps, emp, addr)
  • nombres incoherentes son más confuso (addr vs Addresses): elige uno y se adhieren con que
  • La palabra "número" es ambiguo. Puede ser una identidad ("número prisionero 378 por favor paso adelante.") O un recuento ("el número de ovejas en ese campo es 12."). Como usamos ambos conceptos en el código mucho, es importante aclarar esto. Uso a menudo uso "índice" para el primero y "cuenta" para el segundo.
  • Tener el campo type sea una cadena es un olor código. Si puede convertirlo en enum, es probable que su código sea mejor.
+0

Gracias por sus comentarios Jay. El código no se copió y pegó desde el proyecto real, y se ofuscó para el consumo público. –

+0

Lo segundo. Separar la consulta de la actualización permite una solución mucho más limpia y más fácil de mantener. Esto es mucho mejor que las otras soluciones de expresiones lambda "todo en uno" que se enumeran aquí. – SeeMoreGain

2

Dirty de una sola línea.

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

Para actualizar el resultado LINQ utilizando bucle foreach, que creen en primer lugar ‘lista’ variable local y luego realizar la actualización mediante FOREACH Loop. El valor se actualiza de esta manera. Lee más aquí:

How to update value of LINQ results using FOREACH loop

Cuestiones relacionadas