Я пытаюсь создать дерево 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 сначала.
Не знаю, как написать рекурсивный 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];
}
}
}
Я знаю, что немного опоздала. Но вы сказали, что у вас уже есть версия с использованием 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));
}
Вы можете получить древовидную структуру, сочетающую 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);
}
}
}
Надеюсь, это поможет!
Я сделал нечто очень похожее, используя группу 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 };
Хотя вы не можете сделать это с помощью одного запроса (если вы не вызываете SQL напрямую с помощью CTE), вы можете ограничить количество запросов глубиной дерева.
Код слишком длинный для вставки, но основные шаги следующие:
Вы можете минимизировать количество узлов, передаваемых в запрос на шаге 2. SQL-сервер имеет тенденцию взрывать список из более чем 2000 записей. (Однако в SQL Compact такой проблемы нет).
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 означает "интегрированный язык", а это именно то, что я использовал.