Performance: push ORDER BY/TAKE into SQL for hot-path reads
- IInAppNotificationRepository: typed repo with GetPagedAsync, GetRecentAsync, GetUnreadAsync — bell dropdown no longer loads all notifications then slices in C# - Add compound indexes on InAppNotifications(CompanyId, IsDeleted, CreatedAt) and (CompanyId, IsDeleted, IsRead); ContactSubmissions(CompanyId, IsDeleted, CreatedAt) - PlainRepository.GetAllAsync/FindAsync: add AsNoTracking (Announcements, Tips, ReleaseNotes) - AiUsageReportController: replace GetAllAsync + C# Where with FindAsync (SQL-level filter) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,7 @@ public class AiUsageReportController : Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
var companies = (await _unitOfWork.Companies.GetAllAsync(ignoreQueryFilters: true))
|
||||
.Where(c => !c.IsDeleted)
|
||||
var companies = (await _unitOfWork.Companies.FindAsync(c => !c.IsDeleted, ignoreQueryFilters: true))
|
||||
.Select(c => new { c.Id, c.CompanyName, c.SubscriptionPlan, c.IsActive })
|
||||
.ToList();
|
||||
|
||||
|
||||
@@ -18,92 +18,88 @@ public class InAppNotificationsController : Controller
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays the paginated notification history page (read and unread). SuperAdmins see only platform-level notifications (CompanyId 0) via IgnoreQueryFilters; regular users rely on the global query filter for tenant isolation.
|
||||
/// Displays the paginated notification history page (read and unread). SuperAdmins see only
|
||||
/// platform-level notifications (CompanyId 0); regular users rely on the global query filter.
|
||||
/// ORDER BY / SKIP / TAKE run in SQL via GetPagedAsync — no full-table load.
|
||||
/// </summary>
|
||||
public async Task<IActionResult> Index(int pageNumber = 1, int pageSize = 25)
|
||||
{
|
||||
pageNumber = Math.Max(1, pageNumber);
|
||||
pageSize = pageSize is 10 or 25 or 50 ? pageSize : 25;
|
||||
|
||||
var all = _tenant.IsPlatformAdmin()
|
||||
? (await _unitOfWork.InAppNotifications.FindAsync(
|
||||
n => !n.IsDeleted && n.CompanyId == 0, ignoreQueryFilters: true)).ToList()
|
||||
: (await _unitOfWork.InAppNotifications.GetAllAsync()).ToList();
|
||||
|
||||
var totalCount = all.Count;
|
||||
var isPlatformAdmin = _tenant.IsPlatformAdmin();
|
||||
var (items, totalCount) = await _unitOfWork.InAppNotifications
|
||||
.GetPagedAsync(isPlatformAdmin, pageNumber, pageSize);
|
||||
|
||||
var tz = ViewBag.CompanyTimeZone as string;
|
||||
var items = all
|
||||
.OrderByDescending(n => n.CreatedAt)
|
||||
.Skip((pageNumber - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
n.Title,
|
||||
n.Message,
|
||||
n.Link,
|
||||
n.NotificationType,
|
||||
n.IsRead,
|
||||
n.ReadAt,
|
||||
CreatedAt = n.CreatedAt
|
||||
})
|
||||
.ToList();
|
||||
var vm = items.Select(n => new
|
||||
{
|
||||
n.Id,
|
||||
n.Title,
|
||||
n.Message,
|
||||
n.Link,
|
||||
n.NotificationType,
|
||||
n.IsRead,
|
||||
n.ReadAt,
|
||||
CreatedAt = n.CreatedAt
|
||||
}).ToList();
|
||||
|
||||
ViewBag.TotalCount = totalCount;
|
||||
ViewBag.PageNumber = pageNumber;
|
||||
ViewBag.PageSize = pageSize;
|
||||
ViewBag.TotalPages = (int)Math.Ceiling((double)totalCount / pageSize);
|
||||
return View(items);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AJAX endpoint that returns the 20 most recent notifications (read and unread) for the bell dropdown.
|
||||
/// AJAX endpoint for the bell dropdown — returns the 20 most recent notifications.
|
||||
/// ORDER BY and TAKE run in SQL; no full-table load.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Recent()
|
||||
{
|
||||
var all = _tenant.IsPlatformAdmin()
|
||||
? (await _unitOfWork.InAppNotifications.FindAsync(
|
||||
n => !n.IsDeleted && n.CompanyId == 0, ignoreQueryFilters: true)).ToList()
|
||||
: (await _unitOfWork.InAppNotifications.GetAllAsync()).ToList();
|
||||
var isPlatformAdmin = _tenant.IsPlatformAdmin();
|
||||
var items = await _unitOfWork.InAppNotifications.GetRecentAsync(isPlatformAdmin, take: 20);
|
||||
|
||||
var tz = ViewBag.CompanyTimeZone as string;
|
||||
var items = all
|
||||
.OrderByDescending(n => n.CreatedAt)
|
||||
.Take(20)
|
||||
.Select(n => new { n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead, n.CreatedAt })
|
||||
.ToList();
|
||||
|
||||
var unreadCount = items.Count(n => !n.IsRead);
|
||||
return Json(new { count = unreadCount, items = items.Select(n => new {
|
||||
n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead,
|
||||
CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt")
|
||||
}) });
|
||||
return Json(new
|
||||
{
|
||||
count = unreadCount,
|
||||
items = items.Select(n => new
|
||||
{
|
||||
n.Id, n.Title, n.Message, n.Link, n.NotificationType, n.IsRead,
|
||||
CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt")
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AJAX endpoint that returns only unread notifications (up to 20) plus an unread count for the bell badge.
|
||||
/// AJAX endpoint for the bell badge — returns unread count plus up to 20 unread items.
|
||||
/// COUNT and ORDER BY / TAKE run in SQL; no full-table load.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Unread()
|
||||
{
|
||||
var items = _tenant.IsPlatformAdmin()
|
||||
? (await _unitOfWork.InAppNotifications.FindAsync(
|
||||
n => !n.IsDeleted && n.CompanyId == 0 && !n.IsRead, ignoreQueryFilters: true))
|
||||
.OrderByDescending(n => n.CreatedAt).Take(20).ToList()
|
||||
: (await _unitOfWork.InAppNotifications.FindAsync(n => !n.IsRead))
|
||||
.OrderByDescending(n => n.CreatedAt).Take(20).ToList();
|
||||
var isPlatformAdmin = _tenant.IsPlatformAdmin();
|
||||
var (items, unreadCount) = await _unitOfWork.InAppNotifications
|
||||
.GetUnreadAsync(isPlatformAdmin, take: 20);
|
||||
|
||||
var tz = ViewBag.CompanyTimeZone as string;
|
||||
return Json(new { count = items.Count, items = items.Select(n => new {
|
||||
n.Id, n.Title, n.Message, n.Link, n.NotificationType,
|
||||
CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt")
|
||||
}) });
|
||||
return Json(new
|
||||
{
|
||||
count = unreadCount,
|
||||
items = items.Select(n => new
|
||||
{
|
||||
n.Id, n.Title, n.Message, n.Link, n.NotificationType,
|
||||
CreatedAt = n.CreatedAt.Tz(tz).ToString("MMM d, h:mm tt")
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a single notification as read. Tenant isolation is enforced manually for SuperAdmins (CompanyId 0 filter) because IgnoreQueryFilters bypasses the global company filter.
|
||||
/// Marks a single notification as read. Tenant isolation is enforced manually for SuperAdmins
|
||||
/// (CompanyId 0 filter) because IgnoreQueryFilters bypasses the global company filter.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> MarkRead(int id)
|
||||
|
||||
Reference in New Issue
Block a user