Pergunta

Considere o seguinte requisito para construir um aplicativo de fórum

Postagem pai

- Child Post1

    - Child Post1-1
    - Child Post1-2
        - Child Post1-2-1
- Child Post2
    - Child Post

- Child Post3

Estrutura da tabela

tblPost -

  • PostId
  • ChildPostId
  • Título
  • Publicar conteúdo
  • Nome de usuário

=======================

Posso recuperar esse tipo de dados usando um CTE recursivo.Não tenho certeza se essa é a melhor abordagem.

Perguntas

  • Qual é a melhor maneira de recuperar esses dados usando SQL?

  • Existe uma maneira melhor de carregar esses dados usando um ORM?

  • Se seguirmos a rota do SQL, qual é a melhor maneira de carregar esses dados em uma classe como mostrado abaixo:

    public class Post {
      public int PostId {get;set;}
      public string PostTitle {get;set;}
      public string PostContent {get;set;}
      public string PostedBy {get;set;}
      public IEnumerable<Post> ChildPosts {get;set;}
    }
    
  • Que tal exibir esse tipo de dados, digamos, usando a sintaxe do razor para uma visualização ??

Foi útil?

Solução

De acordo com o seu comentário, você está aberto a sugestões sobre como melhorar o seu esquema de banco de dados atual, no qual você basicamente tem um post_id e um child_post_id colunas para realizar o relacionamento hierárquico.

Então, vamos prosseguir:

Qual é a melhor maneira de recuperar esses dados usando SQL?

Eu recomendo que você dê uma olhada no seguinte artigo que ilustra uma técnica muito boa para gerenciar esses dados hierárquicos de uma maneira muito eficiente. Ele usa o Modelo de conjunto aninhado no qual você define conjuntos com nós esquerdo e direito e, em seguida, é capaz de construir a árvore inteira com uma única consulta SQL:

insira a descrição da imagem aqui

Existe uma maneira melhor de carregar esses dados usando um ORM?

Existem maneiras de fazer isso usando um ORM, como NHibernate e EF, mas vou deixar isso para a próxima vez. Você pode considerar dividir suas perguntas em várias perguntas do SO, pois o assunto é bastante amplo. Se você aprender a fazer isso usando o ADO.NET simples, terá um entendimento muito melhor das técnicas subjacentes envolvidas, de modo que amanhã você decida usar tal ORM e já saberá o que procurar para obter consultas eficientes.

Que tal exibir esse tipo de dados, digamos, usando a sintaxe do razor para uma visão ??

Depois de ter construído seu modelo hierárquico, ele é extremamente simples. Tudo o que você precisa fazer é definir um modelo de exibição personalizado para o tipo Post no qual você chamaria o modelo de exibição para todas as postagens filho.

Assumindo o seguinte modelo:

public class Post
{
    public int PostId { get; set; }
    public string PostTitle { get; set; }
    public IEnumerable<Post> ChildPosts { get; set; }
}

e o controlador a seguir (no qual eu obviamente codifiquei os valores, mas depois de ler o tutorial que vinculei no início da minha postagem, você será capaz de construir este modelo com uma única consulta SQL):

public class HomeController : Controller
{
    public ActionResult Index()
    {
        // Hardcoding the model here, but you could use the 
        // Nested Set Model technique I have linked to 
        // in order to build this model from your database
        var post = new Post
        {
            PostId = 1,
            PostTitle = "Parent Post",
            ChildPosts = new[]
            {
                new Post 
                {
                    PostId = 2,
                    PostTitle = "Child Post 1",
                    ChildPosts = new[]
                    {
                        new Post 
                        {
                            PostId = 3,
                            PostTitle = "Child Post 1-1",
                            ChildPosts = new[]
                            {
                                new Post
                                {
                                    PostId = 4,
                                    PostTitle = "Child Post 1-2-1"
                                }
                            }
                        },
                        new Post 
                        {
                            PostId = 5,
                            PostTitle = "Child Post 1-2"
                        },
                    }
                },

                new Post 
                {
                    PostId = 6,
                    PostTitle = "Child Post 2",
                    ChildPosts = new[]
                    {
                        new Post
                        {
                            PostId = 7,
                            PostTitle = "Child Post"
                        }
                    }
                },
                new Post 
                {
                    PostId = 8,
                    PostTitle = "Child Post 3"
                },
            }
        };
        return View(post);
    }
}

e então você teria uma visualização ~/Views/Home/Index.cshtml:

@model Post
<ul>
    @Html.DisplayForModel()
</ul>

e, claro, um modelo de exibição correspondente (~/Views/Home/DisplayTemplates/Post.cshtml) que será recursivo em nosso caso para renderizar a árvore completa:

@model Post
<li>
    @Html.DisplayFor(x => x.PostTitle)
    <ul>
        @Html.DisplayFor(x => x.ChildPosts)
    </ul>
</li>

e, claro, o resultado final é o que se poderia esperar:

insira a descrição da imagem aqui


ATUALIZAÇÃO:

Conforme solicitado na seção de comentários, aqui está um exemplo de como alguém pode preencher o modelo Postagem. Vamos supor que você seguiu o modelo de conjunto aninhado para projetar sua tabela de banco de dados:

CREATE TABLE posts (id int primary key, left int, right int, title nvarchar(100));

e que você o preencheu com as postagens:

INSERT INTO posts (id, left, right, title) VALUES (1, 1, 16, 'Parent Post');
INSERT INTO posts (id, left, right, title) VALUES (2, 2, 9, 'Child Post1');
INSERT INTO posts (id, left, right, title) VALUES (3, 3, 4, 'Child Post1-1');
INSERT INTO posts (id, left, right, title) VALUES (4, 5, 8, 'Child Post1-2');
INSERT INTO posts (id, left, right, title) VALUES (5, 6, 7, 'Child Post1-2-1');
INSERT INTO posts (id, left, right, title) VALUES (6, 10, 13, 'Child Post2');
INSERT INTO posts (id, left, right, title) VALUES (7, 11, 12, 'Child Post');
INSERT INTO posts (id, left, right, title) VALUES (8, 14, 15, 'Child Post3');

Agora você pode buscá-los.

Mas, como sempre antes, realmente fazendo algo, você descreve o que deseja fazer. Ou seja: você define um contrato:

public interface IPostsRepository
{
    Post GetPost();
}

Agora você começa a fazer . Nesse caso, usaremos o ADO.NET simples para consultar o banco de dados e construir o objeto Post. Usaremos um algoritmo iterativo com uma pilha para construir a árvore, mas você também pode usar um algoritmo recursivo:

public class PostsRepositoryAdoNet: IPostsRepository
{
    private readonly string _connectionString;
    public PostsRepositoryAdoNet(string connectionString)
    {
        _connectionString = connectionString;
    }

    private class Scalar
    {
        public int Depth { get; set; }
        public Post Post { get; set; }
    }

    public Post GetPost()
    {
        using (var conn = new SqlConnection(_connectionString))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();
            cmd.CommandText =
            @"
                SELECT p.id, p.title, (COUNT(parent.title) - 1) AS depth
                FROM posts AS p, posts AS parent
                WHERE p.left BETWEEN parent.left AND parent.right
                GROUP BY p.title
                ORDER BY p.left;
            ";
            using (var reader = cmd.ExecuteReader())
            {
                if (!reader.Read())
                {
                    return null;
                }

                var nodes = new Stack<Post>();
                var scalar = FromDataReader(reader);
                var rootNode = scalar.Post;
                int currentDepth = 0;
                var currentNode = rootNode;
                while (reader.Read())
                {
                    var depth = reader.GetInt32(reader.GetOrdinal("depth"));
                    if (depth > currentDepth)
                    {
                        nodes.Push(currentNode);
                        currentDepth = depth;
                    }
                    else if (depth < currentDepth)
                    {
                        while (depth < currentDepth)
                        {
                            --currentDepth;
                            nodes.Pop();
                        }
                    }
                    scalar = FromDataReader(reader);
                    currentNode = scalar.Post;
                    var p = nodes.Peek();
                    if (p.ChildPosts == null)
                    {
                        p.ChildPosts = new List<Post>();
                    }
                    p.ChildPosts.Add(currentNode);
                }
                nodes.Clear();
                return rootNode;
            }
        }
    }

    private Scalar FromDataReader(DbDataReader reader)
    {
        return new Scalar
        {
            Depth = reader.GetInt32(reader.GetOrdinal("depth")),
            Post = new Post
            {
                PostId = reader.GetInt32(reader.GetOrdinal("id")),
                PostTitle = reader.GetString(reader.GetOrdinal("title"))
            }
        };
    }
}

Agora que temos este repositório, podemos juntar as peças:

public class HomeController : Controller
{
    private readonly IPostsRepository _repository;
    public HomeController(IPostsRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index()
    {
        var post = _repository.GetPost();
        return View(post);
    }
}

e a última parte é configurar seu framework de injeção de dependência favorito para injetar a implementação desejada do repositório e como temos apenas um até agora que seria PostsRepositoryAdoNet. E se amanhã você decidir mudar para um ORM, tudo o que você precisa fazer é escrever o repositório correspondente implementando a interface IPostsRepository.

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