none
Expression.Call (Dynamic LINQ query) RRS feed

  • Question

  • Hello. I've been reading much on creating Dynamic Linq queries the past few days, but I just can't quite grasp it yet. I want the users to be able to fill out a screen (Advanced Search) and then create the query from there. I could do it using "normal" SQL but I've never done this sort of thing using LINQ.

    I have this so far(not much):
    Code Snippet

    ParameterExpression param = Expression.Parameter(typeof(Client), "c");
    Expression right = Expression.Constant(clientName);
    Expression left = Expression.Property(param, typeof(Client).GetProperty("ClientName"));
    Expression filter = Expression.Equal(left, right);
    Expression pred = Expression.Lambda(filter, param);

    IQueryable<Client> clients = _db.Clients;
    Expression expr = Expression.Call(typeof(IQueryable), "Select", new Type[] { typeof(Client), typeof(string) }, Expression.Constant(clients), pred);
    IQueryable<string> query = _db.Clients.AsQueryable().Provider.CreateQuery<string>(expr);


    It fails at the Expression.Call line.

    If anyone can give me any suggestions on how to handle this, a book to read, or anything; that would be wonderful!

    Thanks!

    Brenda
    Wednesday, November 12, 2008 8:25 PM

Answers

  • If you know the type (Client) and the specific fields you can write this sort of code.

     

    IQueryable<Client> query = db.Clients;

     

    if (!string.IsNullOrEmpty(name))

        query = query.Where(c => c.Name == name);

     

    if (!string.IsNullOrEmpty(Address))

        query = query.Where(c => c.Address == address);

     

    if (!string.IsNullOrEmpty(City))

        query = query.Where(c => c.City == city);

     

    etc.

     

    If you don't know the type (maybe you want a single function that applies a buch of props & values to any query), make a function to add one predicate to a query:

     

    public IQueryable AddPropertyMatch(IQueryable query, string prop, object value)

    {

        ParameterExpression p = Expression.Parameter(query.ElementType, "p");

        LambdaExpression pred = Expression.Lambda(

    Expression.Equal(

          Expression.PropertyOrField(p, prop),

          Expression.Constant(value)

          ),

    p

    );

        return query.Provider.CreateQuery(

               Expression.Call(typeof(Queryable), "Where", new Type[] {query.ElementType}, query.Expression, pred)

               );
    }

     

    and then you can make another that takes a whole dictionary...

     

    public IQueryable AddPropertyMatches(IQueryable query, Dictionary<string, object> matches)

    {

         foreach(var kv in matches)

         {

             query = AddPropertyMatch(query, kv.Key, kv.Value);

         }

         return query;

    }

     

    Monday, November 17, 2008 5:10 PM
    Moderator
  • That's what I was doing.  Each 'if' statement checked to see if one of the variables was filled in, and if so add just that part to the query. You can string together as many calls to Where as you want. 

     

    Tuesday, November 18, 2008 2:51 AM
    Moderator

All replies

  • Your argument expression 'pred' is a predicate function that is likely meant as argument to the "Where" method.  You are calling "Select" which needs a function that converts Client's into strings.

     

    You probably just wanted to select the ClientName?

     

    ParameterExpression param = Expression.Parameter(typeof(Client), "c");

    Expression selector = Expression.Lambda(Expression.Property(param, typeof(Client).GetProperty("ClientName")), param);

     

    IQueryable<Client> clients = _db.Clients;
    Expression expr = Expression.Call(typeof(IQueryable), "Select", new Type[] { typeof(Client), typeof(string) }, 

             Expression.Constant(clients), selector);
    IQueryable<string> query = _db.Clients.AsQueryable().Provider.CreateQuery<string>(expr);

    Thursday, November 13, 2008 2:47 AM
    Moderator
  • Matt

    I actually want to return some Client objects. A user can enter in a Client's Name, Address, City, Phone Number, etc. Depending on what they enter in, I want to select those Clients.

    I just need to understand the basics of how dynamically building queries on the fly works in LINQ, and then I'll be okay from there.

    There is will be some If statements. For instance, if (!string.IsNullOrEmpty(Address1)), then add that to the query. Does that make sense?

    Any ideas? THANKS!

    Brenda
    Thursday, November 13, 2008 7:30 PM
  • If you know the type (Client) and the specific fields you can write this sort of code.

     

    IQueryable<Client> query = db.Clients;

     

    if (!string.IsNullOrEmpty(name))

        query = query.Where(c => c.Name == name);

     

    if (!string.IsNullOrEmpty(Address))

        query = query.Where(c => c.Address == address);

     

    if (!string.IsNullOrEmpty(City))

        query = query.Where(c => c.City == city);

     

    etc.

     

    If you don't know the type (maybe you want a single function that applies a buch of props & values to any query), make a function to add one predicate to a query:

     

    public IQueryable AddPropertyMatch(IQueryable query, string prop, object value)

    {

        ParameterExpression p = Expression.Parameter(query.ElementType, "p");

        LambdaExpression pred = Expression.Lambda(

    Expression.Equal(

          Expression.PropertyOrField(p, prop),

          Expression.Constant(value)

          ),

    p

    );

        return query.Provider.CreateQuery(

               Expression.Call(typeof(Queryable), "Where", new Type[] {query.ElementType}, query.Expression, pred)

               );
    }

     

    and then you can make another that takes a whole dictionary...

     

    public IQueryable AddPropertyMatches(IQueryable query, Dictionary<string, object> matches)

    {

         foreach(var kv in matches)

         {

             query = AddPropertyMatch(query, kv.Key, kv.Value);

         }

         return query;

    }

     

    Monday, November 17, 2008 5:10 PM
    Moderator
  • Hi Matt

    I want to do the first part, of what you showed above because I just need Client objects returned, but I need to do it different than what you show above. What if they fill out the Address and City but not the name? Is there any way to add to 'query'? Do you know what I mean? Thanks!
    Monday, November 17, 2008 11:16 PM
  • That's what I was doing.  Each 'if' statement checked to see if one of the variables was filled in, and if so add just that part to the query. You can string together as many calls to Where as you want. 

     

    Tuesday, November 18, 2008 2:51 AM
    Moderator
  • Thanks Matt for all your time. It works perfectly!!!
    Wednesday, November 19, 2008 5:39 PM