Pergunta

Eu tenho alguns dados hierárquicos - cada entrada tem um id e uma identificação de entrada (anulável) pai. Eu quero recuperar todas as entradas na árvore sob uma determinada entrada. Isto é, em um banco de dados SQL Server 2005. Eu estou consultando-lo com LINQ to SQL em C # 3.5.

LINQ to SQL não suporta Common Table Expressions diretamente. Minhas escolhas são para reunir os dados em código com várias consultas LINQ, ou para fazer uma vista sobre o banco de dados que as superfícies uma CTE.

Qual opção (ou outra opção) que você acha que terá melhor desempenho quando os volumes de dados obter grande? É SQL Server 2008 HierarchyId digite suportado em LINQ to SQL?

Foi útil?

Solução

eu iria criar uma visão e uma função baseada em tabela associada com base no CTE. Meu raciocínio para isso é que, enquanto você poderia implementar a lógica do lado da aplicação, isso envolveria o envio de dados intermediários sobre o fio para a computação na aplicação. Usando o designer DBML, a visão se traduz em uma entidade de mesa. Você pode, então, associar a função com a entidade Mesa e invocar o método criado no DataContext para objetos da deriva do tipo definido pela visão. Usando a função baseada em tabela permite que o mecanismo de consulta para levar os seus parâmetros em conta durante a construção do conjunto de resultados em vez de aplicar uma condição no conjunto de resultados definidos pela visão após o fato.

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]

Para usá-la você faria algo assim - assumindo algum esquema de nomenclatura razoável:

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...
    }
}

Outras dicas

Este opção também pode ser útil:

método LINQ AsHierarchy () de extensão
http://www.scip.be/index.php?Page=ArticlesNET18

Estou surpreso que ninguém tenha mencionado um projeto alternativo de banco de dados - quando as necessidades de hierarquia a ser achatada de vários níveis e recuperados com alto desempenho (não tão considerando o espaço de armazenamento) é melhor usar outra tabela entidade-2-entidade para acompanhar hierarquia em vez de abordagem parent_id.

Ele permitirá não apenas as relações monoparentais, mas também as relações parentais multi, indicações de nível e diferentes tipos de relações:

CREATE TABLE Person (
  Id INTEGER,
  Name TEXT
);

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

Eu tenho feito isso de duas maneiras:

  1. Siga a recuperação de cada camada da árvore com base na entrada do usuário. Imagine um controle de exibição de árvore preenchido com o nó raiz, os filhos da raiz, e os netos de raiz. Apenas a raiz e as crianças são expandidos (netos estão escondidos com o colapso). À medida que o usuário expande uma criança nó os netos da raiz são display (que foram obtidas anteriormente e escondido), e uma recuperação de todos os bisnetos é lançado. Repetir o teste padrão para N-camadas profundas. Esse padrão funciona muito bem para árvores de grande porte (profundidade ou largura) porque só recupera a parte da árvore necessário.
  2. Use um procedimento armazenado com LINQ. Use algo como uma expressão de tabela comum no servidor para construir seus resultados em uma mesa plana, ou construir uma árvore XML em T-SQL. Scott Guthrie tem uma ótimo artigo sobre o uso de procedimentos armazenados em LINQ. Construa a sua árvore a partir dos resultados quando voltar se em um formato plano, ou usar a árvore XML se isso é que é o que você voltar.

Este método de extensão poderia ser modificado para usar IQueryable. Eu usei-o com sucesso no passado em uma coleção de objetos. Ela pode trabalhar para o seu cenário.

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

Aqui está como eu o chamei:

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

Este código é uma versão melhorada do código, fixa-bug encontrado aqui .

No MS SQL Server 2008 Você pode usar HierarchyID diretamente, em sql2005 você pode ter que implementá-las manualmente. ParentID não é tão alto desempenho em grandes conjuntos de dados. Verifique também este artigo para mais discussão sobre o tema.

Eu tenho essa abordagem de de Rob Conery blogue (verificação em torno Pt. 6 para este código, também no CodePlex) e eu adoro usá-lo. Isso poderia ser remodelada para suportar múltiplos níveis "sub".

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

O problema com a buscar os dados do lado do cliente é que você nunca pode ter certeza de quão profundo você precisa ir. Este método vai fazer uma ida e volta por profundidade e que poderia ser union'd fazer de 0 a uma profundidade especificada de uma ida e volta.

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

Não se pode, no entanto, fazer profundidade arbitrária. Se você realmente exigem profundidade arbitrária, você precisa fazer isso no banco de dados - para que você possa tomar a decisão correta para a paragem

.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top