2010-08-29 4 views
1

J'ai une table de données qui contient un-à-plusieurs auto-référentiel:LINQ: Counting Nombre total de Nested éléments

Plant 
{ 
    ID, 
    Name, 
    ParentID 
} 

Je suis en train de créer une requête LINQ qui me dira le total nombre de descendants provenant d'une plante.

Exemple:

J'ai 5 plantes:

One {ID = 1, Name = Pine, Parent = null};// This is the root 
    Two {ID = 2, Name = Evergreen, Parent = 1} 
     Three {ID = 3, Name = Winter Evergreen, Parent = 2} 
    Four {ID = 4, Name = Christmas Tree, Parent = 1} 
Five {ID = 5, Name = Maple, Parent = null};// This is the root 

Quand j'appelle ma requête LINQ avec une entrée de ID = 1, je veux qu'il return 3, parce qu'il ya 3 descendants de One; Deux, Trois et Quatre. Five n'est pas un descendant de One. La seule façon de le faire implique des requêtes linq récursives imbriquées sur les résultats internes. Je n'ai aucune idée de comment faire cela et je me sens comme s'il y avait un moyen plus facile. J'utilise C# 4.0 et LINQ si cela est important.

+0

Vous parlez d'une table, donc je suppose que votre objectif est quelque chose qui peut se traduire en SQL? c'est-à-dire LinqToSql? (comme oppose à LinqToObjects, dont il existe des solutions faciles disponibles) –

Répondre

0

Si vous utilisez SQL Server, la requête que vous voulez construire serait:

DECLARE @TargetElementId int 
SET @TargetElementId = 1; 

WITH Plants AS 
(
    SELECT ID, Name, ParentId 
    FROM PlantsTable 
    WHERE ParentId = @TargetElementId 

    UNION ALL 

    SELECT ID, Name, ParentId 
    FROM PlantsTable 
    INNER JOIN Plants ON PlantsTable.ParentId = Plants.ID 
) 
SELECT COUNT(ID) - 1 FROM Plants 

Je ne crois pas que cela puisse être fait avec LinqToSql, voir Common Table Expression (CTE) in linq-to-sql?, qui est une question de même nature .

+0

C'est excellent et plus rapide que la solution linq. Je vais finir par l'utiliser. J'ai juste besoin de brosser mes trucs SQLConnection dans .Net. ExecuteScalar() ici je viens! – Erica

+0

C'est ce que j'ai fini par mettre en œuvre. Ça marche. Je pense que je vais probablement devoir le modifier pour que le paramètre soit lu à partir d'une autre requête, mais c'est pour un autre jour. – Erica

0

EDIT Ajout d'un code prenant en charge LINQ to SQL grâce au commentaire @Kirk Woll.

class Program 
{ 
    private static Table<Plant> plants; 

    static void Main(string[] args) 
    { 
     using (var dataContext = new DataClasses1DataContext()) 
     { 
      plants = dataContext.Plants; 
      var treesNodes = GetTreesNodes(plants.Where(plant => plant.ID == 1)); 
      Console.WriteLine(string.Join(",", 
              treesNodes.Select(plant => plant.ID))); 
     } 
    } 

    public static IEnumerable<Plant> GetTreesNodes(IEnumerable<Plant> roots) 
    { 
     if(!roots.Any()) 
     { 
      return roots; 
     } 

     var children = roots.SelectMany(root => 
            plants.Where(plant => plant.Parent == root)); 
     return roots.Union(GetTreesNodes(children)); 
    } 

} 

version précédente qui correspondent à LINQ to Objects:

Cette méthode peut fournir tous les nœuds de l'arbre:

public IEnumerable<Plant> GetTreesNodes(IEnumerable<Plant> roots) 
{ 
    if(!roots.Any()) 
    { 
     return Enumerable.Empty<Plant>(); 
    } 

    var rootsIds = roots.Select(root => root.ID); 
    var children = plants.Where(plant => rootsIds.Contains(plant.Parent)); 
    return roots.Union(GetTreesNodes(children)); 
} 

Il peut être utilisé comme dans l'exemple suivant:

[Test] 
public void ExampleTest() 
{ 
    var One = new Plant() {ID = 1, Name = "Pine", Parent = 0}; 
    var Two = new Plant() {ID = 2, Name = "Evergreen", Parent = 1}; 
    var Three = new Plant() {ID = 3, Name = "Winter Evergreen", Parent = 2}; 
    var Four = new Plant() {ID = 4, Name = "Christmas Tree", Parent = 1}; 
    var Five = new Plant() {ID = 5, Name = "Maple", Parent = 0}; 

    plants = new []{One,Two,Three,Four,Five}; 

    var nestedElements = GetTreesNodes(new[]{One}); 
    var numOfNestedElementsWithoutRoot = nestedElements.Count()-1; 

    Assert.That(numOfNestedElementsWithoutRoot, Is.EqualTo(3)); 
} 

Le code suppose qu'il n'y a pas de re les ferences.

+0

Elle mentionne "table de données" et "relation un à plusieurs" donc je pense qu'elle a besoin de quelque chose qui se traduira en SQL. Et cela ne le fera pas. En d'autres termes, elle a besoin de quelque chose qui fonctionnera avec IQueryable. –

+0

@Kirk Woll, vous avez un point là. S'il s'agit d'une table de données chargée, LinqToObjects la résoudra probablement (même s'il existe des solutions plus efficaces). Si c'est une table "en direct" où il faudra traduire en SQL alors le code pourrait ne pas fonctionner. – Elisha

+0

@Kirk Woll, à la réflexion, je ne trouve pas la partie qui aura du mal à traduire en SQL. Pouvez-vous pointer le code problématique? Il y a de bonnes chances que vous ayez raison, mais je ne peux pas trouver le problème moi-même :) – Elisha