How can you update a Linq Expression with additional parameters?

前端 未结 5 904
感情败类
感情败类 2020-12-29 14:28

I have a Linq Expression, which may be altered depending on certain conditions. An example of what I would like to do (left blank the bit I am not sure about):



        
相关标签:
5条回答
  • 2020-12-29 14:36

    If you Get method retrives the data and returns in memory objects the you could do so

    Expression<Func<Project, bool>> filter = (Project p) => p.UserName == "Bob";
    if(showArchived) {
         filter = (Project p) => p.UserName == "Bob" && p.Archived;
    }
    IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);
    

    EDIT

    Just to point out. When you use .ToList() method it enumerates the Queryable, i.e. makes a database request.

    0 讨论(0)
  • 2020-12-29 14:36

    Get rid of ToList() and you'll be just fine.

    0 讨论(0)
  • 2020-12-29 14:45

    If I understand the question, then most likely here's the problem:

    IEnumerable<Project> projects = unitOfWork.ProjectRepository.Get(filter);
    

    Any work on projects is going to be using Enumerable, not Queryable; it should probably be:

    IQueryable<Project> projects = unitOfWork.ProjectRepository.Get(filter);
    if(showArchived)
    {
        projects = projects.Where(p => p.Archived);
    }
    

    The latter is composable, and .Where should work as you expect, building up a more restrictive query before sending it to the server.

    Your other option is to rewrite the filter to combine before sending:

    using System;
    using System.Linq.Expressions;
    
    static class Program
    {
        static void Main()
        {
            Expression<Func<Foo, bool>> filter1 = x => x.A > 1;
            Expression<Func<Foo, bool>> filter2 = x => x.B > 2.5;
    
            // combine two predicates:
            // need to rewrite one of the lambdas, swapping in the parameter from the other
            var rewrittenBody1 = new ReplaceVisitor(
                filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);
            var newFilter = Expression.Lambda<Func<Foo, bool>>(
                Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters);
            // newFilter is equivalent to: x => x.A > 1 && x.B > 2.5
        }
    }
    class Foo
    {
        public int A { get; set; }
        public float B { get; set; }
    }
    class ReplaceVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public ReplaceVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
    

    Or re-written in a way to allow convenient usage:

    using System;
    using System.Linq.Expressions;
    
    static class Program
    {
        static void Main()
        {
            Expression<Func<Foo, bool>> filter = x => x.A > 1;
    
            bool applySecondFilter = true;
            if(applySecondFilter)
            {
                filter = Combine(filter, x => x.B > 2.5);
            }
            var data = repo.Get(filter);
        }
        static Expression<Func<T,bool>> Combine<T>(Expression<Func<T,bool>> filter1, Expression<Func<T,bool>> filter2)
        {
            // combine two predicates:
            // need to rewrite one of the lambdas, swapping in the parameter from the other
            var rewrittenBody1 = new ReplaceVisitor(
                filter1.Parameters[0], filter2.Parameters[0]).Visit(filter1.Body);
            var newFilter = Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters);
            return newFilter;
        }
    }
    class Foo
    {
        public int A { get; set; }
        public float B { get; set; }
    }
    class ReplaceVisitor : ExpressionVisitor
    {
        private readonly Expression from, to;
        public ReplaceVisitor(Expression from, Expression to)
        {
            this.from = from;
            this.to = to;
        }
        public override Expression Visit(Expression node)
        {
            return node == from ? to : base.Visit(node);
        }
    }
    
    0 讨论(0)
  • 2020-12-29 14:49

    I think you want to combine filters this way:

    var myFilters = new List<Expression<Func<Customer, bool>>>();
    myFilters.Add(c => c.Name.StartsWith("B"));
    myFilters.Add(c => c.Orders.Count() == 3);
    if (stranded)
    {
      myFilters.Add(c => c.Friends.Any(f => f.Cars.Any())); //friend has car
    }
    Expression<Func<Customer, bool>> filter = myFilters.AndTheseFiltersTogether();
    IEnumerable<Customer> thoseCustomers = Data.Get(filter);
    

    This code will allow you to combine your filters.

        public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(params Expression<Func<T, bool>>[] filters)
        {
            return filters.OrTheseFiltersTogether();
        }
    
        public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
        {
            if (!filters.Any())
            {
                Expression<Func<T, bool>> alwaysTrue = x => true;
                return alwaysTrue;
            }
    
            Expression<Func<T, bool>> firstFilter = filters.First();
    
            var body = firstFilter.Body;
            var param = firstFilter.Parameters.ToArray();
            foreach (var nextFilter in filters.Skip(1))
            {
                var nextBody = Expression.Invoke(nextFilter, param);
                body = Expression.OrElse(body, nextBody);
            }
            Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
            return result;
        }
    
    
        public static Expression<Func<T, bool>> AndTheseFiltersTogether<T>(params Expression<Func<T, bool>>[] filters)
        {
            return filters.AndTheseFiltersTogether();
        }
    
        public static Expression<Func<T, bool>> AndTheseFiltersTogether<T>(this IEnumerable<Expression<Func<T, bool>>> filters)
        {
            if (!filters.Any())
            {
                Expression<Func<T, bool>> alwaysTrue = x => true;
                return alwaysTrue;
            }
            Expression<Func<T, bool>> firstFilter = filters.First();
    
            var body = firstFilter.Body;
            var param = firstFilter.Parameters.ToArray();
            foreach (var nextFilter in filters.Skip(1))
            {
                var nextBody = Expression.Invoke(nextFilter, param);
                body = Expression.AndAlso(body, nextBody);
            }
            Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param);
            return result;
        }
    
    0 讨论(0)
  • 2020-12-29 14:50

    This all depends on how does ProjectRepository.Get() behave and what it returns. The usual way (for example, LINQ to SQL does something like this) is that it returns a IQueryable<T> and lets you (among other things) add more Where() clauses before sending it to the server in the form of one SQL query, with all the Where() clauses included. If this is the case, Mark's solution (use IQuerybale<T>) will work for you.

    But if the Get() method executes the query based on the filter immediately, you need to pass it the whole filter in the expression. To do that, you can use PredicateBuilder.

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