Ajouter une partie du système de commentaires 1 - Établissement de la base de données
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