递归LINQ调用

时间:2010-04-25 12:18:51

标签: c# linq

我正在尝试使用父子关系构建一些数据的XML树,但是在同一个表中。

这两个重要领域是

CompetitionID ParentCompetitionID

某些数据可能

CompetitionID = 1, ParentCompetitionID =空

CompetitionID = 2, ParentCompetitionID = 1

CompetitionID = 3, ParentCompetitionID = 1

我破坏的查询只是以平面格式显示结果。看到我正在使用XML,需要某种递归功能。我可以使用普通的for循环递归来做到这一点,但是想看看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 here找到了一篇有趣的文章,向您展示了如何递归调用lambda委托。这是代码。谢谢克里斯!

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

Func<int, int> factoral = null;

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

^添加了代码格式以显示lamba函数 诀窍是首先为Func委托指定null。

6 个答案:

答案 0 :(得分: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];
        }
    }
}

答案 1 :(得分:2)

我知道我在这里有点太晚了。但是你说你已经有了一个使用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)

您可以获得类似树的结构,将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代理节点的id节点的节点。选择每个节点时,它再次调用委托,传递父节点的id以进行查找。它首先将id参数设置为null,因此,选择firts根节点:

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

希望它有所帮助!

答案 3 :(得分:0)

我使用LINQ group by

做了类似的事情

我不使用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;
}

然后,您可以按整个竞争对象进行分组,而不是按ID进行分组,这样可以更快,更轻松地生成XML。

var results = from c in comps
    group c by c.ParentCompetition into g
    select new { Parent = g.Key, Child = g };

答案 4 :(得分:0)

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,将root的ID作为parentID传递,然后返回树结构。您还可以将Competition对象更改为您选择的XML API。

我知道这不是你想要的,但是afaik LINQ不支持递归。然而,LINQ的LIN部分意味着语言集成,这正是我使用的。

答案 5 :(得分:0)

虽然您无法使用单个查询执行此操作(除非您使用CTE直接调用SQL),但您可以将查询数限制为树的深度。

代码太长而无法粘贴,但基本步骤是:

  1. 收集根节点并添加到“所有”节点
  2. 在“所有”节点中收集父节点(传递列表以进行查询)
  3. 将步骤2中的节点添加到“所有节点”
  4. 重复2 - 3直到第2步返回0个节点(我认为应该是树的深度+ 1)。
  5. 您可以最小化在步骤2中传递给查询的节点数量.SQL Server倾向于使用超过2000个条目的列表进行轰炸。 (SQL Compact没有这样的问题)。