2009-08-12 21 views
25

Basado en my question from yesterday:Cómo anexar a una expresión

si tuviera que añadir a mi 'donde' expresión existente, ¿Cómo puedo añadir?

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    clientWhere = c => c.ClientFName == searchForClientFName; 
} 

if (filterByClientLName) 
    { 
     clientWhere = c => c.ClientLName == searchForClientLName; 
    } 

El usuario puede ingresar el nombre o el apellido o ambos. Si ingresan ambos quiero agregar a la expresión. Tratando de ver si hay un equivalente de un append en el que podía hacer

clientWhere.Append or clientWhere += add new expression 

o algo similar

+0

He estado tratando de encontrar una solución similar, porque usamos nuestra herramienta ORML desarrollada por nuestro equipo, que admite las operaciones adjuntas por "Y" o "O" y nuestro código depende en gran medida de la extensión. Hasta ahora no hemos podido cambiar a linq, pero básicamente linq crea IExpression , y si encuentra una forma de crear el árbol IExpression usted mismo, eso puede ayudar. –

Respuesta

7

Este es un escenario complejo. Está casi construyendo su propio motor de búsqueda sobre LINQ. La solución de JaredPar (¿dónde fue?) Es excelente si quieres un AND lógico entre todos tus criterios, pero puede no ser siempre el caso.

Cuando estaba discutiendo con esto en uno de mi proyecto recientemente, he creado dos listas:

List<Predicate<T>> andCriteria; 
List<Predicate<T>> orCriteria; 

(En este caso, T es cliente, para usted)

que poblaron las Listas con predicados que quiero que sean verdaderas Por ejemplo,

decimal salRequirement = 50000.00; 
andCriteria.Add(c => c.Salary > salRequirement); 
orCriteria.Add(c => c.IsMarried); 

Luego, verificaría todos los criterios en las Listas en mi cláusula Where. Por ejemplo:

Expression<Func<Client, bool>> clientWhere = 
    c => andCriteria.All(pred => pred(c)) && orCriteria.Any(pred => pred(c)); 

Esto también se puede hacer con un bucle for para mayor legibilidad. Recuerde utilizar el orden correcto de operaciones al aplicar sus cláusulas OR y AND.

+0

Tenga en cuenta que esto también se ocupa del caso en el que desea hacer más que simplemente usar "==", ya que los predicados podrían ser * cualquier * función booleana. – JoshJordan

+0

Buena solución Josh! – grenade

+1

Josh: si lo construyo con su estilo, que es genial por cierto, y luego llame a la consulta: var query = desde C en db.clients.Where (clientWhere) junte O en db.orders.Where (orderWhere) en c.clientid es igual a O.clientid unirse a P en db.products.Where (productWhere) en O.productid es igual a P.productid seleccione new {C, O}; Aparece el siguiente error: La secuencia local no se puede utilizar en la implementación LINQ to SQL de los operadores de consulta, excepto el operador Contiene(). –

33

creo que sólo puede hacer lo siguiente:

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    var prefix = clientWhere.Compile(); 
    clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName; 
} 
if (filterByClientLName) 
{ 
    var prefix = clientWhere.Compile(); 
    clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName; 
} 

Si necesita mantener todo en Expression Land (para usar con IQueryable), también se puede hacer lo siguiente:

Expression<Func<Client, bool>> clientWhere = c => true; 

if (filterByClientFName) 
{ 
    Expression<Func<Client, bool>> newPred = 
     c => c.ClientFName == searchForClientFName; 
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
     Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters); 
} 
if (filterByClientLName) 
{ 
    Expression<Func<Client, bool>> newPred = 
     c => c.ClientLName == searchForClientLName; 
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
     Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters); 
} 

Esto se puede hacer menos detallado al definir este método de extensión:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right) 
{ 
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters); 
} 

continuación, puede utilizar la sintaxis como esta:

Expression<Func<Client, bool>> clientWhere = c => true; 
if (filterByClientFName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName); 
} 
if (filterByClientLName) 
{ 
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName); 
} 
+5

Jason: Aprender con toda la información que todos han proporcionado. Intenté su camino, al definir la extensión en una clase estática, y obtuve un error: El operador binario AndAlso no está definido para los tipos 'System.Func'2 [Models.Client, System.Boolean]' y 'System.Func'2 [Models.Client, System.Boolean]'. –

+4

Incluso si corrige el error en el método de extensión, esta es una forma muy frágil de hacerlo. Ver mi respuesta a http://stackoverflow.com/questions/2231302/append-to-an-expression-c para más detalles. –

+1

@Jason: Demasiados usuarios con el nombre para mostrar "Jason" en mi opinión. – jason

6

Tome un vistazo a Predicate Builder, creo que esto podría funcionar para usted.

+1

Predicate Builder es el truco. Funciona muy bien con LinqToSQL y es, sobre todo, sus características, fácil de usar. –

+0

¡También está en Nuget, perfecto para mí, gracias! – VinnyG

0

O algo que añadir a Josh (Póngalo en mi bolsa de trucos):

public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource> 
     { 
      //Yeah :) 
      Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O)); 
      return SearchObject.Where<TSource>(ObjectWhere); 
     } 
1

Está no es exactamente la respuesta para su pregunta, pero, yo estaba buscando el mismo que tú, y luego encontré una mejor respuesta a mi pregunta.

En lugar de construir una expresión dinámica, se puede recuperar el IQueryable y luego filtrar lo que quiere de esta manera:

var customers = CustomerRepository.AllEntities(); 

if (!forename.IsNullOrEmpty()) 
    customers = customers.Where(p => p.Forename == forename); 
if (!familyname.IsNullOrEmpty()) 
    customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname)); 
if (dob.HasValue) 
    customers = customers.Where(p => p.DOB == dob); 

Nota: que estaba preocupado por la ejecución de más de una declaración ".Where" porque tenía miedo de que esto generaría más de una consulta en la Base de Datos, o porque tendría que recuperar todos los registros y luego filtrarlos, pero esto no es cierto, Dinámica de Linq solo genera una consulta cuando se llama a .ToList () método.

Here puede ver la pregunta original de la que tomé el ejemplo.

0

Intenté implementar este tipo de cosas. Me llevó un día averiguarlo. Mi solución se basa en el filtro en un bucle basado en una matriz de predicado. Como nota, es Reflejo totalmente genérico y basado porque la única información sobre clase y campo es Cadena. Para hacerlo simple, llamo directamente a la clase Model pero en un proyecto debe ir por un controlador que está llamando al Modelo.

Así que aquí vamos: la parte del modelo, donde T es un genérico en la clase

public class DALXmlRepository<T> where T : class 
    { 
    public T GetItem(Array predicate) 
    { 
     IQueryable<T> QueryList = null; 

     QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0)); 
     for (int i = 1; i < predicate.GetLength(0); i++) 
     { 
      QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i)); 
     } 

     if (QueryList.FirstOrDefault() == null) 
      throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found."); 
     return QueryList.FirstOrDefault(); 
    } 
    } 

Ahora el LambdaExpression constructor, que es una base (con el tipo de cuerda o algo más), se puede mejorar con más Funcionalidad:

private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue) 
    { 
     LambdaExpression lambda = null; 

     Expression Criteria = null; 

     Random r = new Random(); 
     ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString()); 

     if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string)) 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null); 
      //Type du champ recherché 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(FieldValue, propType); 
      Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null); 
      Criteria = Expression.Equal(LefttoUpper, RighttoUpper); 
     } 
     else 
     { 
      Expression left = Expression.PropertyOrField(predParam, FieldName); 
      Type propType = GenericArgument.GetProperty(FieldName).PropertyType; 
      Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType); 

      Criteria = Expression.Equal(left, right); 
     } 

     lambda = Expression.Lambda(Criteria, predParam); 
     return lambda; 
    } 

Ahora la función de llamada:

public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter) 
    { 
     //Get the type 
     Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel"); 
     Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType(type); 
     //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML); 
     ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) }); 
     IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null }); 

     //Building the string type Expression<func<T,bool>> to init the array 
     Type FuncType = typeof(Func<,>).MakeGenericType(type ,typeof(bool)); 
     Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType); 
     Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count); 

     MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() }); 

     if (method == null) 
      throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name); 

     int j = 0; 
     IDictionaryEnumerator criterias = FieldFilter.GetEnumerator(); 
     criterias.Reset(); 
     while (criterias.MoveNext()) 
     { 
      if (!String.IsNullOrEmpty(criterias.Key.ToString())) 
      { 
       lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j); 
      } 
      else 
      { 
       throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString()); 
      } 
      j++; 
     } 

     Object item = method.Invoke(DalInstance, new object[] { lambda }); 
     } 

El argumento es: Entidad de cadena: nombre de clase de entidad. XMLContext: es la unidad de trabajo del repositorio, argumento que uso para inicializar la clase Model Hashtable FieldsNameToGet: Índice/valor de la lista del campo que quiero recuperar Hashtable FieldFilter: la clave/Valor con FieldName/Contenido utilizado para hacer la expresión Lambda

Buena suerte.