Back to "Доdanie частини системи коментарів 1 - Установлення бази даних"

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

Доdanie частини системи коментарів 1 - Установлення бази даних

Monday, 26 August 2024

Введение

Однією з ключових аспектів такого блогінгу є система коментарів. Я вирішила створити таку системуM SK2 Це перша частина серії статей про те, як побудувати систему коментарів . В цій статті, Я розпочну роботу над створенням бази даних

Налагодження бази даних

Як і з усіма іншими сайтами, я користуюсь Postgres для блогів. Я починаю з I', використовуючи рекурсивні пошуки, щоб зберігати коментарі. МSK3. Мене це дуже не подобається.

Контекст ЕФ

В теперішній системі ми використовуємо 'Kommentar ClosureM SK1 для визначення відносин між Kommentarами - так, що я можу підтримувати потокові коментарі

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
    }

Тут ви бачите, що кожен CommentEntity має PostId для відправки це', прикріплені доM SK1 і ParentCommentId для коментарів це ' прикріплений до МSK1 якщо якийсь МSK2 CommentClosure таблицю, щоб зберігати зв 'язки між Kommentarами.

Комментарна служба

І CommentService відповідає за управління коментарями. У ньому є способи додаватиM SK1 підтверджувати, відкидати , і видаляти коментаріМSK4 У нього є також способи отримати коментарі для статтіMSC5 та отримувати коментарі для коментарівMST6

Наш інтерфейс для цього сервісу виглядає ось так

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 SK1 Якщо якась з операцій провалиться, операцію можна повернути назадMSC3 і всі зміни будуть відмененіМSK4 Це важливо, коли ви маєте кілька операцій, які повинні бути проведені разом , і ви хочете, щоб вони всі впоралися або провалились разом.

Далі вона аналізує текст коментарів з Маркдау до HTML за допомогою бібліотеки Маркдіґа. Це простий спосіб дозволити користувачам форматувати свої коментарі за допомогою МаркдауM SK1

Далі він створює об 'єкт і зберігає його в базі даних.

Це робиться в двох етапах: - спочатку зачувається коментар , потім закривається запис МSK2 Це тому, що для генерації Id МSK3, який є використаний в закриванні, треба заpisać запис

Далі ми будуємо ієрархію CommentClosures які визначають взаємозв 'язки між Kommentarами. Ми починаємо з самого себеM SK1 відказуючи на закритий запис, тоді, якщо є батьківський коментар ми вилучаємо всі предки батьківського коментарів і додаємо їх до нового komentara . Потім ми додаємо прямий батьківськийMSC4 дитячий зв' язокМSK5

Врешті-решт, МСК0 , ми вчиняємо транзакцію ".". Якщо якась з операцій провалиться ",", транзакція повернеться назад .

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

Metodа GetForPost

Ми не зможемо описати всю ситуацію, але Add і Get це наші головні CRUD операції для цієї служби.

Як ви бачите, ми маємо плагування на основі відгуку верхнього рівня. Ми також маємо maxDepth параметр, який дозволяє нам обмежити глибину дерева коментарів. Це корисне, якщо ми хочемо показати тільки верхні відгукиM SK1рівневі коментаріМSK2 або якщо ми бажаємо обмежувати глибиnu дерева для покращення продуктивності.

Неочікувано, завдяки цьому рекурсивному підходу, нам потрібно застосувати фільтр глибини після того, як ми' завантажили коментарі ,, так само як ми можемо(M SK2) зробити це в SQL. Це тому, що глибина коментарів визначається числом предків, які у нього(МSK4), а ми( МSK5) можемо. легко задати це питання в SQL

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

На завершення

Це простий потоковий системний коментар, який використовує рекурсивні запити для зберігання зв 'язків між Kommentarами. Це ' не найефективнішою системою МSK2 але це МСК3 проста і працює М СК4 В майбутньому M СК5 Я М סК6 об' єднатиму фронтальні аспекти цієї системи М S К 7 багато HTMX М ็ С К 8 Альпіний М Ш К 9 Дж ’ юс М С К 10 та Tailwind CSS M S К 11

До цього часу, будь ласка, залишіть свій коментар нижчеM SK1

logo

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