如何在 EF Core 7 中使用 IQueryExpressionInterceptor 将 Where 子句注入查询?

问题描述 投票:0回答:1

我正在尝试使用新的 IQueryExpressionInterceptor 接口将

Where
子句注入到我的所有
Select
查询中,遵循以下示例:https://learn.microsoft.com/en-us/ef/core/what-is -new/ef-core-7.0/whatsnew#linq-expression-tree-interception

示例是针对 OrderBy 之后的 ThenBy 子句,但我需要 Select 之后的 Where 子句。这是我取得的成就:

public class UserAccessFilteringExpressionInterceptor : IQueryExpressionInterceptor
{
    private AuthenticationStateProvider AuthStateProvider { get; }
    public IAuthorizationService AuthorizationService { get; }
    public UserAccessFilteringExpressionInterceptor(AuthenticationStateProvider authStateProvider, IAuthorizationService authorizationService)
    {
        AuthStateProvider = authStateProvider;
        AuthorizationService = authorizationService;
    }

    public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData)
        => new KeyOrderingExpressionVisitor(AuthStateProvider, AuthorizationService).Visit(queryExpression);

    private sealed class KeyOrderingExpressionVisitor : ExpressionVisitor
    {
        private AuthenticationStateProvider AuthStateProvider { get; set; }
        private IAuthorizationService AuthorizationService { get; }

        public KeyOrderingExpressionVisitor(AuthenticationStateProvider authStateProvider, IAuthorizationService authorizationService)
        {
            AuthStateProvider = authStateProvider;
            AuthorizationService = authorizationService;
        }

        private static readonly MethodInfo WhereMethodInfo = typeof(Queryable).GetMethods().First(m => m.Name == nameof(Queryable.Where));

        protected override Expression VisitMethodCall(MethodCallExpression? node)
        {
            var whereMethods = typeof(Queryable).GetMethods().Where(m => m.Name == nameof(Queryable.Where));

            var user = AuthStateProvider.GetAuthenticationStateAsync().Result.User;
            var userId = user.GetHomeObjectId();
            var manager = AuthorizationService.AuthorizeAsync(user, "RequireManagerRole").Result;

            var methodInfo = node!.Method;
            //Skip filtering if user is a manager
            //if (manager.Succeeded) return base.VisitMethodCall(node); //TODO: Re-enable this

            //Check if method is OrderBy
            if (methodInfo.DeclaringType == typeof(Queryable)
                && methodInfo.Name == nameof(Queryable.Select)
                && methodInfo.GetParameters().Length == 2)
            {
                //Get first argument for the node type
                var sourceType = node.Type.GetGenericArguments()[0];

                if (typeof(BaseDTO).IsAssignableFrom(sourceType))
                {
                    //Extract lambda expression of Select
                    var lambdaExpression = (LambdaExpression)((UnaryExpression)node.Arguments[1]).Operand;
                    //Extract parameter of the lambda expression Select(e => ...)
                    var entityParameterExpression = lambdaExpression.Parameters[0];
                    var test = Expression.Call(
                        method: WhereMethodInfo.MakeGenericMethod(sourceType, typeof(bool)),
                        arg0: base.VisitMethodCall(node),
                        arg1: Expression.Lambda(typeof(Func<,>).MakeGenericType(entityParameterExpression.Type, typeof(bool)), //Create Func<Dto, bool>
                            Expression.Property(entityParameterExpression, nameof(BaseDTO.Id)),
                            true));
                    return test;
                }
            }

            return base.VisitMethodCall(node);
        }
    }
}

第一个问题是为 Where 检索正确的 MethodInfo。该示例通过简单地计算输入来区分重载,但是 Where 有两个具有相同计数的重载。我试过

typeof(Queryable).GetMethod(nameof(Queryable.Where), new Type[] { typeof(IQueryable<object>), typeof(Expression<Func<object,bool>>) });
但它给了我空。我也不明白如何创建新的谓词
lambdaExpression.OwnerId == userId
.

c# reflection entity-framework-core authorization interceptor
1个回答
0
投票

Queryable.Where
是泛型方法,所以需要匹配泛型参数,例如使用
Type.MakeGenericMethodParameter

var methodInfo = typeof(Queryable)
    .GetMethod(nameof(Queryable.Where), 
        new []
        {
            typeof(IQueryable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), 
            typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(Type.MakeGenericMethodParameter(0), typeof(bool)))
        });
© www.soinside.com 2019 - 2024. All rights reserved.