# 添加一个注释系统部件@ 1 @ \ MPK1\ 建立数据库

<!--category-- Entity Framework  -->
<datetime class="hidden">2024-08-26T11:53</datetime>

## 一. 导言导言 导言 导言 导言

@._@这是一系列关于如何建立评论系统的文章的第一部分_.}#,}在这个文章中, 我将建立数据库_.

[TOC]

## 建立数据库

使用 Postgress 博客@;}用Postgres 开始于 I @'_m, 用循环查询来存储评论@.}我讨厌这种强烈的评论吗?,}但是它能让我们走出大门 带着一个螺纹的批注系统(和I doñ'}没有太多流量,}所以它的'不是什么大事...

### EF背景

在目前的系统里, 我们使用“ '” Comment Lipt' 来定义批注@ MS K2 之间的关系, 这样我就可以支持线性备注@. 我们的类图看起来像这个 @ :

```mermaid
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` 具有 a 有 `PostId` 与“,”和A `ParentCommentId` 与“(”链接的注释 `CommentClosure` 表格用于存储备注@. @%

### 注释服务

缩略 `CommentService` 负责管理备注@. 它有方法添加 @ , 核准\ , 拒绝\ MS K3 并删除备注}%.}它也有办法为 post, 获取批注, 为备注获得批注}.

此服务的界面看起来像这个#:}

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

#### 添加方法

我们添加的方法总结了这个评论系统的结构 =.}

它首先开始一个新的交易“; ” , 因为那些不熟悉的交易是确保将一组操作作为单一的工作单位处理的一种方式 ‘. ’ 如果任何操作失败了 , ’ 该交易可以回滚\ , } 并且所有更改都被撤销 . 当您有多个需要一起完成的操作时, 这很重要 $,\ 并且你想确保它们一起成功或者失败 {. }

然后用 Markdig 库 < .\ > 来将批注文字从 MarkDown 到 HTML 进行解析,这是允许用户使用 markdown@.} 格式化评论的简单方法

下一步它创建该实体并保存到数据库@. @%

这是在两个步骤中完成的 {-} 。 首先, 备注被保存 , 然后, 批注关闭被保存了.} 这是因为注释需要保存来生成 Id\ I,} id\ msk3} 这段备注用于结束@.}

我们接下来将建立 `CommentClosures` 我们先用一个自@-}引用关闭条目 < , > 那么如果有父子意见的话... ...我们把母子评论的所有祖先都收集起来 然后把它们添加到新的注释里...

最后,,我们进行交易 . 如果任何操作失败...",该交易被回放\.

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

```

#### GetForest 后方法

我们赢得了MSKOT 覆盖整个事情 但是 `Add` 和 `Get` 我们的主要 CRUD 操作为此服务@. @%

正如你可以看到的,我们有一个基于 顶级的评论“. 我们还有一个 `maxDepth` 允许我们限制批注树的深度@ .}如果只显示顶部 @ MS K1}关节评论@ I,,或者如果我们想限制树的深度来改进性能\ .

在这项递归方法下,我们不得不在以下情况下应用深度过滤器: 我们 @ '@ 已经获取了批注@ MS K1} 因为我们可以在查询中做 '} 这是因为一个评语的深度取决于它拥有的祖先数量@ MPK4。 我们可以在 SQL=. 中很容易地查询这个问题。

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

```

## 在结论中的结论

这是一个简单的线条评论系统, 使用循环查询来存储批注@. @ it'}不是最有效率的系统{,但是它很简单 它能工作\.}在将来 <,}~I'}

到那时为止, , @#请在下面留言 @!@%