سؤال

I am dynamically creating a LINQ query based on various search criteria.

As an example, let's say I am searching a Automobiles table, and I have an option to filter by ratings. I have two controls:

  1. Compare type: [At least], [At most], [Less than], [Greater than], and [Equal].
  2. Value: The value to compare the rating against.

So the user could, for example, select the compare type [At least] and the value 3, and my code needs to create a query that limits results to automobile ratings greater than or equal to 3.

I found a great solution given by VinayC in the question How to implement search functionality in C#/ASP.NET MVC. His DynamicWhere() method dynamically creates part of the expression that would produce the correct filter.

My problem is that my primary query type is Automobile but my ratings are in a separate table (Automobile.Ratings). How could I implement this same technique and filter on a type other than my primary query type?

Thanks for any tips.

هل كانت مفيدة؟

المحلول

Since the number of operations that you have is small, finite, and known at comiple time, you can simply handle it with a switch:

IQueryable<Something> query = GetQuery();

int ratingToComareWith = 1;
string operation = "Equal";

switch (operation)
{
    case ("Equal"):
        query = query.Where(item => item == ratingToComareWith);
        break;
    case ("Less Than"):
        query = query.Where(item => item < ratingToComareWith);
        break;
}

نصائح أخرى

Here's an Entity Framework friendly alternative to expression building. Of course you will want to validate column and op to prevent SQL injection.

// User provided values
int value = 3;
string column = "Rating";
string op = "<";
// Dynamically built query
db.Database.SqlQuery<Automobile>(@"select distinct automobile.* from automobile
    inner join ratings on .... 
    where [" + column + "] " + op + " @p0", value);

Here is a method to build conditions for nested collections or types for linq-to-entities. Restructured for your needs:

        public static Expression GetCondition(Expression parameter, object value, OperatorComparer operatorComparer, 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 = GetCondition(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
    {
       var childProperty = parameter.Type.GetProperty(properties[0]);
       var left = Expression.Property(parameter, childProperty);
       var right = Expression.Constant(value,value.GetType());
       if(!new List<OperatorComparer>    {OperatorComparer.Contains,OperatorComparer.StartsWith}.Contains(operatorComparer))
        {
            navigationPropertyPredicate = Expression.MakeBinary((ExpressionType)operatorComparer,left, right);
        }
        else
        {
            var method = GetMethod(childProperty.PropertyType, operatorComparer); //get property by enum-name from type
            navigationPropertyPredicate = Expression.Call(left, method, right);
        }
        resultExpression = MakeLambda(parameter, navigationPropertyPredicate);
    }
    return resultExpression;
}

private static MethodInfo GetMethod(Type type,OperatorComparer operatorComparer)
{
    var method = type.GetMethod(Enum.GetName(typeof(OperatorComparer),operatorComparer));
    return method;
} 

public enum OperatorComparer
{
    Equals = ExpressionType.Equal,
    Contains,
    StartsWith,
    GreaterThan = ExpressionType.GreaterThan
    ....

}

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;
    }
}
    }

You could replace the params string[] with params Expression(Func(T,object)), if you want. Would need some more work to do it that way. You would need to definie nested collections with a syntax like

item => item.nestedCollection.Select(nested => nested.Property)

and rewrite the expression with the help of an expressionvisitor.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top