2011-03-04 17 views
5

Me encontré con un problema hoy y he estado perplejo por algún tiempo tratando de obtener los resultados que estoy buscando.Pregunta de consulta de LINQ anidada

Actualmente tengo una clase similar al siguiente:

public class InstanceInformation 
{ 
    public string PatientID {get; set;} 
    public string StudyID {get; set;} 
    public string SeriesID {get; set;} 
    public string InstanceID {get; set;} 
} 

Tengo un List<InstanceInformation> y yo estoy tratando de utilizar LINQ (o cualquier otro medio para generar caminos (para un directorio de archivos) basada en esta lista similares a los siguientes:

PatientID/StudyID/SeriesID/InstanceID 

Mi problema es que los datos no es estructurado actualmente, ya que viene en la forma antes mencionada (lista) y necesito una forma de agrupar todos los datos con los ss obstáculos que plantean:

  • Grupo InstanceIDs por SeriesID
  • Grupo SeriesIDs por StudyID
  • StudyIDs Grupo de PatientID

que actualmente tienen algo que se parece a esto:

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from studyGroups in 
       (from instance in patientGroups 
        group instance by instance.StudyID) 
        from seriesGroup in 
         (from instance in studyGroups 
         group instance by instance.SeriesID) 
          from instanceGroup in 
           (from instance in seriesGroup 
            group instance by instance.InstanceID) 
      group instanceGroup by patientGroups.Key; 

que solo agrupa todos mis ID de instancia mediante PatientID, y es bastante difícil eliminar todos los datos después de esta agrupación masiva para ver si las áreas intermedias (StudyID/SeriesID) se están perdiendo. Cualquier otro método para resolver este problema sería más que bienvenido.

Esto es principalmente sólo para agrupar los objetos - como que iba a necesitar para iterar a continuación, a través de ellos (el uso de un foreach)

+0

¿Esto ayudaría? var result = instances.GroupBy (i => new {i.InstanceID, i.SeriesID, i.StudyID, i.PatientID}) – WorldIsRound

+0

Nope - Gracias a HelloWorld, esto dio como resultado solo una lista de todos los elementos de mi lista . Creo que el problema que estoy teniendo es tratar de usar LINQ para construir relaciones. –

Respuesta

11

No tengo idea si la consulta que ha surgido es la consulta que realmente desea o necesita, pero suponiendo que lo sea, consideremos la cuestión de si la consulta re es una mejor manera de escribirlo.

El lugar que desea buscar es la sección 7.16.2.1 de la especificación C# 4, una porción del cual cito aquí para su conveniencia:


una expresión de consulta con una continuación

from ... into x ... 

se traduce en

from x in (from ...) ... 

¿Está claro? Vamos a echar un vistazo a un fragmento de la consulta que he marcado con estrellas:

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from studyGroups in 
       **** (from instance in patientGroups 
        group instance by instance.StudyID) **** 
        from seriesGroup in 
         (from instance in studyGroups 
         group instance by instance.SeriesID) 
          from instanceGroup in 
           (from instance in seriesGroup 
            group instance by instance.InstanceID) 
      group instanceGroup by patientGroups.Key; 

Aquí tenemos

from studyGroups in (from ...) ... 

la especificación dice que esto es equivalente a

from ... into studyGroups ... 

para que podamos reescribir su consulta como

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from instance in patientGroups 
      group instance by instance.StudyID into studyGroups 
      from seriesGroup in 
      **** (from instance in studyGroups 
        group instance by instance.SeriesID) **** 
         from instanceGroup in 
          (from instance in seriesGroup 
          group instance by instance.InstanceID) 
      group instanceGroup by patientGroups.Key; 

Hazlo de nuevo. Ahora tenemos

from seriesGroup in (from ...) ... 

y la especificación dice que esto es lo mismo que

from ... into seriesGroup ... 

por lo que volver a escribir así:

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from instance in patientGroups 
      group instance by instance.StudyID into studyGroups 
      from instance in studyGroups 
      group instance by instance.SeriesID into seriesGroup 
      from instanceGroup in 
       ****  (from instance in seriesGroup 
        group instance by instance.InstanceID) **** 
      group instanceGroup by patientGroups.Key; 

y otra vez!

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from instance in patientGroups 
      group instance by instance.StudyID into studyGroups 
      from instance in studyGroups 
      group instance by instance.SeriesID into seriesGroup 
      from instance in seriesGroup 
      group instance by instance.InstanceID into instanceGroup 
      group instanceGroup by patientGroups.Key; 

Que espero que usted esté de acuerdo es mucho más fácil de leer. Que podrían mejorar su legibilidad más cambiando el hecho de que "ejemplo" se utiliza una media docena de veces para significar cosas diferentes:

var groups = from instance in instances 
      group instance by instance.PatientID into patientGroups 
      from patientGroup in patientGroups 
      group patientGroup by instance.StudyID into studyGroups 
      from studyGroup in studyGroups 
      group studyGroup by studyGroup.SeriesID into seriesGroups 
      from seriesGroup in seriesGroups 
      group seriesGroup by seriesGroup.InstanceID into instanceGroup 
      group instanceGroup by patientGroups.Key; 

Si esto es en realidad la consulta que necesita para resolver su problema, no sé , pero al menos esta puede razonar sin voltearse tratando de seguir todo el anidamiento.

Esta técnica se llama "continuación de la consulta". Básicamente, la idea es que la continuación presente una nueva variable de rango sobre la consulta hasta el momento.

+1

Ya sabe, no siempre entiendo muy bien sus respuestas, pero siempre aprendo mucho de ellas. ¿Cuál será el tipo de datos de 'groups' en el último ejemplo? –

+1

Esta pregunta es ahora favorita gracias a su respuesta. Se ha bromeado antes, pero realmente deberías publicar estas respuestas en un libro :) (no es broma) –

+0

¿Es correcto que tu última consulta sea solo una serie de llamadas 'GroupBy' (que se pueden escribir directamente usando llamadas a métodos) ? –

2

creo que esto dará paso a lo que estás buscando:

public class InstanceInformation { 
    public string PatientID { get; set; } 
    public string StudyID { get; set; } 
    public string SeriesID { get; set; } 
    public string InstanceID { get; set; } 

    public override string ToString() { 
     return String.Format("Series = {0} Study = {1} Patient = {2}", SeriesID, StudyID, PatientID); 
    } 
} 

class Program { 
    static void Main(string[] args) { 
     List<InstanceInformation> infos = new List<InstanceInformation>() { 
      new InstanceInformation(){ SeriesID = "A", StudyID = "A1", PatientID = "P1" }, 
      new InstanceInformation(){ SeriesID = "A", StudyID = "A1", PatientID = "P1" }, 
      new InstanceInformation(){ SeriesID = "A", StudyID = "A1", PatientID = "P2" }, 
      new InstanceInformation(){ SeriesID = "A", StudyID = "A2", PatientID = "P1" }, 
      new InstanceInformation(){ SeriesID = "B", StudyID = "B1", PatientID = "P1"}, 
      new InstanceInformation(){ SeriesID = "B", StudyID = "B1", PatientID = "P1"}, 
     }; 

     IEnumerable<IGrouping<string, InstanceInformation>> bySeries = infos.GroupBy(g => g.SeriesID); 
     IEnumerable<IGrouping<string, InstanceInformation>> byStudy = bySeries.SelectMany(g => g.GroupBy(g_inner => g_inner.StudyID)); 
     IEnumerable<IGrouping<string, InstanceInformation>> byPatient = byStudy.SelectMany(g => g.GroupBy(g_inner => g_inner.PatientID)); 

     foreach (IGrouping<string, InstanceInformation> group in byPatient) { 
      Console.WriteLine(group.Key); 
      foreach(InstanceInformation II in group) 
       Console.WriteLine(" " + II.ToString()); 
     } 
} 
+0

Esto no es un grupo real (porque agrupa, luego vuelve a colocar todo), sino algo más similar a un 'OrderBy(). ThenBy(). ThenBy() ...' – digEmAll

+0

Tiene razón. Sin embargo, produce los grupos que él busca. Dependiendo de cómo quiera recuperar los datos, es posible que desee que su solución –

2

En su clase, anule el método tostring; como abajo.

public class InstanceInformation 
    { 
     public string PatientID { get; set; } public string StudyID { get; set; } public string SeriesID { get; set; } public string InstanceID { get; set; } 
     public override string ToString() 
     { 
      var r = string.Format("{0}/{1}/{2}/{3}", PatientID, StudyID, SeriesID, InstanceID); 
      return r; 
     } 
    } 

var listofstring = list.ConvertAll<string>(x => x.ToString()).ToList(); 
var listofstringdistinct = listofstring.Distinct().ToList(); 

Esto es más fácil de leer y comprender.

+0

haya corregido el formato de su código :) –

2

No sabe exacly lo que necesita, pero esto (código muy larga) devolverá un diccionario (de los diccionarios ...) agrupados como usted ha dicho (es decir PatientID/StudyID/SeriesID/InstanceID):

var byPatient = new Dictionary<string, Dictionary<string, Dictionary<string, Dictionary<string, InstanceInformation>>>>(); 
foreach (var patientGroup in instances.GroupBy(x => x.PatientID)) 
{ 
    var byStudy = new Dictionary<string, Dictionary<string, Dictionary<string, InstanceInformation>>>(); 
    byPatient.Add(patientGroup.Key, byStudy); 
    foreach (var studyGroup in patientGroup.GroupBy(x => x.StudyID)) 
    { 
     var bySeries = new Dictionary<string, Dictionary<string, InstanceInformation>>(); 
     byStudy.Add(studyGroup.Key, bySeries); 
     foreach (var seriesIdGroup in studyGroup.GroupBy(x => x.SeriesID)) 
     { 
      var byInstance = new Dictionary<string, InstanceInformation>(); 
      bySeries.Add(seriesIdGroup.Key, byInstance); 
      foreach (var inst in seriesIdGroup) 
      { 
       byInstance.Add(inst.InstanceID, inst); 
      } 
     } 
    } 
} 

P. S.
He considerado InstanceID como único entre todas las instancias.

De lo contrario, el último nivel diccionario debe ser: Dictionary<string, List<InstanceInformation>>

EDIT:

La lectura de su último comentario, creo que no es necesario un verdadero GroupBy, sino más bien una OrderBy().ThenBy()...

foreach (var el in instances.OrderBy(x => x.PatientID) 
          .ThenBy(x => x.StudyID) 
          .ThenBy(x => x.SeriesID) 
          .ThenBy(x => x.InstanceID)) 
{ 
    // it yields: 
    // Pat1 Std1 Srs1 Inst1 
    // Pat1 Std1 Srs1 Inst2 
    // Pat1 Std1 Srs2 Inst1 
    // Pat1 Std2 Srs2 Inst2 
    // ... 
} 
+0

Creo que esto es lo más cercano a lo que necesito hasta ahora. Actualmente estoy tratando de descubrir cómo voy a repetir los resultados. . Digamos, por ejemplo, si quería mostrarlo como Patient1/Study1/Series1/1, Patient1/Study1/Series1/2, etc. –

+0

Al leer su comentario, creo que no necesita un grupo, revise mi edición;) – digEmAll

+0

The El diccionario parecía funcionar para lo que necesitaba, se necesitó un poco de código para iterar a través de él, algunos foreachs de anidamiento que usaban KeyValuePairs, pero hizo el trabajo. Lo aprecio digEmAll. –

1

La siguiente declaración de Linq en sintaxis de consulta debería resolver su problema.

var groups = from instance in instances 
         group instance by instance.PatientGuid into patientGroups 
         select new 
         { 
          patientGroups.Key, 
          StudyGroups = from instance in patientGroups 
              group instance by instance.StudyGuid into studyGroups 
              select new 
              { 
              studyGroups.Key, 
              SeriesGroups = from c in studyGroups 
                 group c by c.SeriesGuid into seriesGroups 
                 select seriesGroups 
              } 

         }; 

Puede iterar sus grupos con el siguiente conjunto de bucles foreach anidados en los grupos. Esto le permitirá crear su árbol de directorios de manera eficiente y realizar cualquier otra operación en cada nivel.

foreach (var patientGroups in groups) 
      { 
       Console.WriteLine("Patient Level = {0}", patientGroups.Key); 
       foreach (var studyGroups in patientGroups.StudyGroups) 
       { 
        Console.WriteLine("Study Level = {0}", studyGroups.Key); 
        foreach (var seriesGroups in studyGroups.SeriesGroups) 
        { 
         Console.WriteLine("Series Level = {0}", seriesGroups.Key); 
         foreach (var instance in seriesGroups) 
         { 
          Console.WriteLine("Instance Level = {0}", instance.InstanceGuid); 
         } 
        } 
       } 

      } 

Esto es una prueba de concepto, pero las pruebas iniciales muestran que funciona correctamente. Cualquier comentario será bienvenido.

1

Eric Lippert explicó perfectamente cómo se puede evitar el horrible anidamiento y se escribe una sola consulta plana usando "consulta de continuación" (la palabra clave into).

Creo que puede hacer un paso más y escribirlo directamente utilizando el método GroupBy. A veces, el uso de los métodos de LINQ directamente le da código más claro y creo que esto es un ejemplo de ello:

var groups = instances. 
    GroupBy(instance => instance.PatientID). 
    GroupBy(patientGroup => patientGroup.StudyID). 
    GroupBy(studyGroup => studyGroup.SeriesID). 
    GroupBy(seriesGroup => seriesGroup.InstanceID). 
    GroupBy(instanceGroup => patientGroups.Key); 

(Realmente no sé si esto es lo que está buscando - Acabo de hacer una "sintáctica transformación" de lo que escribió Eric - y yo creo que no cambiar el significado de la pregunta de Eric)

EDITAR puede haber algún truco con el último group by, porque no es totalmente regular.