Pregunta

Considere el siguiente requisito para crear una aplicación de foro

Publicación principal

- Child Post1

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

- Child Post3

Estructura de la tabla

TBLPOST -

  • ID del mensaje
  • ID de publicación infantil
  • Título
  • Publicar Contenido
  • Nombre de usuario

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

Puedo recuperar este tipo de datos usando un CTE recursivo.No estoy seguro de que este sea el mejor enfoque.

Preguntas

  • ¿Cuál es la mejor manera de recuperar estos datos usando SQL?

  • ¿Existe una mejor manera de cargar estos datos usando un ORM?

  • Si seguimos la ruta SQL, ¿cuál es la mejor manera de cargar estos datos en una clase como se muestra a continuación?

    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;}
    }
    
  • ¿Qué tal mostrar este tipo de datos, digamos, usar la sintaxis de Razor para una vista?

¿Fue útil?

Solución

Según su comentario, está abierto a sugerencias sobre cómo mejorar su esquema de base de datos actual en el que básicamente tiene una post_id y un child_post_id columnas para realizar la relación jerárquica.

Así que procedamos:

¿Cuál es la mejor manera de recuperar estos datos usando SQL?

Te recomiendo que eches un vistazo a siguiente artículo lo que ilustra una técnica muy buena para gestionar dichos datos jerárquicos de una manera muy eficiente.Utiliza el Modelo de conjunto anidado en el que define conjuntos con nodos izquierdo y derecho y luego puede construir el árbol completo con una sola consulta SQL:

enter image description here

¿Existe una mejor manera de cargar estos datos usando un ORM?

Hay maneras de hacer esto usando un ORM como NHibernate y EF pero lo dejaré para la próxima vez.Podría considerar dividir sus preguntas en varias preguntas SO, ya que el tema es bastante amplio.Si aprende cómo hacer esto usando ADO.NET simple, comprenderá mucho mejor las técnicas subyacentes involucradas, de modo que mañana decida usar dicho ORM ya sabrá qué buscar en el orden de consultas eficientes.

¿Qué tal mostrar este tipo de datos, por ejemplo, utilizando la sintaxis de afeitar para una vista?

Una vez que haya construido su modelo jerárquico, es extremadamente simple.Todo lo que tiene que hacer es definir una plantilla de visualización personalizada para el Post tipo en el que invocaría la plantilla de visualización para todas las publicaciones secundarias.

Suponiendo entonces el siguiente modelo:

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

y el siguiente controlador (en el que obviamente he codificado los valores, pero después de leer el tutorial al que me vinculé al principio de mi publicación, podrás construir este modelo con una sola 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);
    }
}

y entonces tendrías un ~/Views/Home/Index.cshtml vista:

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

y por supuesto una plantilla de visualización correspondiente (~/Views/Home/DisplayTemplates/Post.cshtml) que será recursivo en nuestro caso para representar el árbol completo:

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

y por supuesto el resultado final es el que cabría esperar:

enter image description here


ACTUALIZAR:

Como se solicitó en la sección de comentarios, aquí hay un ejemplo de cómo se podría completar el modelo de publicación.Supongamos que ha seguido las modelo de conjunto anidado para diseñar la tabla de su base de datos:

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

y que lo has llenado con los posts:

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

Ahora podrías ir a buscarlos.

Pero como siempre antes en realidad haciendo algo que describas lo que quieres hacer.Eso es:usted define un contrato:

public interface IPostsRepository
{
    Post GetPost();
}

Ahora llegas al haciendo.En este caso usaremos ADO.NET simple para consultar la base de datos y crearemos el objeto Post.Usaremos un algoritmo iterativo con una pila para construir el árbol, pero también puedes usar un 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"))
            }
        };
    }
}

Ahora que tenemos este repositorio podríamos juntar las piezas:

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

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

y la última parte es configurar su marco de inyección de dependencia favorito para inyectar la implementación deseada del repositorio y, dado que hasta ahora solo tenemos uno, eso sería PostsRepositoryAdoNet.Y si mañana decides cambiar a un ORM todo lo que tienes que hacer es escribir el repositorio correspondiente implementando el IPostsRepository interfaz.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top