如何通过反射基于ChildAttribute构建指定的属性Where过滤表达式?
动态构建带过滤的属性访问表达式(基于反射和特性)
要实现通过反射构建和你手动写的完全一致的表达式,咱们可以通过反射读取特性信息 + 表达式树构建来完成,下面是具体的实现步骤和代码:
1. 先确认基础定义(确保特性与实体类可用)
首先得保证你的ChildAttribute和相关类的定义没问题,比如:
public enum FilterType { Equal } public class ChildAttribute : Attribute { public FilterType FilterType { get; } public string PropertyName { get; } public object Value { get; } public ChildAttribute(FilterType filterType, string propertyName, object value) { FilterType = filterType; PropertyName = propertyName; Value = value; } } public class BaseEntity { } public class ItemFileLink { public int ParentTypeId { get; set; } // 其他业务属性... }
2. 核心实现:反射+表达式树构建目标表达式
咱们一步步拆解实现逻辑:
步骤1:获取目标属性与特性信息
先通过反射找到AircraftFlight类里的Files属性,再读取它上面的ChildAttribute配置:
// 目标实体类型 Type entityType = typeof(AircraftFlight); // 反射获取Files属性 PropertyInfo filesProperty = entityType.GetProperty(nameof(AircraftFlight.Files)) ?? throw new InvalidOperationException("未找到Files属性"); // 获取属性上绑定的ChildAttribute ChildAttribute childAttr = filesProperty.GetCustomAttribute<ChildAttribute>() ?? throw new InvalidOperationException("Files属性未标记ChildAttribute");
步骤2:拼接表达式树的各个节点
接下来我们要手动拼接出i => i.Files.Where(f => f.ParentTypeId == 13)这个表达式的每一部分:
① 创建实体参数(对应表达式里的i)
ParameterExpression entityParam = Expression.Parameter(entityType, "i");
② 构建属性访问表达式(对应i.Files)
MemberExpression filesAccessExpr = Expression.Property(entityParam, filesProperty);
③ 构建Where方法的过滤Lambda(对应f => f.ParentTypeId == 13)
先处理集合元素的过滤逻辑:
// 获取集合元素类型(这里是ItemFileLink) Type elementType = typeof(ItemFileLink); // 创建过滤Lambda的参数(对应`f`) ParameterExpression elementParam = Expression.Parameter(elementType, "f"); // 构建`f.ParentTypeId`的属性访问表达式 MemberExpression filterPropertyExpr = Expression.Property(elementParam, childAttr.PropertyName); // 构建常量值(确保类型与属性匹配) ConstantExpression filterValueExpr = Expression.Constant( Convert.ChangeType(childAttr.Value, filterPropertyExpr.Type), filterPropertyExpr.Type); // 构建相等判断表达式(对应`f.ParentTypeId == 13`) BinaryExpression filterConditionExpr = Expression.Equal(filterPropertyExpr, filterValueExpr); // 把条件包装成完整的Lambda表达式 Expression<Func<ItemFileLink, bool>> filterLambda = Expression.Lambda<Func<ItemFileLink, bool>>(filterConditionExpr, elementParam);
④ 构建Where方法的调用表达式
因为Where是Enumerable类的扩展方法,需要反射获取方法定义并构建调用:
// 获取Enumerable.Where的泛型方法模板 MethodInfo whereMethodDef = typeof(Enumerable).GetMethods() .First(m => m.Name == nameof(Enumerable.Where) && m.GetParameters().Length == 2); // 实例化泛型方法(指定元素类型) MethodInfo whereMethod = whereMethodDef.MakeGenericMethod(elementType); // 构建Where方法调用:Files.Where(filterLambda) MethodCallExpression whereCallExpr = Expression.Call( whereMethod, filesAccessExpr, filterLambda);
⑤ 包装成最终的目标表达式
// 最终得到和手动编写完全一致的表达式 Expression<Func<AircraftFlight, IEnumerable<ItemFileLink>>> finalExpression = Expression.Lambda<Func<AircraftFlight, IEnumerable<ItemFileLink>>>(whereCallExpr, entityParam);
3. 验证效果
你可以编译表达式并执行,验证和手动写的逻辑是否一致:
// 编译成可执行委托 var func = finalExpression.Compile(); // 测试数据 var testFlight = new AircraftFlight { Files = new List<ItemFileLink> { new ItemFileLink { ParentTypeId = 13 }, new ItemFileLink { ParentTypeId = 14 } } }; // 执行过滤 var filteredFiles = func(testFlight); // 结果应该只包含ParentTypeId=13的元素
额外优化建议
- 如果你的
FilterType有更多类型(比如大于、小于),可以扩展条件表达式的构建逻辑(比如用Expression.GreaterThan等) - 可以把这段逻辑封装成通用方法,支持任意实体类型和带有
ChildAttribute的属性 - 增加类型安全检查,避免特性配置的属性名或值类型不匹配的问题
内容的提问来源于stack exchange,提问作者Evgenii Babak
相关产品推荐
相关产品推荐

