如何在EF Core 7中使用IQueryExpressionInterceptor注入Where子句?
如何通过IQueryExpressionInterceptor注入Where子句?
我正尝试使用新的IQueryExpressionInterceptor接口,将Where子句注入到所有Select查询中。官方示例是给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>>) });返回null。 - 谓词表达式构建困难:不清楚如何正确创建
lambdaExpression.OwnerId == userId这样的表达式。
后续更新
- 关于全局过滤器:了解过EF Core的全局过滤器,但不适合当前场景——需要先判断用户角色,再决定是否应用过滤规则。
- 需求明确:拦截器需要实现的效果是,将
DbContext.MyEntity转换为DbContext.MyEntity.Where(entity => entity.OwnerId == userId),且仅在用户不具备Manager角色时生效。角色判断逻辑已完成,当前问题仅集中在表达式构建环节。 - 当前代码状态:更新后的代码如下,但仍存在DTO类型与数据库实体类型冲突的问题,整体实现复杂度较高。打算先采用更简单的方案(比如使用DbCommandInterceptor修改生成的查询字符串),之后再回来解决表达式树的问题。
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 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 var userIdString = user.GetHomeObjectId(); if (userIdString is null) { throw new UnauthorizedAccessException($"User ID not found during DB query interception by {nameof(UserAccessFilteringExpressionInterceptor)}!"); } var userId = Guid.Parse(userIdString); //Check if method is Select 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(TopicResumeDGDTO).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 method = WhereMethodInfo.MakeGenericMethod(sourceType); var arg1 = Expression.Lambda(delegateType: typeof(Func<,>).MakeGenericType(typeof(TopicResume), typeof(bool)), //Create Func<Dto, bool> body: Expression.Equal(left: Expression.MakeMemberAccess(entityParameterExpression, typeof(TopicResume).GetMember(nameof(TopicResume.AadOwnerId)).Single()), right: Expression.Convert(Expression.Constant(userId), typeof(Guid?))), tailCall: true, parameters: entityParameterExpression); var test = Expression.Call( method: method, arg0: base.VisitMethodCall(node), arg1: arg1); return test; } } return base.VisitMethodCall(node); }
内容的提问来源于stack exchange,提问作者Luk164
相关产品推荐
相关产品推荐

