views:

110

answers:

1

Hi,

I'm trying to get the following SQL query to work in LINQ:

    Select id from table1 where id in (1,2) or canceledId in (1,2)

I'm using BuildContainsExpression to achieve the "IN" condition, but I can't figure out how to implement the "or" condition. My shot in the dark is as follows:

var identifiers = new List<int> {1,2};

var query = (from t in Context.Table1
             select t);

var query =
    query.Where(BuildContainsExpression<Table1, int>(t => t.Id, identifiers));

if (showCanceled)
{
   var expression = query.Where(BuildContainsExpression<Table1, int>(t => t.CanceledId.Value, identifiers)).Expression;
   Expression.Or(expression, transactionsQuery.Expression);
}

But I get the following exception:
The binary operator Or is not defined for the types 'System.Linq.IQueryable1[Table1]' and 'System.Linq.IQueryable1[Table1]'..

Any ideas? -Am I in the right direction?

Thanks, Nir.

A: 

You are appending your OR in the wrong place. What you are doing now is effectively the something like this:

(from t in Context.Table1
 where identifiers.Contains(t.Id)
 select t)
OR
(where identifiers.Contains(t.CanceledId))

The second problem is that the BuildContainsExpression method you use, returns a lambda expression, something that looks like this:

t => t.Id == 1 || t.Id == 2 || ...

You can't change this expression once it's generated. However, that's what you want because you'd like to have something like this:

t => t.Id == 1 || t.Id == 2 || ... || t.CanceledId == 1 || t.CanceledId == 2 || ...

You can't simply take the body of this lambda expression and or it together with another expression because it depends on the parameter t.

So what you can do is the following:

// Overload of BuildContainsExpression.
private static Expression<Func<T, bool>> BuildOtherContainsExpression<T>(
    ParameterExpression p, Expression field1, Expression field2, int[] values)
{
    var eq1 = values.Select(v => Expression.Equal(field1, Expression.Constant(v)));
    var eq2 = values.Select(v => Expression.Equal(field2, Expression.Constant(v)));

    var body = eq1.Aggregate((acc, equal) => Expression.Or(acc, equal));
    body = eq2.Aggregate(body, (acc, equal) => Expression.Or(acc, equal));
    return Expression.Lambda<Func<T, bool>>(body, p);
}

// Create a parameter expression that represents something of type Table1.
var parameter = Expression.Parameter(typeof(Table1), "t");

// Create two field expressions that refer to a field of the parameter.
var idField = Expression.Property(parameter, "Id");
var canceledIdField = Expression.Property(parameter, "CanceledId");

// And finally the call to this method.
query.Where(BuildContainsExpression<Table1>(
    parameter, idField, canceledIdField, identifiers));

Your if statement would now look like this:

if (!showCanceled)
{
    // Use original version of BuildContainsExpression.
}
else
{
    // Create some expressions and use overloaded version of BuildContainsExpression.
}
Ronald Wildenberg
I'm getting compliation error on the inside where assignment (Expression.Or): Cannot implicitly convert type 'System.Linq.Expressions.BinaryExpression' to 'System.Linq.Expressions.Expression<System.Func<Table1,bool>>' In addition, the .Expression is not needed since it's already an expression, isn't it?
nirpi
You're right and I was a little too soon answering your question. Working on a solution right now..
Ronald Wildenberg
nirpi
That makes sense, I tested this with a class containing a field but in the real world it's a property. I updated my answer.
Ronald Wildenberg
Getting closer...Now I get the following (since CanceledId field is actually nullable):System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int32]' and 'System.Int32'..I tried to use the "Expression.Convert(idField, typeof(int?))" (saw that somewhere), but did not have any success with it...Any idea?
nirpi
Managed to solve that by overriding the CanceledIdField with its Value property: canceledIdField = Expression.Property(canceledIdField, "Value"); Everything works like a charm, Roland - you're the man! Thanks.
nirpi