2012-03-13 20 views
5

Ho un Expression Linq, che può essere modificata a seconda dei casi. Un esempio di quello che vorrei fare (sinistra vuota il bit non sono sicuro circa):Come si può aggiornare un Expression Linq con parametri aggiuntivi?

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

Come posso aggiornare il filtro per aggiungere eventuali parametri aggiuntivi?

Al momento tutti i record vengono recuperati, allora io uso un Where per filtrare ulteriormente i risultati. Tuttavia, che si traduce in più query al database che sono strettamente necessari.

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

metodo Get sta usando il modello 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(); 
    } 
} 

Aggiornamento
Creato alcuni metodi di estensione in base al codice di seguito da Marc Gravell e David B, risolve il problema per me

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

Qual è il tipo di ritorno e interlans di 'ProjectRepository.Get (filter);'? – Oybek

+0

Che cos'è 'showAchieved'? Enumera la variabile 'projects'? – Oybek

+0

showArchived è solo un booleano – SamWM

risposta

11

Se ho capito la domanda, allora molto probabilmente ecco il problema:

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

Qualsiasi intervento sul projects sta per essere utilizzando Enumerable, non Queryable; probabilmente dovrebbe essere:

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

Quest'ultimo è componibili, e .Where dovrebbe funzionare come ci si aspetta, la costruzione di una query più restrittiva prima di inviarlo al server.

L'altra opzione è quella di riscrivere il filtro di combinare prima di inviare:

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

o ri-scritto in modo da consentire l'utilizzo comodo:

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

Il tipo di dati qui non ha importanza. – Oybek

+1

@Oybek la differenza tra 'Enumerable.Where' e' Queryable.Where' importa ** molto ** - puoi chiarire cosa stai dicendo non importa? –

+1

Funziona, è solo che ProjectRepository.Get (filtro) ottiene tutti i record per il database, quindi Dove trova di nuovo il database. Voglio fare la query del database solo una volta. Il secondo bit di codice è come lo faccio ora – SamWM

0

Se Get metodo retrives i dati e restituisce in memoria gli oggetti che potreste fare

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

solo far notare. Quando si utilizza il metodo .ToList(), esso enumera lo Queryable, ovvero effettua una richiesta di database.

+0

Che aggiunge codice ridondante extra. Il filtro iniziale potrebbe essere più di un semplice controllo del nome utente. – SamWM

0

Tutto dipende da come si comporta ProjectRepository.Get() e da cosa viene restituito. Il modo usuale (ad esempio, LINQ to SQL fa qualcosa del genere) è che restituisce un IQueryable<T> e consente (tra le altre cose) di aggiungere ulteriori clausole Where() prima di inviarlo al server sotto forma di una query SQL, con tutte le Where() clausole incluse. Se questo è il caso, la soluzione di Mark (utilizzare IQuerybale<T>) funzionerà per te.

Ma se il metodo Get() esegue la query basata sulla filter subito, è necessario passare l'intero filtro nell'espressione. Per fare ciò, è possibile utilizzare PredicateBuilder.

0

Sbarazzarsi di ToList() e starai bene.

1

penso che si desidera combinare i filtri in questo modo:

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

Questo codice vi permetterà di combinare i filtri.

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

Questo approccio non è * errato *, ma è mal supportato da molti motori LINQ; LINQ-to-SQL va bene con Expression.Invoke, comunque a EF non piace. In quanto tale, è più affidabile (e senza lavoro extra) utilizzare l'approccio "visitatore" per combinare i predicati ** direttamente **. –

+0

<3 LinqToSql. Un giorno EF raggiungerà ... –

+0

Sembra promettente, però, vuole essere il più generico possibile perché ci sono molte classi diverse coinvolte. Usando Entity Framework, ma sperando in una prova futura se qualcos'altro sarebbe mai usato (come NHibernate). Come l'idea di creare un elenco di filtri, quindi solo la combinazione prima di eseguire – SamWM

Problemi correlati