Question

Considérez l'exigence suivante pour créer une application Forum

Post parent

- Child Post1

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

- Child Post3

Structure de table

tblPost -

  • Posté
  • Enfant -postide
  • Titre
  • Publier un contenu
  • Nom d'utilisateur

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

Je peux retarder ce type de données à l'aide d'un CTE récursif. Je ne suis pas sûr que ce soit la meilleure approche.

Des questions

  • Quelle est la meilleure façon de reprendre ces données à l'aide de SQL?

  • Existe-t-il une meilleure façon de charger ces données à l'aide d'un ORM?

  • Si nous parcourons la route SQL, quelle est la meilleure façon de charger ces données dans une classe comme indiqué ci-dessous:

    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 diriez-vous d'afficher ce type de données dire à l'aide de la syntaxe du rasoir pour une vue ??

Était-ce utile?

La solution

Selon votre commentaire, vous êtes ouvert aux suggestions sur l'amélioration de votre schéma de base de données actuel dans lequel vous avez essentiellement un post_id et un child_post_id colonnes pour effectuer la relation hiérarchique.

Alors procédons:

Quelle est la meilleure façon de reprendre ces données à l'aide de SQL?

Je vous recommande de jeter un œil au article suivant qui illustre une très belle technique pour gérer ces données hiérarchiques de manière très efficace. Il utilise le Modèle d'ensemble imbriqué dans lequel vous définissez des ensembles avec des nœuds gauche et droit, puis vous pouvez construire l'arbre entier avec une seule requête SQL:

enter image description here

Existe-t-il une meilleure façon de charger ces données à l'aide d'un ORM?

Il existe des moyens de le faire en utilisant un ORM tel que Nhibernate et EF, mais je vais laisser cela pour la prochaine fois. Vous pourriez envisager de diviser vos questions en plusieurs questions, car le sujet est assez large. Si vous apprenez à le faire en utilisant un ADO.NET ordinaire, vous percerez une bien meilleure compréhension des techniques sous-jacentes impliquées afin que demain vous décidez d'utiliser un tel orm, vous saurez déjà quoi rechercher dans l'ordre de requêtes efficaces.

Que diriez-vous d'afficher ce type de données dire à l'aide de la syntaxe du rasoir pour une vue ??

Une fois que vous avez construit votre modèle hiérarchique, c'est extrêmement simple. Tout ce que vous avez à faire est de définir un modèle d'affichage personnalisé pour le Post Tapez dans lequel vous invoqueriez le modèle d'affichage pour tous les messages enfants.

Alors en supposant le modèle suivant:

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

Et le contrôleur suivant (dans lequel j'ai évidemment codé en dur les valeurs, mais après avoir lu le tutoriel à qui j'ai lié au début de mon message, vous pourrez construire ce modèle avec une seule requête 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);
    }
}

Et puis vous auriez un ~/Views/Home/Index.cshtml voir:

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

et bien sûr un modèle d'affichage correspondant (~/Views/Home/DisplayTemplates/Post.cshtml) qui sera récursif dans notre cas pour rendre l'arbre complet:

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

Et bien sûr, le résultat final est ce à quoi on pourrait s'attendre:

enter image description here


METTRE À JOUR:

Comme demandé dans la section des commentaires, voici un exemple de la façon dont on pourrait peupler le modèle de poste. Supposons que vous avez suivi le modèle d'ensemble imbriqué Pour concevoir votre table de base de données:

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

Et que vous l'avez rempli de messages:

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

Maintenant, vous pouvez les récupérer.

Mais comme toujours avant en fait Faire quelque chose que vous décrivez ce que vous voulez faire. C'est-à-dire: vous définissez un contrat:

public interface IPostsRepository
{
    Post GetPost();
}

Maintenant vous arrivez au Faire. Dans ce cas, nous utiliserons un ADO.net ordinaire pour interroger la base de données et construire l'objet post. Nous utiliserons un algorithme itératif avec une pile pour construire l'arbre, mais vous pouvez également utiliser un algorithme récursif:

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

Maintenant que nous avons ce référentiel, nous pourrions rassembler les pièces:

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

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

Et la dernière partie consiste à configurer votre cadre d'injection de dépendance préféré pour injecter l'implémentation souhaitée du référentiel et puisque nous n'en avons qu'un jusqu'à présent PostsRepositoryAdoNet. Et si demain, vous décidez de passer à un ORM, tout ce que vous avez à faire est d'écrire le référentiel correspondant implémentant le IPostsRepository interface.

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