Цикл по набору результатов Linq с несколькими объединениями - PullRequest
1 голос
/ 17 марта 2012

У меня сложный запрос 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, а затем перебирать каждый InspectorSample, принадлежащий этому InspectorArticle, что-то вроде:

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

        }

Но так как я вызвал .First() в моем наборе результатов, очевидно, я получаю это исключение:

оператор foreach не может работать с переменными типа x, потому что x делает не содержит публичное определение для 'GetEnumerator'

Итак, как я могу просмотреть каждый экземпляр InspectorArticle, а затем выполнить цикл по номеру InspectorSamples для этой статьи?

Спасибо.

1 Ответ

4 голосов
/ 17 марта 2012

Поскольку вы объединяете записи вместе, это утверждение неверно:

Теперь этот набор результатов будет иметь только ОДНУ ProcessBases, каждая ProcessBase будет иметь НЕСКОЛЬКО инспекторов статей, и каждый инспектор статей будет есть несколько образцов инспектора.

На самом деле после выполнения запроса вы получите IEnumerable, где каждый объект в IEnumerable содержит ссылку на ProcessBase, InspectorArticle и InspectorSample. Например, использование приведенного ниже кода в 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; }
}

Результаты:

linqpad results

Итак, если вы хотите сохранить оператор 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

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

EDIT

Чтобы сделать код более эффективным, вы можете сгруппировать по InspectorArticle.ID, а затем создать Dictionary с ключом ID, содержащий исходный 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.

Обратите внимание, что я отключил Dictionary свойства ID, потому что я не поставил IEqualityComparer для метода GroupBy. Если вы предпочитаете вводить сам объект InspectorArticle, вам нужно убедиться, что два разных экземпляра InspectorArticle с одинаковым ID считаются равными, что можно сделать, если вы создадите IEqualityComparer и передадите это в методе GroupBy.

...