Dynamic linq Building Expression

前端 未结 5 2043
失恋的感觉
失恋的感觉 2021-01-06 19:14

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

相关标签:
5条回答
  • 2021-01-06 19:49

    You should use Contains - you are looking inside a list - not Equals.

    0 讨论(0)
  • 2021-01-06 19:56

    On the whole, it is not bad to consider DynamicLinq when you are dealing with dynamic matters

    0 讨论(0)
  • 2021-01-06 19:57

    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);
    
    0 讨论(0)
  • 2021-01-06 20:05

    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

    0 讨论(0)
  • 2021-01-06 20:13

    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:

    • Rename the Books class to Book (this class represents a Book not a list of books)
    • Rename property 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.

    0 讨论(0)
提交回复
热议问题