如何使用其他参数更新Linq表达式?

我有一个Linq表达式,可能会根据某些条件进行更改。 我想做的一个例子(留空一点我不确定):

Expression<Func> filter = (Project p) => p.UserName == "Bob"; if(showArchived) { // update filter to add && p.Archived } // query the database when the filter is built IEnumerable projects = unitOfWork.ProjectRepository.Get(filter); 

如何更新filter以添加任何额外参数?

在检索所有记录的那一刻,我使用Where进一步过滤结果。 但是,这会导致对数据库的查询多于严格必要的查询。

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

Get方法使用GenericRepository模式:

 public class GenericRepository where TEntity : class { internal ProgrammeDBContext context; internal DbSet dbSet; public GenericRepository(ProgrammeDBContext context) { this.context = context; this.dbSet = context.Set(); } public virtual IEnumerable Get( Expression<Func> filter = null, Func<IQueryable, IOrderedQueryable> orderBy = null, string includeProperties = "") { IQueryable query = dbSet; if (filter != null) { query = query.Where(filter); } foreach (var includeProperty in includeProperties.Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } else { return query.ToList(); } } public virtual TEntity GetByID(object id) { return dbSet.Find(id); } public virtual void Insert(TEntity entity) { dbSet.Add(entity); } public virtual void Delete(object id) { TEntity entityToDelete = dbSet.Find(id); Delete(entityToDelete); } public virtual void Delete(TEntity entityToDelete) { if (context.Entry(entityToDelete).State == EntityState.Detached) { dbSet.Attach(entityToDelete); } dbSet.Remove(entityToDelete); } public virtual void Update(TEntity entityToUpdate) { dbSet.Attach(entityToUpdate); context.Entry(entityToUpdate).State = EntityState.Modified; } public virtual IEnumerable GetWithRawSql(string query, params object[] parameters) { return dbSet.SqlQuery(query, parameters).ToList(); } } 

更新
根据Marc Gravell和David B的下面代码创建了一些扩展方法,为我解决了这个问题

 public static class LinqExtensionMethods { public static Expression<Func> CombineOr(params Expression<Func>[] filters) { return filters.CombineOr(); } public static Expression<Func> CombineOr(this IEnumerable<Expression<Func>> filters) { if (!filters.Any()) { Expression<Func> alwaysTrue = x => true; return alwaysTrue; } Expression<Func> firstFilter = filters.First(); var lastFilter = firstFilter; Expression<Func> result = null; foreach (var nextFilter in filters.Skip(1)) { var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body); result = Expression.Lambda<Func>(Expression.OrElse(nextExpression, nextFilter.Body), nextFilter.Parameters); lastFilter = nextFilter; } return result; } public static Expression<Func> CombineAnd(params Expression<Func>[] filters) { return filters.CombineAnd(); } public static Expression<Func> CombineAnd(this IEnumerable<Expression<Func>> filters) { if (!filters.Any()) { Expression<Func> alwaysTrue = x => true; return alwaysTrue; } Expression<Func> firstFilter = filters.First(); var lastFilter = firstFilter; Expression<Func> result = null; foreach (var nextFilter in filters.Skip(1)) { var nextExpression = new ReplaceVisitor(lastFilter.Parameters[0], nextFilter.Parameters[0]).Visit(lastFilter.Body); result = Expression.Lambda<Func>(Expression.AndAlso(nextExpression, nextFilter.Body), nextFilter.Parameters); lastFilter = nextFilter; } return result; } 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); } } } 

如果我理解这个问题,那么很可能是问题所在:

 IEnumerable projects = unitOfWork.ProjectRepository.Get(filter); 

关于projects任何工作都将使用Enumerable ,而不是Queryable ; 它可能应该是:

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

后者是可组合的 ,并且.Where应该按预期工作, 将其发送到服务器之前构建更严格的查询。

您的另一个选择是在发送之前重写要合并的filter:

 using System; using System.Linq.Expressions; static class Program { static void Main() { Expression> filter1 = x => xA > 1; Expression> filter2 = x => xB > 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>( Expression.AndAlso(rewrittenBody1, filter2.Body), filter2.Parameters); // newFilter is equivalent to: x => xA > 1 && xB > 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); } } 

或者以允许方便使用的方式重写:

 using System; using System.Linq.Expressions; static class Program { static void Main() { Expression> filter = x => xA > 1; bool applySecondFilter = true; if(applySecondFilter) { filter = Combine(filter, x => xB > 2.5); } var data = repo.Get(filter); } static Expression> Combine(Expression> filter1, Expression> 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>( 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); } } 

我想你想用这种方式组合filter:

 var myFilters = new List>>(); 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> filter = myFilters.AndTheseFiltersTogether(); IEnumerable thoseCustomers = Data.Get(filter); 

此代码将允许您组合filter。

  public static Expression> OrTheseFiltersTogether(params Expression>[] filters) { return filters.OrTheseFiltersTogether(); } public static Expression> OrTheseFiltersTogether(this IEnumerable>> filters) { if (!filters.Any()) { Expression> alwaysTrue = x => true; return alwaysTrue; } Expression> 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> result = Expression.Lambda>(body, param); return result; } public static Expression> AndTheseFiltersTogether(params Expression>[] filters) { return filters.AndTheseFiltersTogether(); } public static Expression> AndTheseFiltersTogether(this IEnumerable>> filters) { if (!filters.Any()) { Expression> alwaysTrue = x => true; return alwaysTrue; } Expression> 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> result = Expression.Lambda>(body, param); return result; } 

这一切都取决于ProjectRepository.Get()行为和返回的内容。 通常的方法(例如,LINQ to SQL做这样的事情)是它返回一个IQueryable并允许你(除其他外)在以一个SQL的forms将它发送到服务器之前添加更多Where()子句查询,包含所有Where()子句。 如果是这种情况,Mark的解决方案(使用IQuerybale )将适合您。

但是如果Get()方法立即基于filter执行查询,则需要将表达式中的整个filter传递给它。 为此,您可以使用PredicateBuilder

如果Get方法检索数据并在内存对象中返回,则可以执行此操作

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

编辑

只是要指出。 当您使用.ToList()方法时,它枚举Queryable ,即发出数据库请求。

摆脱ToList() ,你会没事的。