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

时间:2014-03-26 20:13:43

标签: c# linq entity-framework-5

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

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

private static MethodCallExpression GetNavigationPropertyExpression<T>(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;
    }

1 个答案:

答案 0 :(得分:9)

有了它,应该可以建立你的查询:

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´s a 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");