Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining multiple expressions (Expression<Func<T,bool>>) not working with variables. Why?

I've written a number of methods (.WhereOr, .WhereAnd) which basically allow me to "stack up" a bunch of lambda queries, and then apply them to a collection. For example, the usage with datasets would be a little like this (although it works with any class by using generics):

WITH LINQ TO DATASETS (Using the .NET DataSetExtensions)

DataTable Result;

List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();

Queries.Add(dr=> dr.Field<string>("field1") == "somestring");
Queries.Add(dr=> dr.Field<string>("field2") == "somestring"); 
Queries.Add(dr=> dr.Field<string>("field3") == "somestring"); 

Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();

Now say that in the example above only one row in the collection matches "somestring", and it's on field "field2".

That means that the count for Result should be 1.

Now, say I re-write the code above slightly to this:

DataTable Result;

List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();

List<string> columns = new string[]{"field1","field2","field3"}.ToList();

string col;

foreach(string c in columns){
    col = c;
    Queries.Add(dr=> dr.Field<string>(col) == "somestring");
}

Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();

Now, I don't really understand expressions, but to me both examples above are doing exactly the same thing.

Except that "Result" in the first example has a count of 1, and "Result" in the second example has a count of 0.

Also, in the List columns in the second example, if you put "field2" last, instead of second, then "Result" does correctly have a count of 1.

So, from all this I've come to a kind of conclusion, but I don't really understand what's happening, nor how to fix it..? Can I "evaluate" those expressions earlier...or part of them?

CONCLUSION:

Basically, it seems like, if I send literal values into there, like "field1", it works. But if I send in variables, like "col", it doesn't work, because those "expressions" are only getting evaluated much later in the code.

that would also explain why it works when I move "field2" to the last position. it works because the variable "col" was assigned to "field2" lastly, thus by the time the expressions evaluate "col" equals "field2".

Ok, so, is there any way around this??

Here's the code for my WhereOr method (it's an extension method to IENumerable):

public static IQueryable<T> WhereOr<T>(this IEnumerable<T> Source, List<Expression<Func<T, bool>>> Predicates) {

        Expression<Func<T, bool>> FinalQuery;

        FinalQuery = e => false;

        foreach (Expression<Func<T, bool>> Predicate in Predicates) {
            FinalQuery = FinalQuery.Or(Predicate);
        }

        return Source.AsQueryable<T>().Where(FinalQuery);
    }

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> Source, Expression<Func<T, bool>> Predicate) {
        InvocationExpression invokedExpression = Expression.Invoke(Predicate, Source.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.Or(Source.Body, invokedExpression), Source.Parameters);
    }
like image 586
andy Avatar asked Apr 12 '26 03:04

andy


1 Answers

The "how to fix" answer. Change this:

string col;
foreach(string c in columns) {
    col = c;
    Queries.Add(dr=> dr.Field<string>(col) == "somestring");
} 

to this:

foreach(string c in columns) {
    string col = c;
    Queries.Add(dr=> dr.Field<string>(col) == "somestring");
} 

Enjoy. The "what & why" answer was given by Brian.

like image 96
Pavel Minaev Avatar answered Apr 15 '26 01:04

Pavel Minaev



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!