Back to "Ajouter une partie du système de commentaires 1 - Établissement de la base de données"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Entity Framework

Ajouter une partie du système de commentaires 1 - Établissement de la base de données

Monday, 26 August 2024

Introduction

Un des aspects clés d’un site de blog comme celui-ci est un système de commentaires.

Établissement de la base de données

Comme pour le reste du site, j’utilise Postgres pour les blogs.

Contexte de l'EF

Dans le système actuel, nous utilisons un 'Comment Closure' pour définir les relations entre les commentaires.

classDiagram
    class CommentEntity {
        int Id
        CommentStatus Status
        string Author
        string? HtmlContent
        string Content
        DateTime CreatedAt
        int PostId
        int? ParentCommentId
    }

    class BlogPostEntity {
        int Id
        string Title
        string Content
        DateTime CreatedAt
    }

    class CommentClosure {
        int AncestorId
        int DescendantId
        int Depth
    }

    CommentEntity "1" --> "1" BlogPostEntity : belongs to
    CommentEntity "1" --> "0..1" CommentEntity : parent
    CommentEntity "0..*" --> "0..*" CommentClosure : Ancestors
    CommentEntity "0..*" --> "0..*" CommentClosure : Descendants
    CommentClosure "1" --> "1" CommentEntity : Ancestor
    CommentClosure "1" --> "1" CommentEntity : Descendant

    class CommentStatus {
        <<enum>>
        Pending
        Approved
        Rejected
        Deleted
    }

Vous pouvez voir ici que chaque CommentEntity a un PostId pour le poste il's attachés à, et un ParentCommentId pour le commentaire il' est attaché à ( s’il y a une CommentClosure Tableau est utilisé pour stocker les relations entre commentaires.

Le service de commentaires

L'article 5 de la Loi sur l'immigration et le statut des réfugiés CommentService est responsable de la gestion des commentaires.

Notre interface pour ce service ressemble à ceci:

public interface ICommentService
{
    Task<string> Add( int postId, int? parentCommentId, string author, string content);
    Task<List<CommentEntity>> GetForPost(int blogPostId, int page = 1, int pageSize = 10, int? maxDepth = null, CommentStatus? status = null);
    Task<List<CommentEntity>> GetDescendants(int commentId, int maxDepth = 0);

     Task<CommentEntity> Get(int commentId);
    Task<List<CommentEntity>> GetAncestors(int commentId);
    Task Delete(int commentId);
    Task Reject(int commentId);
    Task Approve(int commentId);
}

Méthode d'ajout

Notre méthode Add résume la structure de ce système de commentaires.

Il commence d’abord une nouvelle opération; pour les opérations qui ne sont pas connues, c’est un moyen de faire en sorte qu’un groupe d’opérations soit traité comme une seule unité de travail . Si l’une ou l’autre des opérations échoue, la transaction peut être reroulée de nouveauM SK3 et que tous les changements soient annulésMSC4 Cela est important lorsque vous avez plusieurs opérations à effectuer conjointement.

Il analyse ensuite le texte des commentaires de Markdown vers HTML en utilisant la bibliothèque Markdig. C'est une façon simple d'autoriser les utilisateurs à formater leurs commentaires par Markdown.

Ensuite, il crée l'entité et la sauvegarde dans la base de données.

Cela se fait en deux étapes - d’abord le commentaire est conservé, puis la clôture du commentaire est sauvegardée . C’est parce que le commentaire doit être conservé pour générer l’IDMSC3 qui est utilisé dans la fermetureM SK4

Nous construisons ensuite la hiérarchie des CommentClosures qui définissent les relations entre les commentaires. Nous commençons par un auto- entrée de renvoi à la clôture , puis, s’il y a un commentaire parent, nous attirons tous les ancêtres du commentaire parent et les ajoutons au nouveau commentaire.

Finalement, nous engageons la transaction. Si l’une ou l’autre des opérations échoue , la transaction est remboursée

 public async Task<string> Add(int postId, int? parentCommentId, string author, string content)
  {
      await using var transaction = await context.Database.BeginTransactionAsync();
      try
      {
         var html = Markdig.Markdown.ToHtml(content);
          // Create the new comment
          var newComment = new CommentEntity()
          {
              HtmlContent = html,
              Content = content,
              CreatedAt = DateTime.UtcNow,
              PostId = postId,
              Author = author,
              Status = CommentStatus.Pending,
              ParentCommentId = parentCommentId
          };
            
          context.Comments.Add(newComment);
          await context.SaveChangesAsync();
          logger.LogInformation("Saved comment to DB");// Save to generate the new comment's Id

          // Insert into CommentClosure table
          var commentClosures = new List<CommentClosure>();

          // Self-referencing closure entry
          commentClosures.Add(new CommentClosure
          {
              AncestorId = newComment.Id,
              DescendantId = newComment.Id,
              Depth = 0
          });

          // If there is a parent comment, insert the ancestor relationships
          if (parentCommentId.HasValue)
          {
              // Fetch all ancestors of the parent comment
              var parentAncestors = await context.CommentClosures
                  .Where(cc => cc.DescendantId == parentCommentId.Value)
                  .ToListAsync();

              // Add ancestor relationships for the new comment
              foreach (var ancestor in parentAncestors)
              {
                  commentClosures.Add(new CommentClosure
                  {
                      AncestorId = ancestor.AncestorId,
                      DescendantId = newComment.Id,
                      Depth = ancestor.Depth + 1
                  });
              }

              // Add a direct parent-child relationship
              commentClosures.Add(new CommentClosure
              {
                  AncestorId = parentCommentId.Value,
                  DescendantId = newComment.Id,
                  Depth = 1
              });
          }

          context.CommentClosures.AddRange(commentClosures);
          await context.SaveChangesAsync();
          logger.LogInformation("Saved comment closure to DB");

          // Commit transaction
          await transaction.CommitAsync();
          return html;
      }
      catch (Exception e)
      {
          // Rollback transaction in case of failure
          await transaction.RollbackAsync();
          logger.LogError(e, "Failed to save comment to DB");
      }

      return string.Empty;
  }

Méthode GetForPost

Nous ne devons pas couvrir l’ensemble, mais Add et Get sont nos principales opérations de CRUD pour ce service.

Comme vous pouvez le voir, nous avons un messagerie basé sur les commentaires de niveau supérieur. Nous avons également maxDepth Paramètre qui nous permet de limiter la profondeur de l'arbre de commentaires. Ce paramètre est utile si nous voulons seulement montrer les commentaires de niveau supérieurM SK1, ou si nous désirons limiter cette profondeur pour améliorer le rendement

Dans cette approche récursive, nous devons induitement appliquer le filtre de profondeur après que nous ayons obtenu les commentaires' , comme nous pouvons' ne l'avons pas fait dans la rechercheM SK3 Ceci parce que la profondeur d'un commentaire est déterminée par le nombre d'ancesteurs qu'il a.

  public async Task<List<CommentEntity>> GetForPost(int blogPostId, int page = 1, int pageSize = 10, int? maxDepth = null, CommentStatus? status = null)
  {
      // Step 1: Query the top-level comments for the specified blog post
      var query = context.Comments
          .Where(c => c.PostId == blogPostId)
          .OrderByDescending(c => c.CreatedAt)
          .Skip((page - 1) * pageSize)
          .Take(pageSize);

      // Step 2: Filter by status if provided
      if (status.HasValue)
      {
          query = query.Where(c => c.Status == status.Value);
      }

      var topLevelComments = await query
          .Include(c => c.ParentComment)
          .Include(d=>d.Descendants)
          .ToListAsync();

      // Step 4: Filter descendants based on the maxDepth
      foreach (var comment in topLevelComments)
      {
          if (maxDepth != null)
          {
              FilterDescendantsByDepth(comment, 0, maxDepth.Value);
          }
      }

      return topLevelComments;
  }

// Recursive helper method to limit the descendants based on the specified depth
  private void FilterDescendantsByDepth(CommentEntity comment, int currentDepth, int maxDepth)
  {
      if (currentDepth >= maxDepth)
      {
          // If the max depth is reached or there are no descendants, stop recursion
          comment.Descendants = new List<CommentClosure>();  // Clear further descendants beyond maxDepth
          return;
      }

      foreach (var closure in comment.Descendants.ToList())  // Iterate over a copy to prevent modification during iteration
      {
          FilterDescendantsByDepth(closure.Descendant, currentDepth + 1, maxDepth);
      }
  }

En conclusion

Il s’agit d’un système simple de commentaires par fil qui utilise des demandes recursives pour entreposer les relations entre les commentaires.. Il n’est pas le système le plus efficace, mais il est simple et fonctionne. . Dans l’avenir, IM SK6 Je couvrirai les aspects frontales de ce système.

Jusqu’à ce moment, n’hésitez pas à laisser un commentaire ci-dessousM SK1

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.