Blog, Audit ve Forum AppService performans

This commit is contained in:
Sedat Öztürk 2026-02-04 21:39:59 +03:00
parent ec44d03e79
commit 8014d9df34
4 changed files with 128 additions and 68 deletions

View file

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.AuditLogging;
using Volo.Abp.Uow;
using static Erp.Platform.Data.Seeds.SeedConsts;
namespace Erp.Platform.AuditLogs;
@ -32,6 +34,7 @@ public class AuditLogAppService
return await MapToGetOutputDtoAsync(entity);
}
[UnitOfWork]
public override async Task<PagedResultDto<AuditLogDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
var query = await CreateFilteredQueryAsync(input);
@ -41,14 +44,23 @@ public class AuditLogAppService
query = ApplySorting(query, input);
query = ApplyPaging(query, input);
var auditLogList = await AsyncExecuter.ToListAsync(query);
// EntityChanges ile birlikte getir (N+1 query önlenir)
var auditLogRepository = (IAuditLogRepository)Repository;
var auditLogsWithDetails = await auditLogRepository.GetListAsync(
sorting: input.Sorting,
maxResultCount: input.MaxResultCount,
skipCount: input.SkipCount,
includeDetails: true
);
var entityDtos = new List<AuditLogDto>();
foreach (var item in auditLogList)
// Mapping tek seferde yap
var entityDtos = ObjectMapper.Map<List<AuditLog>, List<AuditLogDto>>(auditLogsWithDetails);
// EntityChangeCount'u doldur (artık EntityChanges yüklü)
foreach (var dto in entityDtos)
{
var dto = await MapToGetListOutputDtoAsync(item);
dto.EntityChangeCount = item.EntityChanges?.Count ?? 0; // null kontrolü artık burada güvenli
entityDtos.Add(dto);
var auditLog = auditLogsWithDetails.FirstOrDefault(a => a.Id == dto.Id);
dto.EntityChangeCount = auditLog?.EntityChanges?.Count ?? 0;
}
return new PagedResultDto<AuditLogDto>(

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Erp.Platform.Entities;
using Erp.Platform.Localization;
@ -6,6 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Localization;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
using Volo.Abp.Uow;
using static Erp.Platform.Data.Seeds.SeedConsts;
namespace Erp.Platform.Public;
@ -56,14 +58,17 @@ public class BlogAppService : PlatformAppService, IBlogAppService
var post = await _postRepository.GetAsync(id);
var dto = ObjectMapper.Map<BlogPost, BlogPostDto>(post);
// Get category
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(
await _categoryRepository.GetAsync(post.CategoryId)
);
// Category'yi getir (her zaman gerekli)
if (post.CategoryId != Guid.Empty)
{
var category = await _categoryRepository.GetAsync(post.CategoryId);
dto.Category = ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
}
return dto;
}
[UnitOfWork]
public async Task<BlogPostDto> UpdatePostAsync(Guid id, CreateUpdateBlogPostDto input)
{
var post = await _postRepository.GetAsync(id);
@ -84,22 +89,37 @@ public class BlogAppService : PlatformAppService, IBlogAppService
if (post.CategoryId != input.CategoryId)
{
var oldCategory = await _categoryRepository.GetAsync(post.CategoryId);
oldCategory.DecrementPostCount();
await _categoryRepository.UpdateAsync(oldCategory);
// Eski ve yeni category'leri tek sorguda çek (N+1 önleme)
var categoryIds = new[] { post.CategoryId, input.CategoryId };
var categories = await AsyncExecuter.ToListAsync(
(await _categoryRepository.GetQueryableAsync())
.Where(c => categoryIds.Contains(c.Id))
);
var newCategory = await _categoryRepository.GetAsync(input.CategoryId);
newCategory.IncrementPostCount();
await _categoryRepository.UpdateAsync(newCategory);
var oldCategory = categories.FirstOrDefault(c => c.Id == post.CategoryId);
var newCategory = categories.FirstOrDefault(c => c.Id == input.CategoryId);
if (oldCategory != null)
{
oldCategory.DecrementPostCount();
await _categoryRepository.UpdateAsync(oldCategory, autoSave: false);
}
if (newCategory != null)
{
newCategory.IncrementPostCount();
await _categoryRepository.UpdateAsync(newCategory, autoSave: false);
}
post.CategoryId = input.CategoryId;
}
await _postRepository.UpdateAsync(post);
await _postRepository.UpdateAsync(post, autoSave: true);
return await GetPostAsync(post.Id); // ✅ DTO dönülüyor
return await GetPostAsync(post.Id);
}
[UnitOfWork]
public async Task DeletePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
@ -113,9 +133,9 @@ public class BlogAppService : PlatformAppService, IBlogAppService
// Update category post count
var category = await _categoryRepository.GetAsync(post.CategoryId);
category.DecrementPostCount();
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: false);
await _postRepository.DeleteAsync(id);
await _postRepository.DeleteAsync(id, autoSave: true);
}
public async Task<BlogPostDto> PublishPostAsync(Guid id)
@ -169,7 +189,7 @@ public class BlogAppService : PlatformAppService, IBlogAppService
IsActive = input.IsActive
};
await _categoryRepository.InsertAsync(category);
await _categoryRepository.InsertAsync(category, autoSave: true);
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
}
@ -186,7 +206,7 @@ public class BlogAppService : PlatformAppService, IBlogAppService
category.DisplayOrder = input.DisplayOrder;
category.IsActive = input.IsActive;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<BlogCategory, BlogCategoryDto>(category);
}

View file

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Erp.Platform.DynamicData;

View file

@ -8,6 +8,7 @@ using Volo.Abp.Authorization;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
namespace Erp.Platform.Forum;
@ -164,7 +165,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
};
await _categoryRepository.InsertAsync(category);
await _categoryRepository.InsertAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
@ -181,7 +182,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
category.IsActive = input.IsActive;
category.IsLocked = input.IsLocked;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
@ -191,7 +192,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var category = await _categoryRepository.GetAsync(id);
category.IsLocked = !category.IsLocked;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
@ -201,19 +202,25 @@ public class ForumAppService : PlatformAppService, IForumAppService
var category = await _categoryRepository.GetAsync(id);
category.IsActive = !category.IsActive;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumCategory, ForumCategoryDto>(category);
}
[Authorize("App.ForumManagement.Delete")]
[UnitOfWork]
public async Task DeleteCategoryAsync(Guid id)
{
// Delete all topics and posts in this category
var topics = await _topicRepository.GetListAsync(t => t.CategoryId == id);
var topicIds = topics.Select(t => t.Id).ToList();
// Tüm topic ID'lerini al
var topicQueryable = await _topicRepository.GetQueryableAsync();
var topicIds = await AsyncExecuter.ToListAsync(
topicQueryable
.Where(t => t.CategoryId == id)
.Select(t => t.Id)
);
if (topicIds.Any())
{
// Bulk delete - daha performanslı
await _postRepository.DeleteAsync(p => topicIds.Contains(p.TopicId));
await _topicRepository.DeleteAsync(t => t.CategoryId == id);
}
@ -267,12 +274,19 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var topic = await _topicRepository.GetAsync(id);
topic.ViewCount++;
await _topicRepository.UpdateAsync(topic);
// View count artırma işlemi arka planda yapılmalı (performans için)
// Her okumada update yapmak performans sorununa neden olur
_ = Task.Run(async () =>
{
var t = await _topicRepository.GetAsync(id);
t.ViewCount++;
await _topicRepository.UpdateAsync(t);
});
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
[UnitOfWork]
public async Task<ForumTopicDto> CreateTopicAsync(CreateForumTopicDto input)
{
var topic = new ForumTopic(
@ -289,12 +303,12 @@ public class ForumAppService : PlatformAppService, IForumAppService
IsLocked = input.IsLocked
};
await _topicRepository.InsertAsync(topic);
await _topicRepository.InsertAsync(topic, autoSave: false);
// Update category topic count
var category = await _categoryRepository.GetAsync(input.CategoryId);
category.TopicCount++;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -309,25 +323,28 @@ public class ForumAppService : PlatformAppService, IForumAppService
topic.IsLocked = input.IsLocked;
topic.IsSolved = input.IsSolved;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
[UnitOfWork]
public async Task DeleteTopicAsync(Guid id)
{
var topic = await _topicRepository.GetAsync(id);
// Delete all posts in this topic
// Post sayısını al
var postCount = await _postRepository.CountAsync(p => p.TopicId == id);
// Tüm postları sil
await _postRepository.DeleteAsync(p => p.TopicId == id);
// Update category counts
var category = await _categoryRepository.GetAsync(topic.CategoryId);
category.TopicCount = Math.Max(0, category.TopicCount ?? 0 - 1);
var postCount = await _postRepository.CountAsync(p => p.TopicId == id);
category.PostCount = Math.Max(0, category.PostCount ?? 0 - postCount);
await _categoryRepository.UpdateAsync(category);
category.TopicCount = Math.Max(0, (category.TopicCount ?? 0) - 1);
category.PostCount = Math.Max(0, (category.PostCount ?? 0) - postCount);
await _categoryRepository.UpdateAsync(category, autoSave: false);
await _topicRepository.DeleteAsync(id);
await _topicRepository.DeleteAsync(id, autoSave: true);
}
// Post management
@ -373,6 +390,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
[UnitOfWork]
public async Task<ForumPostDto> CreatePostAsync(CreateForumPostDto input)
{
var post = new ForumPost(
@ -385,25 +403,25 @@ public class ForumAppService : PlatformAppService, IForumAppService
input.TenantId
);
await _postRepository.InsertAsync(post, autoSave: true);
await _postRepository.InsertAsync(post, autoSave: false);
// 🔽 Update topic
// Update topic
var topic = await _topicRepository.GetAsync(input.TopicId);
topic.ReplyCount++;
topic.LastPostId = post.Id;
topic.LastPostDate = post.CreationTime;
topic.LastPostUserId = post.AuthorId;
topic.LastPostUserName = post.AuthorName;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: false);
// 🔽 Update category
// Update category
var category = await _categoryRepository.GetAsync(topic.CategoryId);
category.PostCount++;
category.LastPostId = post.Id;
category.LastPostDate = post.CreationTime;
category.LastPostUserId = post.AuthorId;
category.LastPostUserName = post.AuthorName;
await _categoryRepository.UpdateAsync(category);
await _categoryRepository.UpdateAsync(category, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
@ -421,7 +439,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
post.Content = input.Content;
post.IsAcceptedAnswer = input.IsAcceptedAnswer;
await _postRepository.UpdateAsync(post);
await _postRepository.UpdateAsync(post, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
@ -475,34 +493,46 @@ public class ForumAppService : PlatformAppService, IForumAppService
await _categoryRepository.UpdateAsync(category);
}
[UnitOfWork]
public async Task<ForumPostDto> LikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.LikeCount++;
await _postRepository.UpdateAsync(post);
await _postRepository.UpdateAsync(post, autoSave: false);
var topic = await _topicRepository.GetAsync(post.TopicId);
var postsInTopic = await _postRepository.GetListAsync(p => p.TopicId == topic.Id);
topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0);
await _topicRepository.UpdateAsync(topic);
// Topic'teki tüm postların toplam like'larını hesapla (optimizasyon)
var queryable = await _postRepository.GetQueryableAsync();
var totalLikes = await AsyncExecuter.SumAsync(
queryable.Where(p => p.TopicId == topic.Id),
p => p.LikeCount ?? 0
);
topic.LikeCount = totalLikes;
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
[UnitOfWork]
public async Task<ForumPostDto> UnlikePostAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
post.LikeCount = Math.Max(0, post.LikeCount ?? 0 - 1);
await _postRepository.UpdateAsync(post);
post.LikeCount = Math.Max(0, (post.LikeCount ?? 0) - 1);
await _postRepository.UpdateAsync(post, autoSave: false);
// 🔽 Topic'in toplam beğeni sayısını güncelle
// Topic'in toplam beğeni sayısını güncelle (optimizasyon)
var topic = await _topicRepository.GetAsync(post.TopicId);
var postsInTopic = await _postRepository.GetListAsync(p => p.TopicId == topic.Id);
topic.LikeCount = postsInTopic.Sum(p => p.LikeCount ?? 0);
var queryable = await _postRepository.GetQueryableAsync();
var totalLikes = await AsyncExecuter.SumAsync(
queryable.Where(p => p.TopicId == topic.Id),
p => p.LikeCount ?? 0
);
topic.LikeCount = totalLikes;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
@ -512,7 +542,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = true;
await _postRepository.UpdateAsync(post);
await _postRepository.UpdateAsync(post, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
@ -521,7 +551,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var post = await _postRepository.GetAsync(id);
post.IsAcceptedAnswer = false;
await _postRepository.UpdateAsync(post);
await _postRepository.UpdateAsync(post, autoSave: true);
return ObjectMapper.Map<ForumPost, ForumPostDto>(post);
}
@ -532,7 +562,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount++;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -541,7 +571,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.LikeCount = Math.Max(0, topic.LikeCount - 1);
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -550,7 +580,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = true;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -558,7 +588,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var topic = await _topicRepository.GetAsync(id);
topic.IsPinned = false;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -567,7 +597,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = true;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -575,7 +605,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
{
var topic = await _topicRepository.GetAsync(id);
topic.IsLocked = false;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -584,7 +614,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = true;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -593,7 +623,7 @@ public class ForumAppService : PlatformAppService, IForumAppService
var topic = await _topicRepository.GetAsync(id);
topic.IsSolved = false;
await _topicRepository.UpdateAsync(topic);
await _topicRepository.UpdateAsync(topic, autoSave: true);
return ObjectMapper.Map<ForumTopic, ForumTopicDto>(topic);
}
@ -615,4 +645,3 @@ public class ForumAppService : PlatformAppService, IForumAppService
};
}
}