Рекурсивные вызовы LINQ

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

Эти два важных поля

CompetitionID ParentCompetitionID

Некоторые данные могли бы быть

CompetitionID=1, ParentCompetitionID=null

CompetitionID=2, ParentCompetitionID=1

CompetitionID=3, ParentCompetitionID=1

Поврежденный запрос у меня есть просто результаты дисплеев в плоском формате. Видение, что я работаю с XML, своего рода рекурсивная функциональность, требуется. Я могу сделать это использование, нормальное для рекурсии цикла, но хотел бы видеть linq версию. Любая справка ценится.

var results = 
        from c1 in comps
        select new {
            c.CompetitionID,
            SubComps=
                from sc in comps.Where (c2 => c2.CompetitionID == c1.CompetitionID)
                select sc
        };

Обновление

Я нашел интересную статью Chris Eargle здесь, который показывает Вам, как позвонить делегатам лямбды рекурсивно. Вот код. Спасибо Chris!

Func factoral = x => x <= 1 ? 1 : x + factoral(--x);

Func factoral = null;

factoral = x => x <= 1 ? 1 : x + factoral(--x);

^ добавленное форматирование кода для показа lamba funcs прием должно присвоить пустой указатель делегату Func сначала.

15
задан BozoJoe 29 August 2013 в 02:51
поделиться

6 ответов

Не знаю, как написать рекурсивный LINQ. Но я думаю, что здесь не требуется никакой рекурсии. Дерево может быть построено всего за два шага:

Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
foreach (var c in comps)
    if (dic.ContainsKey(c.ParentCompetitionID))
        dic[c.ParentCompetitionID].Children.Add(c);
var root = dic[1];

Корневая переменная теперь содержит полное дерево.

Вот полный пример для тестирования:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication2
{
    class Competition
    {
        public int CompetitionID;
        public int ParentCompetitionID;
        public List<Competition> Children=new List<Competition>();
        public Competition(int id, int parent_id) 
        { 
            CompetitionID = id; 
            ParentCompetitionID = parent_id; 
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Competition> comps = new List<Competition>()
            {
                new Competition(1, 0), 
                new Competition(2,1),
                new Competition(3,1),
                new Competition(4,2),
                new Competition(5,3)
            };

            Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
            foreach (var c in comps)
                if (dic.ContainsKey(c.ParentCompetitionID))
                    dic[c.ParentCompetitionID].Children.Add(c);
            var root = dic[1];
        }
    }
}
6
ответ дан 1 December 2019 в 05:02
поделиться

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

internal class Competition
{
    public int CompetitionID;
    public int ParentCompetitionID;

    public Competition(int id, int parentId)
    {
        CompetitionID = id;
        ParentCompetitionID = parentId;
    }
}

internal class Node
{
    public Node(int id, IEnumerable<Node> children)
    {
        Children = children;
        Id = id;
    }

    public IEnumerable<Node> Children { get; private set; }
    public int Id { get; private set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        var comps = new List<Competition>
                        {
                            new Competition(1, 0),
                            new Competition(2, 1),
                            new Competition(3, 1),
                            new Competition(4, 2),
                            new Competition(5, 3)
                        };

        Node root = ToTree(0, comps);
    }

    static readonly Func<int, IEnumerable<Competition>, Node> ToTree = 
        (nodeId, competitions) => new Node(nodeId, from c in competitions where c.ParentCompetitionID == nodeId select ToTree(c.CompetitionID, competitions));
}
2
ответ дан 1 December 2019 в 05:02
поделиться

Вы можете получить древовидную структуру, сочетающую LINQ и рекурсию с делегатами. В этом примере я использую такую ​​структуру XML:

<Competitions>
  <Competition ID="1" />
  <Competition ID="2" ParentCompetitionID="1" />
  <Competition ID="3" ParentCompetitionID="1" />
  <Competition ID="4" />
</Competitions>

Итак, чтобы хранить данные узла в коде и облегчить навигацию, создайте такой класс:

class Competition
{
   public int CompetitionID { get; set; }

   public IEnumerable<Competition> Childs { get; set; }
}

Теперь, используя Linq to XML, вы загружаете файл xml в XDocument. После этого объявите делегат, который выполняет итерацию по всем элементам xml внутри документа, выбирая узлы, у которых есть идентификатор, соответствующий параметру идентификатора делегата. При выборе каждого узла он снова обращается к делегату, передавая идентификатор родительского узла для поиска. Сначала он запускается с параметром id, установленным на null, поэтому сначала выбираются корневые узлы:

    var doc = XDocument.Load("tree.xml");

    //Declare the delegate for using it recursively
    Func<int?, IEnumerable<Competition>> selectCompetitions = null;

    selectCompetitions = (int? id) =>
    {
       return doc.Elements("Competitions").Elements().Where(c => 
       {
         //If id is null return only root nodes (without ParentCompetitionID attribute)
         if (id == null)
            return c.Attribute("ParentCompetitionID") == null;
         else
            //If id has value, look for nodes with that parent id
            return  c.Attribute("ParentCompetitionID") != null &&
                    c.Attribute("ParentCompetitionID").Value == id.Value.ToString();
        }).Select(x => new Competition() 
                       { 
                      CompetitionID = Convert.ToInt32(x.Attribute("ID").Value),
                      //Always look for childs with this node id, call again to this
                      //delegate with the corresponding ID
                      Childs = selectCompetitions(Convert.ToInt32(x.Attribute("ID").Value))
                       });
};

var competitions = selectCompetitions(null);

Чтобы проверить это, вы можете использовать простой повторяющийся метод, который выводит дерево на консоль:

private static void Write(IEnumerable<Competition> competitions, int indent)
{
   foreach (var c in competitions)
   {
       string line = String.Empty;

       for (int i = 0; i < indent; i++)
       {
          line += "\t";
       }

       line += "CompetitionID = " + c.CompetitionID.ToString();

       Console.WriteLine(line);

       if (c.Childs != null && c.Childs.Count() > 0)
       {
           int id = indent + 1;
           Write(c.Childs, id);
        }
   }
}

Надеюсь, это поможет!

1
ответ дан 1 December 2019 в 05:02
поделиться

Я сделал нечто очень похожее, используя группу LINQ по

. Я не использую синтаксис запросов LINQ, так что простите меня, если это неправильно:

var results = from c in comps
    group c by c.ParentCompetitionID into g
    select new { ParentId = g.Key, ChildId = g };

Конечно, было бы лучше, если бы ваши классы выглядели примерно так:

class Competition {
   int Id;
   string Description;
   Competition ParentCompetition;
}

Тогда вместо группировки только по идентификатору вы можете группировать по всему соревнованию, что ускоряет и упрощает генерацию XML.

var results = from c in comps
    group c by c.ParentCompetition into g
    select new { Parent = g.Key, Child = g };
0
ответ дан 1 December 2019 в 05:02
поделиться

Хотя вы не можете сделать это с помощью одного запроса (если вы не вызываете SQL напрямую с помощью CTE), вы можете ограничить количество запросов глубиной дерева.

Код слишком длинный для вставки, но основные шаги следующие:

  1. Сбор корневых узлов и добавление ко «всем» узлам
  2. Сбор узлов с родительскими узлами во «всех» узлах (передача списка для запроса)
  3. Добавьте узлы на шаге 2 ко всем узлам
  4. Повторяйте 2-3, пока шаг 2 не вернет 0 узлов (что должно быть глубиной дерева + 1, я думаю).

Вы можете минимизировать количество узлов, передаваемых в запрос на шаге 2. SQL-сервер имеет тенденцию взрывать список из более чем 2000 записей. (Однако в SQL Compact такой проблемы нет).

0
ответ дан 1 December 2019 в 05:02
поделиться
class Competition
{ 
   int ID { get; set;}
   int ParentID { get; set; }
   IEnumerable<Competition> Children { get; set; } 
}

public IEnumerable<Competition> GetChildren(
   IEnumerable<Competition> competitions, int parentID)
{
   IEnumerable<Competition> children =
      competitions.Where(c => c.ParentID == parentID);

   if (children.Count() == 0)
      return null; 

   return children.Select(
      c => new Competition { ID = c.ID, Children = GetChildren(c.ID) };
}

Затем вы можете просто вызвать GetChildren, передав ID корня в качестве parentID, и это вернет древовидную структуру. Вы также можете изменить объект Competition на XML API по вашему выбору.

Я знаю, что это не совсем то, что вы ищете, но, насколько я знаю, LINQ не поддерживает рекурсию. Тем не менее, часть LIN в LINQ означает "интегрированный язык", а это именно то, что я использовал.

0
ответ дан 1 December 2019 в 05:02
поделиться
Другие вопросы по тегам:

Похожие вопросы: