Question

J'ai des données hiérarchiques - chaque entrée a un identifiant et un identifiant d'entrée parent (nullable). Je veux récupérer toutes les entrées de l'arborescence sous une entrée donnée. Ceci est dans une base de données SQL Server 2005. Je l'interroge avec LINQ to SQL en C # 3.5.

LINQ to SQL ne prend pas directement en charge les Expressions de table courantes . Mes choix sont d'assembler les données dans le code avec plusieurs requêtes LINQ ou de créer une vue sur la base de données qui fait surface d'un CTE.

Selon vous, quelle option (ou autre option) fonctionnera mieux lorsque les volumes de données deviennent importants? Le type HierarchyId de SQL Server 2008 est-il pris en charge dans Linq vers SQL?

Était-ce utile?

La solution

Je configurerais une vue et une fonction associée basée sur une table basée sur le CTE. Mon raisonnement est que, bien que vous puissiez implémenter la logique côté application, cela impliquerait d’envoyer les données intermédiaires par le fil pour un calcul dans l’application. À l'aide du concepteur DBML, la vue est convertie en entité Table. Vous pouvez ensuite associer la fonction à l'entité Table et appeler la méthode créée sur le DataContext pour dériver des objets du type défini par la vue. L’utilisation de la fonction table permet au moteur de requête de prendre en compte vos paramètres lors de la construction du jeu de résultats plutôt que d’appliquer une condition au jeu de résultats défini par la vue après coup.

CREATE TABLE [dbo].[hierarchical_table](
    [id] [int] IDENTITY(1,1) NOT NULL,
    [parent_id] [int] NULL,
    [data] [varchar](255) NOT NULL,
 CONSTRAINT [PK_hierarchical_table] PRIMARY KEY CLUSTERED 
(
    [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

CREATE VIEW [dbo].[vw_recursive_view]
AS
WITH hierarchy_cte(id, parent_id, data, lvl) AS
(SELECT     id, parent_id, data, 0 AS lvl
      FROM         dbo.hierarchical_table
      WHERE     (parent_id IS NULL)
      UNION ALL
      SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
      FROM         dbo.hierarchical_table AS t1 INNER JOIN
                            hierarchy_cte AS h ON t1.parent_id = h.id)
SELECT     id, parent_id, data, lvl
FROM         hierarchy_cte AS result


CREATE FUNCTION [dbo].[fn_tree_for_parent] 
(
    @parent int
)
RETURNS 
@result TABLE 
(
    id int not null,
    parent_id int,
    data varchar(255) not null,
    lvl int not null
)
AS
BEGIN
    WITH hierarchy_cte(id, parent_id, data, lvl) AS
   (SELECT     id, parent_id, data, 0 AS lvl
        FROM         dbo.hierarchical_table
        WHERE     (id = @parent OR (parent_id IS NULL AND @parent IS NULL))
        UNION ALL
        SELECT     t1.id, t1.parent_id, t1.data, h.lvl + 1 AS lvl
        FROM         dbo.hierarchical_table AS t1 INNER JOIN
            hierarchy_cte AS h ON t1.parent_id = h.id)
    INSERT INTO @result
    SELECT     id, parent_id, data, lvl
    FROM         hierarchy_cte AS result
RETURN 
END

ALTER TABLE [dbo].[hierarchical_table]  WITH CHECK ADD  CONSTRAINT [FK_hierarchical_table_hierarchical_table] FOREIGN KEY([parent_id])
REFERENCES [dbo].[hierarchical_table] ([id])

ALTER TABLE [dbo].[hierarchical_table] CHECK CONSTRAINT [FK_hierarchical_table_hierarchical_table]

Pour l'utiliser, vous feriez quelque chose comme - en supposant un schéma de nommage raisonnable:

using (DataContext dc = new HierarchicalDataContext())
{
    HierarchicalTableEntity h = (from e in dc.HierarchicalTableEntities
                                 select e).First();
    var query = dc.FnTreeForParent( h.ID );
    foreach (HierarchicalTableViewEntity entity in query) {
        ...process the tree node...
    }
}

Autres conseils

Cette option pourrait également s'avérer utile:

Méthode d'extension LINQ AsHierarchy ()
http://www.scip.be/index.php?Page=ArticlesNET18

Je suis surpris que personne n'ait mentionné une conception de base de données alternative. Lorsque la hiérarchie doit être aplatie de plusieurs niveaux et extraite avec des performances élevées (sans tenir compte de l'espace de stockage), il est préférable d'utiliser une autre table entity-2-entity pour suivre la hiérarchie. au lieu de l'approche parent_id.

Il autorisera non seulement les relations monoparentales, mais également les relations multiparentales, les indications de niveau et différents types de relations:

CREATE TABLE Person (
  Id INTEGER,
  Name TEXT
);

CREATE TABLE PersonInPerson (
  PersonId INTEGER NOT NULL,
  InPersonId INTEGER NOT NULL,
  Level INTEGER,
  RelationKind VARCHAR(1)
);

J'ai fait cela de deux manières:

  1. Pilotez la récupération de chaque couche de l'arborescence en fonction des entrées de l'utilisateur. Imaginez un contrôle d’affichage arborant le nœud racine, les enfants de la racine et les petits-enfants de la racine. Seules la racine et les enfants sont développés (les petits-enfants sont cachés avec l'effondrement). Lorsque l'utilisateur développe un nœud enfant, les petits-enfants de la racine sont affichés (précédemment récupérés et masqués) et la récupération de tous les arrière-petits-enfants est lancée. Répétez le motif pour les couches N profondes. Ce modèle fonctionne très bien pour les grands arbres (profondeur ou largeur) car il ne récupère que la partie de l’arbre nécessaire.
  2. Utilisez une procédure stockée avec LINQ. Utilisez quelque chose comme une expression de table commune sur le serveur pour générer vos résultats dans une table simple ou créez une arborescence XML dans T-SQL. Scott Guthrie a un excellent article sur l'utilisation des processus stockés dans LINQ. Construisez votre arborescence à partir des résultats lorsqu'ils reviennent dans un format plat, ou utilisez l'arborescence XML si c'est ce que vous retournez.

Cette méthode d'extension pourrait éventuellement être modifiée pour utiliser IQueryable. Je l'ai utilisé avec succès dans le passé sur une collection d'objets. Cela peut fonctionner pour votre scénario.

public static IEnumerable<T> ByHierarchy<T>(
 this IEnumerable<T> source, Func<T, bool> startWith, Func<T, T, bool> connectBy)
{
  if (source == null)
   throw new ArgumentNullException("source");

  if (startWith == null)
   throw new ArgumentNullException("startWith");

  if (connectBy == null)
   throw new ArgumentNullException("connectBy");

  foreach (T root in source.Where(startWith))
  {
   yield return root;
   foreach (T child in source.ByHierarchy(c => connectBy(root, c), connectBy))
   {
    yield return child;
   }
 }
}

Voici comment je l'ai appelé:

comments.ByHierarchy(comment => comment.ParentNum == parentNum, 
 (parent, child) => child.ParentNum == parent.CommentNum && includeChildren)

Ce code est une version améliorée et corrigée de bogues du code trouvé ici .

Dans MS SQL 2008, vous pouvez utiliser directement HierarchyID , dans sql2005 vous devrez peut-être les implémenter manuellement. ParentID n'est pas performant sur les grands ensembles de données. Consultez également cet article pour en savoir plus sur le sujet.

J'ai eu cette approche de blog de Rob Conery (consultez le point 6 pour en savoir plus). code, également sur codeplex) et j'adore l'utiliser. Cela pourrait être repensé pour prendre en charge plusieurs " sous " niveaux.

var categories = from c in db.Categories
                 select new Category
                 {
                     CategoryID = c.CategoryID,
                     ParentCategoryID = c.ParentCategoryID,
                     SubCategories = new List<Category>(
                                      from sc in db.Categories
                                      where sc.ParentCategoryID == c.CategoryID
                                      select new Category {
                                        CategoryID = sc.CategoryID, 
                                        ParentProductID = sc.ParentProductID
                                        }
                                      )
                             };

Le problème avec l'extraction des données du côté client est que vous ne pouvez jamais être sûr de la profondeur à laquelle vous devez aller. Cette méthode effectue un aller-retour par profondeur et peut être effectuée de 0 à une profondeur spécifiée en un aller-retour.

public IQueryable<Node> GetChildrenAtDepth(int NodeID, int depth)
{
  IQueryable<Node> query = db.Nodes.Where(n => n.NodeID == NodeID);
  for(int i = 0; i < depth; i++)
    query = query.SelectMany(n => n.Children);
       //use this if the Children association has not been defined
    //query = query.SelectMany(n => db.Nodes.Where(c => c.ParentID == n.NodeID));
  return query;
}

Cela ne peut cependant pas faire une profondeur arbitraire. Si vous avez vraiment besoin d’une profondeur arbitraire, vous devez le faire dans la base de données pour pouvoir prendre la bonne décision d’arrêter.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top