C#反射构建支持集合Select投影的属性选择表达式问题咨询
反射构建含集合Select的属性路径表达式方案
原有逐段拼接成员访问的实现不需要完全推翻,只需要补充路径分段规则和方法调用解析分支,即可兼容普通属性访问和集合Select调用场景,改造成本极低。
核心调整点
- 替换原有的
Split('.')硬拆分逻辑:新增路径分段方法,跳过括号内部的.,避免把Select lambda内部的属性路径点误拆成独立段,保证方法调用整体作为一个解析单元。 - 在原有的聚合拼接逻辑中增加分支判断:普通无括号的段走原有
Expression.PropertyOrField逻辑,完全兼容已实现的普通属性路径场景;带括号的段识别为方法调用,单独解析生成方法调用表达式。
实现代码
1. 路径分段辅助方法
static IEnumerable<string> SplitPropertyPath(string path) { int bracketDepth = 0; int segmentStart = 0; for (int i = 0; i < path.Length; i++) { char c = path[i]; if (c == '(') bracketDepth++; else if (c == ')') bracketDepth--; else if (c == '.' && bracketDepth == 0) { yield return path.Substring(segmentStart, i - segmentStart); segmentStart = i + 1; } } yield return path.Substring(segmentStart); }
2. 核心表达式构建逻辑
Type entityType = Type.GetType("NameSpacePath.MyEntity"); ParameterExpression xParameter = Expression.Parameter(entityType, "x"); string propertyPath = "ReferenceProperty.SubReference.PropertyList.Select(y => y.Id)"; Expression body = SplitPropertyPath(propertyPath).Aggregate((Expression)xParameter, (currentExpr, segment) => { // 普通成员访问,复用原有逻辑 if (!segment.Contains('(')) { return Expression.PropertyOrField(currentExpr, segment); } // 解析方法调用段 int bracketStartIdx = segment.IndexOf('('); string methodName = segment.Substring(0, bracketStartIdx); string methodInnerContent = segment.Substring(bracketStartIdx + 1, segment.Length - bracketStartIdx - 2).Trim(); if (methodName == nameof(Enumerable.Select)) { // 拆分lambda的参数名和body路径 var lambdaParts = methodInnerContent.Split("=>", 2, StringSplitOptions.TrimEntries); string lambdaParamName = lambdaParts[0]; string lambdaBodyPath = lambdaParts[1]; // 取当前集合的元素类型 Type elementType = currentExpr.Type.GetGenericArguments().FirstOrDefault() ?? throw new ArgumentException($"表达式段{segment}对应的类型不是支持的泛型集合"); // 递归复用属性路径构建逻辑生成lambda body ParameterExpression lambdaParam = Expression.Parameter(elementType, lambdaParamName); Expression lambdaBody = lambdaBodyPath.Split('.').Aggregate((Expression)lambdaParam, Expression.PropertyOrField); LambdaExpression selector = Expression.Lambda(lambdaBody, lambdaParam); // 反射取Enumerable.Select的泛型重载,生成方法调用 MethodInfo selectGenericMethod = typeof(Enumerable).GetMethods() .First(m => m.Name == nameof(Enumerable.Select) && m.GetParameters().Length == 2) .MakeGenericMethod(elementType, lambdaBody.Type); return Expression.Call(null, selectGenericMethod, currentExpr, selector); } throw new NotSupportedException($"暂不支持的方法调用:{methodName}"); });
扩展说明
如果需要支持Select(y => new { Id = y.Id })这类匿名类型投影,只需要在lambda body解析部分增加逻辑:识别new { ... }结构,解析内部的属性赋值映射,通过Expression.New构造匿名类型的初始化表达式,替换当前直接拼接属性路径的lambda body生成逻辑即可,整体解析框架不需要改动。如果后续需要支持Where、OrderBy等其他LINQ方法,也可以按照Select的处理分支扩展,新增对应的方法解析逻辑即可。
内容的提问来源于stack exchange,提问作者Mulaga
相关产品推荐
相关产品推荐

