Get from IQueryable<T> predicate Func<T, bool>

5.5k views Asked by At

I'm trying apply query from one collection to another. Sample of my test:

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        Expression sourceExpression = Amazings().Where(x => x.Name.Equals("Kasa")).AsQueryable().Expression;

        Expression<Func<Amazing, bool>> predicate = Expression.Lambda<Func<Amazing, bool>>(sourceExpression, Expression.Parameter(typeof(Amazing)));

        var col2 = Amazings().Where(predicate.Compile());
    }

    private IEnumerable<Amazing> Amazings()
    {
        var amazings = new List<Amazing>
            {
                new Amazing
                    {
                        Name = "Kasa",
                    },
                new Amazing
                    {
                        Name = "Ma@p2a"
                    }
            };
        return amazings;
    }

    class Amazing
    {
        public string Name { get; set; }
    }
}

What is wrong? When I run this test in debug I get the exception:

You can not use expressions such as „System.Linq.EnumerableQuery`1[UnitTestProject1.UnitTest1+Amazing]” for the return type „System.Boolean”.

If I run this without parameter I got exception that I don't have parameters.

If I change parameter to boolean type I got exception: you can't use element bool type for delegate parameter type Amazing...

I tested few more options but nothing works.

Also I found that the key may by casting Expression to: MethodCallExpression but it's not or I can't figurate how.

I tried with somothing like this:

MethodCallExpression e = query.Expression as MethodCallExpression;
        MemberExpression memberExpression = (MemberExpression)e.Object;

        Expression<Func<Amazing, bool>> getCallerExpression = Expression<Func<Amazing>>.Lambda<Func<Amazing, bool>>(memberExpression);

Unfortunately memberExpression is null.

I searched the web and the only thing I found it's a tip: ~"don't do this".

How can I achieve my goal?

2

There are 2 answers

1
xanatos On BEST ANSWER

It isn't clear what you want to do... But as Servy wrote, you put the AsQueryable() in the wrong place. The right place is before the .Where(), and this being a Unit Test, I suggest that the method that returns the data should directly return a IQueryable<T>, to better simulate "normal" queries you can find in code.

Try looking if this is what you want...

public void TestMethod1()
{
    var q1 = Amazings().Where(x => x.Name.Equals("Kasa"));

    Expression predicate = q1.Expression;

    var q2 = Amazings();
    IQueryable<Amazing> q3 = q2.Provider.CreateQuery<Amazing>(predicate);
}

private IQueryable<Amazing> Amazings()
{
    var amazings = new List<Amazing>
    {
        new Amazing
            {
                Name = "Kasa",
            },
        new Amazing
            {
                Name = "Ma@p2a"
            }
    };
    return amazings.AsQueryable();
}

Note that in the most general case, you can't simply move the Expression<Func<Amazing, bool>> condition of a .Where(), because you could have a .Where().Select() or something similar.

Other possible solution, if you know that the last method is a .Where() and you really want to "move" the Expression<Func<Amazing, bool>>:

MethodCallExpression mce = predicate as MethodCallExpression;

if (mce == null)
{
    throw new Exception();
}

UnaryExpression quote = mce.Arguments[1] as UnaryExpression;

if (quote == null || quote.NodeType != ExpressionType.Quote)
{
    throw new Exception();
}

Expression<Func<Amazing, bool>> lambda = quote.Operand as Expression<Func<Amazing, bool>>;

if (lambda == null)
{
    throw new Exception();
}

IQueryable<Amazing> q4 = Amazings().Where(lambda);
0
Servy On

Since you're calling Enumerable.Where and not Queryable.Where the lambda you have is never translated into an Expression, it's only ever compiled into a method, and the IQueryable that you have doesn't even have any way of getting to that delegate, because all that it sees is an arbitrary IEnumerable<T>.