Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I decompose a Predicate Expression into a query?

Tags:

c#

lambda

linq

I have the following class Person, with a custom Where method:

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public string Where(Expression<Func<Person, bool>> predicate)
    {
        return String.Empty;
    }
}

How can I retrieve the Expression parameter names and values in an enumerable manner?

Person p = new Person();
p.Where(i => i.Name == "Shlomi" && i.Age == 26);

For the purpose of building a string query with parameters attached according to the name and value of the expression.

// Eventually I will convert the above into the following:
string Query = "select * from person where name = @Name AND Age = @Age";

SqlParameter[] param = new SqlParameter[] { 
    new SqlParameter("@Name","Shlomi"),
    new SqlParameter("@Age","26")
};
like image 641
Shlomi Komemi Avatar asked Sep 22 '11 19:09

Shlomi Komemi


3 Answers

I certainly think you should follow StriplingWarrior's advice and use LINQ to Entities or LINQ to SQL, but for the sake of reinventing the wheel (poorly) I'll build off of a prior answer of mine.

// Start with a method that takes a predicate and retrieves the property names
static IEnumerable<string> GetColumnNames<T>(Expression<Func<T,bool>> predicate)
{
    // Use Expression.Body to gather the necessary details
    var members = GetMemberExpressions(predicate.Body);
    if (!members.Any())
    {
        throw new ArgumentException(
            "Not reducible to a Member Access", 
            "predicate");
    }

    return members.Select(m => m.Member.Name);
}

Now you need to walk the expression tree, visiting each candidate expression, and determine if it includes a MemberExpression. The GetMemberExpressions method below will walk an expression tree and retrieve each of the MemberExpressions found within:

static IEnumerable<MemberExpression> GetMemberExpressions(Expression body)
{
    // A Queue preserves left to right reading order of expressions in the tree
    var candidates = new Queue<Expression>(new[] { body });
    while (candidates.Count > 0)
    {
        var expr = candidates.Dequeue();
        if (expr is MemberExpression)
        {
            yield return ((MemberExpression)expr);
        }
        else if (expr is UnaryExpression)
        {
            candidates.Enqueue(((UnaryExpression)expr).Operand);
        }
        else if (expr is BinaryExpression)
        {
            var binary = expr as BinaryExpression;
            candidates.Enqueue(binary.Left);
            candidates.Enqueue(binary.Right);
        }
        else if (expr is MethodCallExpression)
        {
            var method = expr as MethodCallExpression;
            foreach (var argument in method.Arguments)
            {
                candidates.Enqueue(argument);
            }
        }
        else if (expr is LambdaExpression)
        {
            candidates.Enqueue(((LambdaExpression)expr).Body);
        }
    }
}
like image 192
user7116 Avatar answered Oct 27 '22 00:10

user7116


The problem is that the Expression is actually a tree.

For example, you have the following predicate:

Expression<Func<Person, bool>> expr = x => x.Name == "Shlomi" && x.Age == 26;

If you inspect expr, you'll see that it's body has "AndAlso" NodeType and it also has two properties for operands -- Left and Right each pointing to another Expression object with "Equal" NodeType which in turn again have two properties Left and Right which point to Expression of type MemberAccess and Constant respectively.

While you can process that tree and extract all information you need, you'll end up implementing your own LINQ2SQL provider, e.g. reinventing a wheel. If you feel so, I hope I provided enough information to start digging...

like image 29
Dmitry Avatar answered Oct 26 '22 22:10

Dmitry


What you're wanting to do is very complicated, and there are entire frameworks that are built to do this, so you don't have to write the logic yourself. Take a look at LINQ to Entities and LINQ to SQL, e.g.

like image 28
StriplingWarrior Avatar answered Oct 26 '22 23:10

StriplingWarrior