Dynamic linq Building Expression

c# dynamic-linq linq query-builder

Question

For a dynamic search, I must develop a dynamic linq expression. Basic searches are successful, but collection searches do not. The book's title and author are available to me, but I am unable to locate the necessary page header. Line Expression.Property(page1, "Heading"); left11; throws an exception for me. I believe the phrase I created is unable to identify the List. How is this even conceivable? Please see the code and error stacktrace below.

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()

1
3
6/16/2016 2:07:59 PM

Accepted Answer

You may apply the procedure specified in here.

The method's output would need to be cast toExpression<Func<T,bool>> T is the kind you like.

When I get home, I'll provide a detailed example.

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;
        }
    }
}

}

Uses for this include

var predicate = ExpressionBuilder.BuildPredicate<Books>("Heading",OperatorComparer.Equals,"Page","Heading");
query = query.Where(predicate);
10
5/23/2017 10:29:50 AM

Popular Answer

Considering what you described, I don't think you needExpression . Developing anExpression using a complicated object model is really challenging. Do you really need to develop a dynamic expression, or may a dynamic query suffice? You won't need to change anything if the object model is fixed.Expression .

I advise cleaning up your object model first:

  • the renameBooks course toBook (This class depicts a Book, not a collection of books)
  • Name the propertyPage to Pages (A list of pages is returned by this attribute)

With simply LINQ and one or more helper functions for each property you need to search, you can now create dynamic code. for instance, to look forHeading You may type:

        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 may also learn why your earlier code failed. The query to find a specifiedHeading is book.Pages.Any(p => p.Heading == x) but notbook.Pages.Heading == x .

In any case, you may change your code to look something like this if you have one or more methods like these:

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);
        }
    }
}

Just as an example, I have ignored search values that are empty or null. Another benefit of this code is that it is checked at compilation time.



Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow