152 lines
7.3 KiB
C#

using Marco.Pms.DataAccess.Data;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Services.Helpers;
using MarcoBMS.Services.Service;
using Microsoft.EntityFrameworkCore;
namespace MarcoBMS.Services.Helpers
{
public class RolesHelper
{
private readonly ApplicationDbContext _context;
private readonly CacheUpdateHelper _cache;
private readonly ILoggingService _logger;
public RolesHelper(ApplicationDbContext context, CacheUpdateHelper cache, ILoggingService logger)
{
_context = context;
_cache = cache;
_logger = logger;
}
/// <summary>
/// Retrieves a unique list of enabled feature permissions for a given employee.
/// This method is optimized to use a single, composed database query.
/// </summary>
/// <param name="EmployeeId">The ID of the employee.</param>
/// <returns>A distinct list of FeaturePermission objects the employee is granted.</returns>
public async Task<List<FeaturePermission>> GetFeaturePermissionByEmployeeId(Guid EmployeeId)
{
_logger.LogInfo("Fetching feature permissions for EmployeeId: {EmployeeId}", EmployeeId);
try
{
// --- Step 1: Define the subquery for the employee's roles ---
// This is an IQueryable, not a list. It will be composed directly into the main query
// by Entity Framework, avoiding a separate database call.
var employeeRoleIdsQuery = _context.EmployeeRoleMappings
.Where(erm => erm.EmployeeId == EmployeeId && erm.IsEnabled == true)
.Select(erm => erm.RoleId);
// --- Step 2: Asynchronously update the cache in the background (Fire and Forget) ---
// This task is started but not awaited. The main function continues immediately,
// reducing latency. The cache will be updated eventually without blocking the user.
_ = Task.Run(async () =>
{
try
{
var roleIds = await employeeRoleIdsQuery.ToListAsync(); // Execute the query for the cache
if (roleIds.Any())
{
await _cache.AddApplicationRole(EmployeeId, roleIds);
_logger.LogInfo("Successfully queued cache update for EmployeeId: {EmployeeId}", EmployeeId);
}
}
catch (Exception ex)
{
// Log errors from the background task so they are not lost.
_logger.LogWarning("Background cache update failed for EmployeeId {EmployeeId} : {Error}", EmployeeId, ex.Message);
}
});
// --- Step 3: Execute the main query to get permissions in a single database call ---
// This single, efficient query gets all the required data at once.
var permissions = await (
from rpm in _context.RolePermissionMappings
join fp in _context.FeaturePermissions.Include(f => f.Feature) // Include related Feature data
on rpm.FeaturePermissionId equals fp.Id
// The 'employeeRoleIdsQuery' subquery is seamlessly integrated here by EF Core,
// resulting in a SQL "IN (SELECT ...)" clause.
where employeeRoleIdsQuery.Contains(rpm.ApplicationRoleId) && fp.IsEnabled == true
select fp)
.Distinct() // Ensures each permission is returned only once
.ToListAsync();
_logger.LogInfo("Successfully retrieved {PermissionCount} unique permissions for EmployeeId: {EmployeeId}", permissions.Count, EmployeeId);
return permissions;
}
catch (Exception ex)
{
_logger.LogError("An error occurred while fetching permissions for EmployeeId {EmployeeId} :{Error}", EmployeeId, ex.Message);
// Depending on your application's error handling strategy, you might re-throw,
// or return an empty list to prevent downstream failures.
return new List<FeaturePermission>();
}
}
public async Task<List<FeaturePermission>> GetFeaturePermissionByRoleID1(Guid roleId)
{
List<Guid> roleMappings = await _context.RolePermissionMappings.Where(c => c.ApplicationRoleId == roleId).Select(c => c.ApplicationRoleId).ToListAsync();
// _context.RolePermissionMappings
var result = await (from rpm in _context.RolePermissionMappings.Where(c => c.ApplicationRoleId == roleId)
join fp in _context.FeaturePermissions.Where(c => c.IsEnabled == true).Include(fp => fp.Feature) // Include Feature
on rpm.FeaturePermissionId equals fp.Id
select fp)
.ToListAsync();
return result;
// return null;
}
/// <summary>
/// Retrieves a unique list of enabled feature permissions for a given role.
/// This method is optimized to fetch all data in a single, efficient database query.
/// </summary>
/// <param name="roleId">The ID of the role.</param>
/// <returns>A distinct list of FeaturePermission objects granted to the role.</returns>
public async Task<List<FeaturePermission>> GetFeaturePermissionByRoleID(Guid roleId)
{
_logger.LogInfo("Fetching feature permissions for RoleID: {RoleId}", roleId);
try
{
// This single, efficient query gets all the required data at once.
// It joins the mapping table to the permissions table and filters by the given roleId.
var permissions = await (
// 1. Start with the linking table.
from rpm in _context.RolePermissionMappings
// 2. Join to the FeaturePermissions table on the foreign key.
join fp in _context.FeaturePermissions on rpm.FeaturePermissionId equals fp.Id
// 3. Apply all filters in one 'where' clause for clarity and efficiency.
where
rpm.ApplicationRoleId == roleId // Filter by the specific role
&& fp.IsEnabled == true // And only get enabled permissions
// 4. Select the final FeaturePermission object.
select fp)
.Include(fp => fp.Feature)
.Distinct()
.ToListAsync();
_logger.LogInfo("Successfully retrieved {PermissionCount} unique permissions for RoleID: {RoleId}", permissions.Count, roleId);
return permissions;
}
catch (Exception ex)
{
_logger.LogError("An error occurred while fetching permissions for RoleId {RoleId}: {Error}", roleId, ex.Message);
// Return an empty list as a safe default to prevent downstream failures.
return new List<FeaturePermission>();
}
}
}
}