GraphQL-NetCore:覆写Where子句参数构建合法SQL表达式
在GraphQL .NET Core中改写加密用户ID的过滤表达式
核心思路
要实现拦截并改写IQueryable的表达式树,核心是通过ExpressionVisitor遍历并替换过滤条件,同时让EF Core能识别解密逻辑并生成合法SQL。具体分三步:注册数据库解密函数、编写表达式树改写器、在GraphQL查询或EF Core全局拦截中应用。
步骤1:注册数据库解密函数到EF Core
首先要让EF Core知道解密逻辑对应的数据库函数,这样才能把C#中的解密方法调用转换成SQL语句。假设数据库中有一个名为DecryptValue的解密函数(比如SQL Server的自定义CLR函数或内置解密函数):
// 静态辅助类,仅用于EF Core的SQL转换标记,不会在内存执行 public static class EncryptionHelper { public static string Decrypt(string encryptedValue) { throw new NotImplementedException("仅用于EF Core生成SQL,请勿直接调用"); } } // 在DbContext的OnModelCreating中注册函数映射 protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.HasDbFunction(typeof(EncryptionHelper).GetMethod(nameof(EncryptionHelper.Decrypt))!) .HasName("DecryptValue"); // 对应数据库中的解密函数名称 }
步骤2:编写表达式树访问器
继承ExpressionVisitor,定位到EncryptedUserId == 参数的条件,将其替换为UserId == Decrypt(参数):
public class EncryptedUserIdRewriter : ExpressionVisitor { private readonly ParameterExpression _entityParam; public EncryptedUserIdRewriter(ParameterExpression entityParam) { _entityParam = entityParam; } protected override Expression VisitBinary(BinaryExpression node) { // 匹配"实体.EncryptedUserId == 加密值参数"的相等条件 if (node.NodeType == ExpressionType.Equal && node.Left is MemberExpression leftMember && leftMember.Member.Name == nameof(YourEntity.EncryptedUserId) && leftMember.Expression == _entityParam) { // 替换为"实体.UserId == Decrypt(加密值参数)" var userIdProperty = Expression.Property(_entityParam, nameof(YourEntity.UserId)); var decryptCall = Expression.Call( typeof(EncryptionHelper).GetMethod(nameof(EncryptionHelper.Decrypt))!, node.Right); return Expression.Equal(userIdProperty, decryptCall); } return base.VisitBinary(node); } }
步骤3:在GraphQL查询中应用改写器
以GraphQL.NET的Resolve方法为例,处理客户端传入的encryptedUserId参数时,改写过滤表达式:
public class YourQuery : ObjectGraphType { public YourQuery(YourDbContext dbContext) { Field<ListGraphType<YourEntityGraphType>>("entities") .Argument<StringGraphType>("encryptedUserId") .Resolve(context => { var encryptedUserId = context.GetArgument<string>("encryptedUserId"); var query = dbContext.YourEntities.AsQueryable(); if (!string.IsNullOrWhiteSpace(encryptedUserId)) { // 构建原始过滤表达式 Expression<Func<YourEntity, bool>> originalFilter = e => e.EncryptedUserId == encryptedUserId; // 改写表达式 var rewriter = new EncryptedUserIdRewriter(originalFilter.Parameters[0]); var rewrittenBody = rewriter.Visit(originalFilter.Body); var rewrittenFilter = Expression.Lambda<Func<YourEntity, bool>>(rewrittenBody, originalFilter.Parameters); // 应用改写后的过滤条件 query = query.Where(rewrittenFilter); } return query; }); } }
进阶:全局EF Core查询拦截
如果要让所有涉及EncryptedUserId的过滤都自动改写,可以用EF Core的查询拦截器,无需在每个GraphQL Resolve中单独处理:
public class EncryptedQueryInterceptor : IQueryExpressionInterceptor { public Expression QueryCompilationStarting(Expression queryExpression, QueryExpressionEventData eventData) { // 自定义一个能遍历所有参数的访问器(可基于上面的改写器调整) var globalRewriter = new GlobalEncryptedUserIdRewriter(); return globalRewriter.Visit(queryExpression); } } // 在Program.cs注册拦截器 builder.Services.AddDbContext<YourDbContext>(options => options.UseSqlServer("你的连接字符串") .AddInterceptors(new EncryptedQueryInterceptor()));
注意事项
- 确保数据库中的解密函数与EF注册的函数名、参数完全匹配,否则会生成无效SQL。
- 如果使用GraphQL的通用过滤输入(比如FilterInput),需要在解析过滤表达式的环节注入改写逻辑(比如自定义过滤提供者)。
- 若加密逻辑需要密钥,要确保数据库函数能正确获取密钥(比如SQL Server的
OPEN SYMMETRIC KEY)。
内容的提问来源于stack exchange,提问作者m1ha1
相关产品推荐
相关产品推荐

