1

Have read other responses to similar issuebut I can not use PredicateBuilder, or copy its source. I'm trying what I've read here:

<https://blogs.msdn.microsoft.com/meek/2008/05/02/linq-to-entities-combining-predicates/

but as I'm newb, am having trouble with translating what I'm reading to what I'm applying. I have created a L2E query, and trying to append a series of OR clauses onto the WHERE:

So as simplified snippet (this one will be AND'd with the previously already defined WHERE clause):

if (firstParm == "realtor")
    query = query.Where(x=> x.A == "realtor");

Now trying to OR:

if (secondParm == "clown")
   // how to add this one as an OR to the above query:
   query = query.OR(x=> x.fool == "clown");

I understand this can be done also with Union, but not clear on the syntax:

query = query.Union(x=> x.fool == "clown");  // ??

I've also referenced:

Combining two expressions (Expression<Func<T, bool>>)

Unable to create a compound Expression<Func<string, bool>> from a set of expressions

but again, I am new to LINQ and especially Expression Trees, so need more fillin.

Community
  • 1
  • 1
frododot
  • 127
  • 2
  • 16

2 Answers2

2

There are two ways to generate expressions.

  1. Use the compiler to do it.

    Expression<Func<Person, bool>> = p => p.LastName.Contains("A");
    

    Limitations: The only expressions that can be generated this way are instances of LambdaExpression. Also, it is rather complicated to extract parts of the expression and combine with other parts.

  2. Use the static methods at System.Linq.Expressions.Expression.


In order to generate dynamic expressions, you can either choose between different compiler-generated expressions:

// using Record and Records as a placeholder for the actual record type and DbSet property

Expression<Func<Record,bool>> expr;
if (firstParam == "realtor") {
    if (secondParam == "clown") {
        expr = x => x.A == "realtor" || x.fool == "clown";
    } else {
        expr = x => x.A == "realtor";
    }
} else {
    if (secondParam == "clown") {
        expr = x => x.fool="clown";
    } else {
        expr = x => false;
    }
}

var ctx = new MyDbContext();
var qry = ctx.Records.Where(expr).Select(x => new {x.A, x.fool});

Or, you can dynamically create the expression using the static methods:

(Add using System.Linq.Expressions; and using static System.Linq.Expressions.Expression; to the top of the file.)

Expression expr;
var parameter = Parameter(typeof(Record));
if (firstParam == "realtor") {
    expr = Equals(
        MakeMemberAccess(parameter, typeof(Record).GetProperty("A")),
        Constant("realtor")
    );
}
if (secondParam == "clown") {
    var exprClown = Equals(
        MakeMemberAccess(parameter, typeof(Record).GetProperty("fool")),
        Constant("clown")
    );
    if (expr == null) {
        expr = exprClown;
    } else {
        expr = Or(expr, exprClown);
    }
}

var lambda = Lambda<Func<Record,bool>>(expr, new [] {parameter});
var ctx = new MyDbContext();
var qry = ctx.Records.Where(lambda).Select(x => new {x.A, x.fool});

Given a query with a type unknown at compile time, so any variable referring to it must be IQueryable only, and not IQueryable<T>:

IQueryable qry = ctx.GetQuery(); //dynamically built query here
var parameter = Parameter(qry.ElementType);
if (firstParam == "realtor") {
    expr = Equals(
        MakeMemberAccess(parameter, qry.ElementType.GetProperty("A")),
        Constant("realtor")
    );
}
if (secondParam == "clown") {
    var exprClown = Equals(
        MakeMemberAccess(parameter, qry.ElementType.GetProperty("fool")),
        Constant("clown")
    );
    if (expr == null) {
        expr = exprClown;
    } else {
        expr = Or(expr, exprClown);
    }
}

var lambda = Lambda(expr, new [] {parameter});

//Since we don't have access to the TSource type to be used by the Where method, we have
//to invoke Where using reflection.
//There are two overloads of Queryable.Where; we need the one where the generic argument
//is Expression<Func<TSource,bool>>, not Expression<Func<TSource,int,bool>>
var miWhere = typeof(Queryable).GetMethods().Single(mi => {
    mi.Name == "Where" && 
        mi.GetParameters()[1].ParameterType.GetGenericArguments()[0].GetGenericArguments().Length == 2
});

qry = miWhere.Invoke(null, new [] {qry, lambda});
Zev Spitz
  • 13,950
  • 6
  • 64
  • 136
  • Since I will be comparing against properties of a query, I'm not clear as to what to put for Record type in above. The typeT is the virtual record created by the select, that I am trying to concat the WHERE OR clauses onto. Hope that makes sense :-) -- newb – frododot Nov 07 '16 at 23:19
  • @frododot Could you show how you are creating your query? Are you using an anonymous type, or something else? – Zev Spitz Nov 07 '16 at 23:22
  • It's anonymous: var query = from... where...select new{...} – frododot Nov 08 '16 at 00:33
  • @frododot 2 options -- 1) You could use method syntax and apply the `Where` before the `Select`, and `Record` will be whatever type you are using before the `Select`; or 2) anonymous types are actual types but with compiler-assigned names, so you can extract the type using reflection and continue from there. I've updated my answer to use (1). If (1) is not an option, I'll update my answer to show using (2). – Zev Spitz Nov 08 '16 at 00:39
  • Option 1) will not be an option, as this is built dynamically. Could you supply additional syntax info for option 2)? Many thanks :-) – frododot Nov 08 '16 at 00:46
  • 1
    @frododot Updated. – Zev Spitz Nov 08 '16 at 01:48
0

For Or you can try

  if (secondParm == "clown") 
  {
    query = query.Where(x=> x.fool == "clown" || x.fool==x.fool);
  }

OR

  if (secondParm == "clown") 
  {
    query = query.Where(x=> x.fool == "clown" || true );
  }
vibhuti
  • 11
  • 2