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 author but fails to get the required page heading. I get the exception in line "left11 = Expression.Property(page1, "Heading");". I think the expression that i built is unable to recognise the List. How could this be possible? Please see the below code and stacktrace exception.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace XMLStorageAndFilter
{
public class Books
{
public Books()
{
Page = new List<Page>();
}
public string Title { get; set; }
public Author Author { get; set; }
public List<Page> Page { 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 = "Heading";
Books bok = new Books();
bok.Title = "Title";
bok.Author = new Author() { FirstName = "FirstName" };
bok.Page.Add(page);
List<Books> testList = new List<Books>();
testList.Add(bok);
IQueryable<Books> queryableTestData = testList.AsQueryable<Books>();
ParameterExpression pe11 = Expression.Parameter(typeof(Books), "p");
Expression left11 = Expression.Property(pe11, "Title");
Expression right11 = Expression.Constant("Title");
Expression e11 = Expression.Equal(left11, right11);
var author = Expression.Property(pe11, "Author");
left11 = Expression.Property(author, "FirstName");
right11 = Expression.Constant("FirstName");
Expression e21 = Expression.Equal(left11, right11);
Expression predicateBody11 = Expression.And(e11, e21);
Expression<Func<Books, bool>> condition = Expression.Lambda
<Func<Books, bool>>(predicateBody11, new ParameterExpression[] { pe11 });
var q = queryableTestData.Where(condition);
var page1 = Expression.Property(pe11, "Page");
left11 = Expression.Property(page1, "Heading");
right11 = Expression.Constant("Heading");
Expression e22 = Expression.Equal(left11, right11);
Expression predicateBody12 = Expression.And(e11, e22);
Expression<Func<Books, bool>> condition2 = Expression.Lambda
<Func<Books, bool>>(predicateBody12, new ParameterExpression[] { pe11 });
var qq1 = queryableTestData.Where(condition2);
}
}
}
Exception Message:- {"Instance property 'Heading' is not defined for type >'System.Collections.Generic.List`1[XMLStorageAndFilter.Page]'"}
StackTrace:-
at System.Linq.Expressions.Expression.Property(Expression expression, String propertyName)
at XMLStorageAndFilter.Program2.Main() in c:\Users\Administrator\Documents\Visual Studio 2013\Projects\XMLStorageAndFilter\NavProperty.cs:line 61
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Based on your description I'm not sure that you need
Expression
. Creating anExpression
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 needExpression
.I suggest first of all to clean your object model:
Books
class toBook
(this class represents a Book not a list of books)Page
toPages
(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:Here you can also see why your previous code didn't work. The expression to search for a given
Heading
isbook.Pages.Any(p => p.Heading == x)
and notbook.Pages.Heading == x
.In any case given one or more functions like this you can rewrite your code with something like:
I have skipped search values when null or empty, just an example. This code has also the advantage that is verified at compile time.
On the whole, it is not bad to consider DynamicLinq when you are dealing with dynamic matters
You should use Contains - you are looking inside a list - not Equals.
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:
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:
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
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:
}
This can be used like