我正在尝试使用新的 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
.
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)))
});