用于过滤嵌套集合属性的动态表达式树

我正在使用entity framework并动态地使用导航属性构建查询。

对于我的大多数用例,以下工作正常:

private static MethodCallExpression GetNavigationPropertyExpression(string propertyName, int test, ParameterExpression parameter, string subParameter) { var navigationPropertyCollection = Expression.Property(parameter, propertyName); var childType = navigationPropertyCollection.Type.GetGenericArguments()[0]; var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(childType); var aclAttribute = GetAclAttribute(typeof(T), propertyName); var childProperty = aclAttribute.ChildProperty; var propertyCollectionGenericArg = childType; var serviceLocationsParam = Expression.Parameter(propertyCollectionGenericArg, subParameter); var left = Expression.Property(serviceLocationsParam, childProperty); var right = Expression.Constant(test, typeof(int)); var isEqual = Expression.Equal(left, right); var subLambda = Expression.Lambda(isEqual, serviceLocationsParam); var resultExpression = Expression.Call(anyMethod, navigationPropertyCollection, subLambda); return resultExpression; } 

我使用通过元数据类型和部分类分配给属性的自定义AclAttribute类。 对于导航属性,提供了ChildProperty,以便表达式构建器知道更深入地查找所需的属性。

例如:表Services引用另一个名为ServiceLocations的表。 我需要ServiceLocations引用中的LocationId值。 这部分工作正常。

我的问题是当有超过1个嵌套属性时。 另一个例子:表ServiceCategories引用服务,它再次引用ServiceLocations。 像以前一样,我需要来自ServiceLocations的LocationId值。

我通过使用两个“Any”方法手动完成此操作,组合并返回结果表达式,但有时候导航属性不是集合,或者导航属性的子节点可能是集合。 对于那些情况,我需要某种递归选项。 我现在已经尝试了一段时间,并且做得很短。

自从我提到它以来,这是我为第二个例子放在一起的测试方法。 这有效,但我违反了DRY。 (注意:我没有给表格命名):

 private static MethodCallExpression GetNestedNavigationPropertyExpression(int test, ParameterExpression rootParameter) { var servicesProperty = Expression.Property(rootParameter, "tblServices"); var servicesParameter = Expression.Parameter(servicesProperty.Type.GetGenericArguments()[0], "ss"); var serviceLocationsProperty = Expression.Property(servicesParameter, "tblServiceLocations"); var serviceLocationsParameter = Expression.Parameter(serviceLocationsProperty.Type.GetGenericArguments()[0], "s"); var servicesAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(servicesProperty.Type.GetGenericArguments()[0]); var serviceLocationsAnyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(serviceLocationsProperty.Type.GetGenericArguments()[0]); var aclAttribute = GetAclAttribute(typeof(tblService), "tblServiceLocations"); var left = Expression.Property(serviceLocationsParameter, aclAttribute.ChildProperty); var right = Expression.Constant(test, typeof(int)); var isEqual = Expression.Equal(left, right); var subLambda = Expression.Lambda(isEqual, serviceLocationsParameter); var endExpression = Expression.Call(serviceLocationsAnyMethod, serviceLocationsProperty, subLambda); var intermediaryLamba = Expression.Lambda(endExpression, servicesParameter); var resultExpression = Expression.Call(servicesAnyMethod, servicesProperty, intermediaryLamba); return resultExpression; } 

有了它,应该可以构建您的查询:

 public static Expression GetNavigationPropertyExpression(Expression parameter, int test, params string[] properties) { Expression resultExpression = null; Expression childParameter, navigationPropertyPredicate; Type childType = null; if (properties.Count() > 1) { //build path parameter = Expression.Property(parameter, properties[0]); var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type); //if it´sa collection we later need to use the predicate in the methodexpressioncall if (isCollection) { childType = parameter.Type.GetGenericArguments()[0]; childParameter = Expression.Parameter(childType, childType.Name); } else { childParameter = parameter; } //skip current property and get navigation property expression recursivly var innerProperties = properties.Skip(1).ToArray(); navigationPropertyPredicate = GetNavigationPropertyExpression(childParameter, test, innerProperties); if (isCollection) { //build methodexpressioncall var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2); anyMethod = anyMethod.MakeGenericMethod(childType); navigationPropertyPredicate = Expression.Call(anyMethod, parameter, navigationPropertyPredicate); resultExpression = MakeLambda(parameter, navigationPropertyPredicate); } else { resultExpression = navigationPropertyPredicate; } } else { //Formerly from ACLAttribute var childProperty = parameter.Type.GetProperty(properties[0]); var left = Expression.Property(parameter, childProperty); var right = Expression.Constant(test, typeof(int)); navigationPropertyPredicate = Expression.Equal(left, right); resultExpression = MakeLambda(parameter, navigationPropertyPredicate); } return resultExpression; } private static Expression MakeLambda(Expression parameter, Expression predicate) { var resultParameterVisitor = new ParameterVisitor(); resultParameterVisitor.Visit(parameter); var resultParameter = resultParameterVisitor.Parameter; return Expression.Lambda(predicate, (ParameterExpression)resultParameter); } private class ParameterVisitor : ExpressionVisitor { public Expression Parameter { get; private set; } protected override Expression VisitParameter(ParameterExpression node) { Parameter = node; return node; } } 

称之为:

 var parameter = Expression.Parameter(typeof(A), "A"); var expression = ExpressionBuilder.GetNavigationPropertyExpression(parameter, 8,"CollectionOfB", "CollectionOfC", "ID");