I need to create a dynamic linq expression for a dynamic search.The basic search is working but it fails to work with collection. I am able to get the book\'s title and auth
You should use Contains - you are looking inside a list - not Equals.
On the whole, it is not bad to consider DynamicLinq when you are dealing with dynamic matters
You can use the method described here.
You would need to cast the result of the method to Expression<Func<T,bool>>
. T being your type.
I will provide an complete example when i get home.
Edit:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Collections;
using System.Reflection;
namespace ExpressionPredicateBuilder
{
public enum OperatorComparer
{
Contains,
StartsWith,
EndsWith,
Equals = ExpressionType.Equal,
GreaterThan = ExpressionType.GreaterThan,
GreaterThanOrEqual = ExpressionType.GreaterThanOrEqual,
LessThan = ExpressionType.LessThan,
LessThanOrEqual = ExpressionType.LessThan,
NotEqual = ExpressionType.NotEqual
}
public class ExpressionBuilder
{
public static Expression<Func<T,bool>> BuildPredicate<T>(object value, OperatorComparer comparer, params string[] properties)
{
var parameterExpression = Expression.Parameter(typeof(T), typeof(T).Name);
return (Expression<Func<T, bool>>)BuildNavigationExpression(parameterExpression, comparer, value, properties);
}
private static Expression BuildNavigationExpression(Expression parameter, OperatorComparer comparer, object value, params string[] properties)
{
Expression resultExpression = null;
Expression childParameter, predicate;
Type childType = null;
if (properties.Count() > 1)
{
//build path
parameter = Expression.Property(parameter, properties[0]);
var isCollection = typeof(IEnumerable).IsAssignableFrom(parameter.Type);
//if it´s a collection we later need to use the predicate in the methodexpressioncall
if (isCollection)
{
childType = parameter.Type.GetGenericArguments()[0];
childParameter = Expression.Parameter(childType, childType.Name);
}
else
{
childParameter = parameter;
}
//skip current property and get navigation property expression recursivly
var innerProperties = properties.Skip(1).ToArray();
predicate = BuildNavigationExpression(childParameter, comparer, value, innerProperties);
if (isCollection)
{
//build subquery
resultExpression = BuildSubQuery(parameter, childType, predicate);
}
else
{
resultExpression = predicate;
}
}
else
{
//build final predicate
resultExpression = BuildCondition(parameter, properties[0], comparer, value);
}
return resultExpression;
}
private static Expression BuildSubQuery(Expression parameter, Type childType, Expression predicate)
{
var anyMethod = typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2);
anyMethod = anyMethod.MakeGenericMethod(childType);
predicate = Expression.Call(anyMethod, parameter, predicate);
return MakeLambda(parameter, predicate);
}
private static Expression BuildCondition(Expression parameter, string property, OperatorComparer comparer, object value)
{
var childProperty = parameter.Type.GetProperty(property);
var left = Expression.Property(parameter, childProperty);
var right = Expression.Constant(value);
var predicate = BuildComparsion(left, comparer, right);
return MakeLambda(parameter, predicate);
}
private static Expression BuildComparsion(Expression left, OperatorComparer comparer, Expression right)
{
var mask = new List<OperatorComparer>{
OperatorComparer.Contains,
OperatorComparer.StartsWith,
OperatorComparer.EndsWith
};
if(mask.Contains(comparer) && left.Type != typeof(string))
{
comparer = OperatorComparer.Equals;
}
if(!mask.Contains(comparer))
{
return Expression.MakeBinary((ExpressionType)comparer, left, Expression.Convert(right,left.Type));
}
return BuildStringCondition(left, comparer, right);
}
private static Expression BuildStringCondition(Expression left, OperatorComparer comparer, Expression right)
{
var compareMethod = typeof(string).GetMethods().Single(m => m.Name.Equals(Enum.GetName(typeof(OperatorComparer), comparer)) && m.GetParameters().Count() == 1);
//we assume ignoreCase, so call ToLower on paramter and memberexpression
var toLowerMethod = typeof(string).GetMethods().Single(m => m.Name.Equals("ToLower") && m.GetParameters().Count() == 0);
left = Expression.Call(left, toLowerMethod);
right = Expression.Call(right, toLowerMethod);
return Expression.Call(left, compareMethod, right);
}
private static Expression MakeLambda(Expression parameter, Expression predicate)
{
var resultParameterVisitor = new ParameterVisitor();
resultParameterVisitor.Visit(parameter);
var resultParameter = resultParameterVisitor.Parameter;
return Expression.Lambda(predicate, (ParameterExpression)resultParameter);
}
private class ParameterVisitor : ExpressionVisitor
{
public Expression Parameter
{
get;
private set;
}
protected override Expression VisitParameter(ParameterExpression node)
{
Parameter = node;
return node;
}
}
}
}
This can be used like
var predicate = ExpressionBuilder.BuildPredicate<Books>("Heading",OperatorComparer.Equals,"Page","Heading");
query = query.Where(predicate);
Update
The way to query a collection has been answered in the following:
Building a dynamic expression tree to filter on a collection property
Original Response
I believe Davide Lcardi is correct with his statement:
Heading is book.Pages.Any(p => p.Heading == x) and not book.Pages.Heading == x.
if you want to query the list you need to use Any() method to do so. I could not get it exactly right but it should look something like the following using Expression.Call:
ParameterExpression pe41 = Expression.Parameter(typeof (Page), "pg");
Expression left41 = Expression.Property(pe41, "Heading");
Expression right41 = Expression.Constant("Heading");
Expression e41 = Expression.Equal(left41, right41);
var methodCall = Expression.Call( Expression.Property(pe11, "Pages"), "Any", new Type[] {typeof(Page), typeof(Boolean)}, e41 );
I am getting this error: No method 'Any' exists on type 'System.Collections.Generic.List`1[SO.Page]'. SO is my NameSpace where class Page exists.
I think I am not sending the correct Type or the whole call may be incorrect. I think this is the correct direction thought to help you find your solution.
Here are some examples I was looking at:
http://msdn.microsoft.com/en-us/library/bb349020(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/dd402755(v=vs.110).aspx
http://community.bartdesmet.net/blogs/bart/archive/2009/08/10/expression-trees-take-two-introducing-system-linq-expressions-v4-0.aspx
http://blogs.msdn.com/b/csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx
Based on your description I'm not sure that you need Expression
. Creating an Expression
with a complex object model is quite difficult. Do you really need to create dynamic expression or you simply need to create a dynamic query? If the object model is fixed then you don't need Expression
.
I suggest first of all to clean your object model:
Books
class to Book
(this class represents a Book not a list of books)Page
to Pages
(this property returns a list of pages)Now you can write a dynamic where using just LINQ and one or more helper functions one for each property that you need to search. For example to search for Heading
you can write:
private static bool SearchByHeading(Book b, string heading)
{
if (string.IsNullOrEmpty(heading))
return true;
else
return b.Pages.Any(p => p.Heading == heading);
}
Here you can also see why your previous code didn't work. The expression to search for a given Heading
is book.Pages.Any(p => p.Heading == x)
and not book.Pages.Heading == x
.
In any case given one or more functions like this you can rewrite your code with something like:
using System.Collections.Generic;
using System.Linq;
namespace XMLStorageAndFilter
{
public class Book
{
public Book()
{
Pages = new List<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> Pages { get; set; }
}
public class Author
{
public string FirstName { get; set; }
}
public class Page
{
public string Heading { get; set; }
}
public class Program2
{
static void Main()
{
Page page = new Page();
page.Heading = "Heading1";
Book bok = new Book();
bok.Title = "Title1";
bok.Author = new Author() { FirstName = "FirstName1" };
bok.Pages.Add(page);
List<Book> testList = new List<Book>();
testList.Add(bok);
var searchResult = Search(testList,
title: "Title1",
author: "FirstName1",
heading: "Heading1");
}
private static IEnumerable<Book> Search(IEnumerable<Book> books, string author = null, string title = null, string heading = null)
{
return books
.Where((b) => SearchByAuthor(b, author))
.Where((b) => SearchByHeading(b, heading))
.Where((b) => SearchByTitle(b, title))
.ToList();
}
private static bool SearchByAuthor(Book b, string author)
{
if (string.IsNullOrEmpty(author))
return true;
else
return b.Author.FirstName == author;
}
private static bool SearchByTitle(Book b, string title)
{
if (string.IsNullOrEmpty(title))
return true;
else
return b.Title == title;
}
private static bool SearchByHeading(Book b, string heading)
{
if (string.IsNullOrEmpty(heading))
return true;
else
return b.Pages.Any(p => p.Heading == heading);
}
}
}
I have skipped search values when null or empty, just an example. This code has also the advantage that is verified at compile time.