From 8014d9df340db3db988f36cddced73c38f0424ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Wed, 4 Feb 2026 21:39:59 +0300 Subject: [PATCH] Blog, Audit ve Forum AppService performans --- .../AuditLogs/AuditLogAppService.cs | 24 +++- .../Blog/BlogAppService.cs | 52 +++++--- .../CustomEndpointAppService.cs | 1 - .../Forum/ForumAppService.cs | 119 +++++++++++------- 4 files changed, 128 insertions(+), 68 deletions(-) diff --git a/api/src/Erp.Platform.Application/AuditLogs/AuditLogAppService.cs b/api/src/Erp.Platform.Application/AuditLogs/AuditLogAppService.cs index 1feb2164..fc6f6d39 100644 --- a/api/src/Erp.Platform.Application/AuditLogs/AuditLogAppService.cs +++ b/api/src/Erp.Platform.Application/AuditLogs/AuditLogAppService.cs @@ -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> 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(); - foreach (var item in auditLogList) + // Mapping tek seferde yap + var entityDtos = ObjectMapper.Map, List>(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( diff --git a/api/src/Erp.Platform.Application/Blog/BlogAppService.cs b/api/src/Erp.Platform.Application/Blog/BlogAppService.cs index 6c84c24a..06a42e44 100644 --- a/api/src/Erp.Platform.Application/Blog/BlogAppService.cs +++ b/api/src/Erp.Platform.Application/Blog/BlogAppService.cs @@ -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(post); - // Get category - dto.Category = ObjectMapper.Map( - 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(category); + } return dto; } + [UnitOfWork] public async Task 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 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(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(category); } diff --git a/api/src/Erp.Platform.Application/CustomEndpoints/CustomEndpointAppService.cs b/api/src/Erp.Platform.Application/CustomEndpoints/CustomEndpointAppService.cs index 3460d2ee..08cc8972 100644 --- a/api/src/Erp.Platform.Application/CustomEndpoints/CustomEndpointAppService.cs +++ b/api/src/Erp.Platform.Application/CustomEndpoints/CustomEndpointAppService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading.Tasks; using Erp.Platform.DynamicData; diff --git a/api/src/Erp.Platform.Application/Forum/ForumAppService.cs b/api/src/Erp.Platform.Application/Forum/ForumAppService.cs index f82000e0..e940adfe 100644 --- a/api/src/Erp.Platform.Application/Forum/ForumAppService.cs +++ b/api/src/Erp.Platform.Application/Forum/ForumAppService.cs @@ -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(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(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(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(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(topic); } + [UnitOfWork] public async Task 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(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(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(post); } + [UnitOfWork] public async Task 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(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(post); } @@ -475,34 +493,46 @@ public class ForumAppService : PlatformAppService, IForumAppService await _categoryRepository.UpdateAsync(category); } + [UnitOfWork] public async Task 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'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 = postsInTopic.Sum(p => p.LikeCount ?? 0); - await _topicRepository.UpdateAsync(topic); + topic.LikeCount = totalLikes; + await _topicRepository.UpdateAsync(topic, autoSave: true); return ObjectMapper.Map(post); } + [UnitOfWork] public async Task 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(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(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(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(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(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(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(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(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(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(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(topic); } @@ -615,4 +645,3 @@ public class ForumAppService : PlatformAppService, IForumAppService }; } } -