You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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这样的表达式。

后续更新

  1. 关于全局过滤器:了解过EF Core的全局过滤器,但不适合当前场景——需要先判断用户角色,再决定是否应用过滤规则。
  2. 需求明确:拦截器需要实现的效果是,将DbContext.MyEntity转换为DbContext.MyEntity.Where(entity => entity.OwnerId == userId),且仅在用户不具备Manager角色时生效。角色判断逻辑已完成,当前问题仅集中在表达式构建环节。
  3. 当前代码状态:更新后的代码如下,但仍存在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 05:12:20