2012-03-13 2 views
5

У меня есть выражение Linq, которое может быть изменено в зависимости от определенных условий. Пример того, что я хотел бы сделать (левый пустой бит я не уверен):Как вы можете обновить выражение Linq с дополнительными параметрами?

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

Как обновить фильтр, чтобы добавить какие-либо дополнительные параметры?

В настоящий момент все записи извлекаются, затем я использую Where для дальнейшего фильтрации результатов. Однако это приводит к большему количеству запросов к базе данных, чем это строго необходимо.

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

Get метод использует GenericRepository шаблон:

public class GenericRepository<TEntity> where TEntity : class 
{ 
    internal ProgrammeDBContext context; 
    internal DbSet<TEntity> dbSet; 

    public GenericRepository(ProgrammeDBContext context) 
    { 
     this.context = context; 
     this.dbSet = context.Set<TEntity>(); 
    } 

    public virtual IEnumerable<TEntity> Get(
     Expression<Func<TEntity, bool>> filter = null, 
     Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
     string includeProperties = "") 
    { 
     IQueryable<TEntity> 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<TEntity> GetWithRawSql(string query, params object[] parameters) 
    { 
     return dbSet.SqlQuery(query, parameters).ToList(); 
    } 
} 

Update
создал некоторые методы расширения, основанные на коде ниже Марк Gravell и Дэвид B, решает эту проблему для меня

public static class LinqExtensionMethods 
{ 
    public static Expression<Func<T, bool>> CombineOr<T>(params Expression<Func<T, bool>>[] filters) 
    { 
     return filters.CombineOr(); 
    } 

    public static Expression<Func<T, bool>> CombineOr<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 lastFilter = firstFilter; 
     Expression<Func<T, bool>> 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<T, bool>>(Expression.OrElse(nextExpression, nextFilter.Body), nextFilter.Parameters); 
      lastFilter = nextFilter; 
     } 
     return result; 
    } 

    public static Expression<Func<T, bool>> CombineAnd<T>(params Expression<Func<T, bool>>[] filters) 
    { 
     return filters.CombineAnd(); 
    } 

    public static Expression<Func<T, bool>> CombineAnd<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 lastFilter = firstFilter; 
     Expression<Func<T, bool>> 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<T, bool>>(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); 
     } 
    } 
} 
+0

Каков тип возврата и интерланы 'ProjectRepository.Get (filter);'? – Oybek

+0

Что такое 'showAchieved'? Перечисляет ли она переменную 'projects'? – Oybek

+0

showArchived - это просто логическое значение – SamWM

ответ

11

Если я понимаю вопрос, то, скорее всего, здесь проблема:

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

Любая работа на projects собирается использовать Enumerable, не Queryable; это, вероятно, следует:

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

Последнее компонуемы и .Where должны работать, как ожидается, создание более ограничительный запрос перед тем отправкой его на сервер.

Другой вариант заключается в переписать фильтр объединить перед отправкой:

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

Или переписана таким образом, чтобы обеспечить удобное использование:

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

Тип данных здесь не имеет значения. – Oybek

+1

@ Oybek разница между 'Enumerable.Where' и' Queryable.Where' имеет значение ** очень много ** - можете ли вы пояснить, что вы говорите, не имеет значения? –

+1

Он работает, это просто, что ProjectRepository.Get (фильтр) получает все записи для базы данных, затем «Где» снова удаляет базу данных. Я хочу сделать запрос базы данных только один раз. Второй бит кода - это то, как я делаю это сейчас. – SamWM

0

Если вы Get метод retrives данные и возвращает в объекты памяти вы могли бы сделать это

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

Просто чтобы указать. Когда вы используете метод .ToList(), он перечисляет Queryable, т. Е. Делает запрос базы данных.

+0

Это добавляет дополнительный избыточный код. Исходный фильтр может быть больше, чем просто проверка имени пользователя. – SamWM

0

Все зависит от того, как ведет себя ProjectRepository.Get() и что он возвращает. Обычный способ (например, LINQ to SQL делает что-то вроде этого) заключается в том, что он возвращает IQueryable<T> и позволяет вам (помимо прочего) добавлять дополнительные Where() предложения перед отправкой на сервер в виде одного SQL-запроса со всеми Where() предложения включены. Если это так, решение Марка (используйте IQuerybale<T>) будет работать для вас.

Но если метод Get() выполняет запрос на основе filter, вам необходимо передать ему весь фильтр в выражении. Для этого вы можете использовать PredicateBuilder.

1

Я думаю, что вы хотите объединить фильтры таким образом:

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

Этот код позволит вам комбинировать фильтры.

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

Этот подход не является * неправильным *, но плохо поддерживается многими двигателями LINQ; LINQ-to-SQL отлично работает с Expression.Invoke, однако EF ему это не нравится. Таким образом, более надежно (и без дополнительной работы) использовать подход «посетителя» для непосредственного комбинирования предикатов **. –

+0

<3 LinqToSql. Когда-нибудь EF догонит ... –

+0

Выглядел многообещающе, хотя и хочу быть настолько общим, насколько я могу, поскольку в нем задействовано много разных классов. Использование Entity Framework, но надеясь на будущее доказательство, если что-то еще будет использовано (например, NHibernate). Подобно идее создания списка фильтров, а затем просто комбинируя перед выполнением – SamWM

Смежные вопросы