嵌套的LINQ查询问题

时间:2011-03-04 21:39:34

标签: c# asp.net-mvc linq grouping

我今天遇到了一个问题而且我已经被困了一段时间试图得到我正在寻找的结果。

我目前有一个类似于以下的类:

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(或其他任何方法来生成基于此列表的路径(用于文件目录),类似于以下内容:< / p>

PatientID/StudyID/SeriesID/InstanceID

我的问题是数据目前是非结构化的,因为它出现在前面提到的表单(List)中,我需要一种方法来对具有以下约束的所有数据进行分组:

  • 按系列ID分组的InstanceID
  • 按StudyID分组系列ID
  • PatientID的小组StudyID

我目前有类似的东西:

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;

它只是按PatientID对我的所有InstanceID进行分组,并且很难在此大规模分组之后剔除所有数据,以查看其间的区域(StudyID / SeriesID)是否丢失。解决这个问题的任何其他方法都非常受欢迎。

这主要是为了对对象进行分组 - 因为我需要迭代它们(使用foreach)

6 个答案:

答案 0 :(得分:11)

我不知道你提出的查询是否是你真正想要或需要的查询,但假设它是,让我们考虑是否有更好的方法来编写它。

您想要查看的地方是C#4规范的第7.16.2.1节,为方便起见,我在此引用其中一部分:


  

带有延续的查询表达式

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;

这是否实际上是你需要解决问题的查询,我不知道,但至少这一个你可以推理而不是让自己内心试图跟随所有的嵌套。

这种技术称为“查询延续”。基本上这个想法是,到目前为止,continuation在查询中引入了一个新的范围变量。

答案 1 :(得分:2)

我认为这会产生你想要的东西:

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

答案 2 :(得分:2)

在你的类中重写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();

这更容易阅读和理解。

答案 3 :(得分:2)

不知道你需要什么,但是这个(很长的代码)会返回一个字典(字典......)按你所说的分组(即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。
我认为InstanceID在所有实例中都是唯一的。

否则,最后一个字典级别应为:Dictionary<string, List<InstanceInformation>>

修改

阅读您的上一条评论,我认为您不需要真正的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
    // ...
}

答案 4 :(得分:1)

查询语法中的以下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);
                         }
                     }
                 }

             }

这是一个概念验证,但初步测试表明它可以正常工作。任何意见将不胜感激。

答案 5 :(得分:1)

Eric Lippert完美地解释了如何避免可怕的嵌套并使用“查询延续”(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);

(我真的不知道这是不是你正在寻找的东西 - 我只是做了一个埃里克写的“语法转换” - 我相信我没有改变Eric的查询的意思)

编辑最后group by可能会有一些诡计,因为它不是完全正常的。