Вопрос о вложенном запросе LINQ - PullRequest
5 голосов
/ 05 марта 2011

Сегодня я столкнулся с проблемой, и некоторое время я был в тупике, пытаясь получить результаты, которые искал.

В настоящее время у меня есть класс, похожий на следующий:

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

У меня есть List<InstanceInformation>, и я пытаюсь использовать LINQ (или любые другие средства для генерации путей ( для файлового каталога ) на основе этого списка, который выглядит следующим образом:

PatientID/StudyID/SeriesID/InstanceID

Моя проблема в том, что данные в настоящее время неструктурированы, поскольку они поступают в ранее упомянутой форме (Список), и мне нужен способ сгруппировать все данные со следующими ограничениями:

  • групповые идентификаторы по SeriesID
  • групповые идентификаторы по StudyID
  • групповые идентификаторы по PatientID

В настоящее время у меня есть что-то похожее на это:

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;

, который просто группирует все мои InstanceIDs по PatientID, и довольно трудно отбирать все данные после этой массовой группировки, чтобы увидеть, находятся ли области между (StudyID / SeriesID)теряютсяЛюбые другие методы решения этой проблемы были бы более чем приветствуемыми.

Это в первую очередь просто для группировки объектов - так как мне нужно было бы затем выполнить итерации по ним (используя foreach)

Ответы [ 6 ]

11 голосов
/ 05 марта 2011

Я понятия не имею, является ли запрос, который вы создали, именно тем запросом, который вам действительно нужен или необходим, но, предположив, что это так, давайте рассмотрим вопрос о том, есть ли лучший способ его написать.

Место, которое вы хотите посмотреть, это раздел 7.16.2.1 спецификации C # 4, часть которой я привожу здесь для вашего удобства:


Выражение запроса с продолжением

from ... into x ...

переведено на

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

Это ясно?Давайте посмотрим на фрагмент вашего запроса, который я пометил звездами:

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;

Здесь у нас есть

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

спецификация говорит, что это эквивалентно

from ... into studyGroups ...

, чтобы мы могли переписать ваш запрос как

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;

Сделайте это снова.Теперь у нас есть

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

, и спецификация говорит, что это то же самое, что и

from ... into seriesGroup ...

, поэтому переписать это так:

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;

И снова!

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;

С чем, я надеюсь, вы согласны, намного легче читать.Я бы еще больше улучшил его читабельность, изменив тот факт, что «экземпляр» используется полдюжины раз для обозначения разных вещей:

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;

Является ли этот запрос действительно необходимым для решения вашей проблемы, я неНе знаю, но, по крайней мере, об этом вы можете рассуждать, не выворачивая себя наизнанку, пытаясь выполнить все вложения.

Этот метод называется «продолжение запроса».По сути, идея состоит в том, что продолжение пока вводит новую переменную диапазона в запросе.

2 голосов
/ 05 марта 2011

Не знаю точно, что вам нужно, но этот (очень длинный код) вернет словарь (словарей ...), сгруппированный, как вы сказали (т.е. 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);
            }
        }
    }
}

приписка
Я считал InstanceID уникальным среди всех случаев.

В противном случае последний уровень словаря должен быть: Dictionary<string, List<InstanceInformation>>

EDIT:

Читая ваш последний комментарий, я думаю, вам не нужен настоящий GroupBy, а скорее 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
    // ...
}
2 голосов
/ 05 марта 2011

В вашем классе переопределите метод tostring; как показано ниже.

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

Это легче читать и понимать.

2 голосов
/ 05 марта 2011

Я думаю, что это даст то, что вы ищете:

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());
        }
}
1 голос
/ 05 марта 2011

Эрик Липперт прекрасно объяснил, как можно избежать ужасного вложения и написать только один простой запрос, используя «продолжение запроса» (ключевое слово into).

Я думаю, что вы можете сделать еще один шаг и написать его напрямую, используя метод GroupBy.Иногда использование методов LINQ напрямую дает вам более ясный код, и я думаю, что это один из таких примеров:

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

(я не знаю, действительно ли это то, что вы ищете - я только что сделал«синтаксическая трансформация» того, что написал Эрик - и я думаю, что я не изменил смысла запроса Эрикане совсем регулярно.

1 голос
/ 05 марта 2011

Следующая инструкция Linq в синтаксисе запроса должна решить вашу проблему.

 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
                                          }

                        };

Затем вы можете выполнять итерации своих групп со следующим набором вложенных циклов foreach для групп. Это позволит вам эффективно создавать дерево каталогов и выполнять любые другие операции на каждом уровне.

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

             }

Это подтверждение концепции, но первоначальное тестирование показывает, что оно работает правильно. Любые комментарии будут оценены.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...