dimanche 28 août 2022

IQueryable dynamic recursive expression

Fast Question: How to rewrite this

builder.Entity<User>().HasQueryFilter(s =>
            httpContextAccessor.HttpContext.User.GetGroup() == s.Institute ||
            httpContextAccessor.HttpContext.User.IsInRole(ERole.Root.ToString()));

from model builder layer to bussines and logic layer with all includes.

Description and problem itself:

I have this interface

public interface IInstituteEntity
{
    public string Institute { get; set; }
}

And I want to get all entities from database where entity itself or ANY of it's properties(recursive: childs of childs) has Institute property that equals my filter OR user has role "Root" (notice that properties can not be included and can be empty/null as well)

I'm not strong with Expression Trees and can not find suitable solution, I have basic example without recursion:

public static IQueryable<T> HasInstituteAccess<T>(this IQueryable<T> entities,
        IHttpContextAccessor accessor) where T : IInstituteEntity
    {
        var user = accessor.HttpContext?.User;
        if (user is null) return Enumerable.Empty<T>().AsQueryable();
        
        return user.IsInRole(ERole.Root.ToString())
            ? entities.Cast<T>()
            : entities.Where(HasInstituteAccessFilter<T>(user)).Cast<T>();
    }

private static Expression<Func<T, bool>> HasInstituteAccessFilter<T>(ClaimsPrincipal principal)
        where T : IInstituteEntity
        => arg => principal.GetGroup() == arg.Institute;

I need it as workload about global filters that covers includes, that's the thing:

builder.Entity<User>().HasQueryFilter(s =>
            httpContextAccessor.HttpContext.User.GetGroup() == s.Institute ||
            httpContextAccessor.HttpContext.User.IsInRole(ERole.Root.ToString()));

But it not suits me because I need to manualy specify IgnoreQueryFilters() for all queries that have no need in this logic, so it's better to write in manually via extensions above

Expected query:

var userInfo =
            await context.UserInfo.AsNoTracking()
                .Where(ui => !ui.IsDeleted)
                .Include(ui => ui.User)
                .HasInstituteAccess(accessor)
                .FirstOrDefaultAsync(ui => ui.Id == request.Id, cancellationToken);

Expected result: return some info if authorized user has access to ui.User.Institute (UserInfo has no institute property)





Aucun commentaire:

Enregistrer un commentaire