【问题标题】:Looping through a Linq Result Set with Multiple Joins循环遍历具有多个连接的 Linq 结果集
【发布时间】:2012-03-17 00:14:17
【问题描述】:

我有一个复杂的 LINQ 查询,例如

        var results = from obj1 in context.ProcessBases
                      join obj2 in context.InspectorArticles
                      on obj1.ID equals obj2.ProcessBaseID
                      join obj3 in context.InspectorSamples
                      on obj2.ID equals obj3.InspectorArticleID
                      where obj1.ID == _processBaseID
                      select new {obj1, obj2, obj3,};

现在这个结果集将只有 ONE ProcessBases,每个 ProcessBase 将有 MULTIPLE InspectorArticles 并且每个 InspectorArticle 将有 MULTIPLE InspectorSamples。因此,当我遍历结果集时,我想遍历每个InspectorArticle,然后遍历属于该InspectorArticle 的每个InspectorSample,类似于:

        foreach (InspectorArticle _iart in results.First().obj2)
        {
          ...          
            foreach (InspectorSample _isamp in results.First().obj3)
            {
               ...
            }

        }

但由于我在结果集上调用了.First(),显然我得到了这个异常:

foreach 语句不能对 x 类型的变量进行操作,因为 x 可以 不包含“GetEnumerator”的公共定义

那么我如何循环遍历InspectorArticle 的每个实例,然后循环遍历该文章的InspectorSamples 的数量?

谢谢。

【问题讨论】:

    标签: c# linq loops join enumeration


    【解决方案1】:

    由于您将记录连接在一起,因此此陈述不正确:

    现在这个结果集将只有一个 ProcessBase,每个 ProcessBase 将有 MULTIPLE InspectorArticles 并且每个 InspectorArticle 将 有多个 InspectorSamples。

    执行查询后,您实际上将拥有一个IEnumerable,其中IEnumerable 中的每个对象都包含对ProcessBaseInspectorArticleInspectorSample 的引用。例如,在 LinqPad 中使用下面的代码将产生一个IEnumerable,其内容如下:

    代码:

    void Main()
    {
        var processBases = new List<ProcessBase>();
        var inspectorArticles = new List<InspectorArticle>();
        var inspectorSamples = new List<InspectorSample>();
        processBases.Add(new ProcessBase { ID = 1 });
        processBases.Add(new ProcessBase { ID = 2 });
        inspectorArticles.Add(new InspectorArticle { ID = 3, ProcessBaseID = 1 });
        inspectorArticles.Add(new InspectorArticle { ID = 4, ProcessBaseID = 1 });
        inspectorArticles.Add(new InspectorArticle { ID = 5, ProcessBaseID = 2 });
        inspectorSamples.Add(new InspectorSample { ID = 6, InspectorArticleID = 3 });
        inspectorSamples.Add(new InspectorSample { ID = 7, InspectorArticleID = 3 });
        inspectorSamples.Add(new InspectorSample { ID = 8, InspectorArticleID = 3 });
        inspectorSamples.Add(new InspectorSample { ID = 9, InspectorArticleID = 4 });
        inspectorSamples.Add(new InspectorSample { ID = 10, InspectorArticleID = 5 });
        inspectorSamples.Add(new InspectorSample { ID = 11, InspectorArticleID = 5 });
    
        var processBaseID = 1;
        var results =   from obj1 in processBases
                        join obj2 in inspectorArticles on obj1.ID equals obj2.ProcessBaseID
                        join obj3 in inspectorSamples on obj2.ID equals obj3.InspectorArticleID
                        where obj1.ID == processBaseID
                        select new { obj1, obj2, obj3 };
        Console.WriteLine(results);
    }
    
    public class ProcessBase
    {
        public int ID { get; set; }
    }
    
    public class InspectorArticle
    {
        public int ID { get; set; }
        public int ProcessBaseID { get; set; }
    }
    
    public class InspectorSample
    {
        public int ID { get; set; }
        public int InspectorArticleID { get; set; }
    }
    

    结果:

    因此,如果您想保持 Linq 语句保持原样并使用多个 foreach 语句循环遍历它,则需要使用类似于以下代码的内容:

    foreach(var article in results.GroupBy(g => g.obj2.ID))
    {
        Console.WriteLine("Article ID: #{0}", article.Key);
        foreach(var sample in results
                              .Where(s => s.obj3.InspectorArticleID == article.Key)
                              .Select(s => s.obj3))
        {
            Console.WriteLine("\tSample ID: #{0}", sample.ID);
        }
    }
    

    使用此代码(并继续上面的示例)应该可以得到输出:

    Article ID: #3
      Sample ID: #6
      Sample ID: #7
      Sample ID: #8
    Article ID: #4
      Sample ID: #9
    

    这种方法的缺点是您必须多次枚举结果列表。如果你知道这个列表总是很小,那没什么大不了的。如果列表非常大,那么您可能需要想出一个更好的方法来返回数据。

    编辑

    为了使代码更高效,您可以按InspectorArticle.ID 分组,然后创建一个以ID 为键的Dictionary,并包含原始InspectorArticle 和关联的InspectorSamples

    var articles = results.GroupBy(g => g.obj2.ID)
                          .ToDictionary(k => k.Key, v => new { 
                              InspectorArticle = v.Select(s => s.obj2).First(), 
                              InspectorSamples = v.Select(s => s.obj3) });
    
    foreach(var article in articles.OrderBy(a => a.Key).Select(kv => kv.Value))
    {
        Console.WriteLine("Article ID: #{0}", article.InspectorArticle.ID);
        foreach(var sample in article.InspectorSamples)
        {
            Console.WriteLine("\tSample ID: #{0}", sample.ID);
        }
    }
    

    上面的代码将产生与我的第一个示例相同的结果,但对于较长的文章和示例列表将更有效,因为它只会在构建 Dictionary 时枚举整个列表一次。

    请注意,我将DictionaryID 属性分开,因为我没有为GroupBy 方法提供IEqualityComparer。如果您希望通过 InspectorArticle 对象本身作为键,则需要确保具有相同 ID 的两个不同 InspectorArticle 实例被视为相等,如果您创建一个 IEqualityComparer 并通过将其放入GroupBy 方法中。

    【讨论】:

    • @ClunkyCoder 多考虑一下,如果您的列表较长,则发布更有效的方法。
    猜你喜欢
    • 2019-02-18
    • 2012-11-27
    • 1970-01-01
    • 2010-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-14
    相关资源
    最近更新 更多