2012-08-27 10 views
7

Voy a comenzar con esto estoy buscando activamente la solución a este problema, pero pensé que podría acortar un poco el tiempo de investigación y desarrollo si alguien aquí en la pila ya se ha dado cuenta. (No he encontrado nada en línea así que aquí va)Encadenar las condiciones OR en EF 5.0

Tenemos un caso en un marco de aplicación que estamos construyendo donde necesitamos la capacidad de tomar en un conjunto de Predicados (List<Expression<Func<T,bool>>>) y analizarlo en un marco de búsqueda.

En este momento tenemos la capacidad para filtrar de esta manera es que:

//Assume predicates is passed as a method argument. 
//  of List<Expression<Func<T,bool>>> 
//Assume user is passed in as a method argument. 
//Assume FilterToUserAccess is a custom extension method that restricts the dataset 
// to access restrictions. 
var query = _dbContext.Set<EntityType>() 
    .FilterToUserAccess(user); 
foreach(var p in predicates){ 
    query = query.Where(p); 
} 

return p.ToList(); 

La razón por la que tenemos que hacer esto es para ampliación de la capacidad de los objetos que se pueden filtrar. Sin embargo, para una búsqueda rápida esto no es posible debido a las capacidades integradas de EF. Lo que necesito hacer es:

Objeto A (imaginemos que es un coche de carreras) y queremos buscar marca, modelo, equipo y conductor en un cuadro de búsqueda rápida. Entonces, si ingreso "Earnhardt", buscará todas las propiedades de la entidad del auto de carrera que sean marca, modelo, equipo y controlador. Terminaría con todos los autos DEI y con Dale Jr. Me gustaría usar el mismo enfoque para poder configurar una entidad con capacidad de búsqueda y reflejar la configuración de búsqueda al inicio de la aplicación. Yo lo ideal sería realizar alguna manera de tener la consulta un aspecto similar a este:

//Assume predicates is passed as a method argument. 
//  of List<Expression<Func<T,bool>>> 
//Assume user is passed in as a method argument. 
//Assume FilterToUserAccess is a custom extension method that restricts the dataset 
// to access restrictions. 
var query = _dbContext.Set<EntityType>() 
    .FilterToUserAccess(user); 
foreach(var p in predicates){ 
    query = query.Or(p); 
} 

return p.ToList(); 

que se dan cuenta de que puedo hacer:

_dbContext.Set<EntityType>().Where(predicate1 || predicate2 || predicate3) 

Sin embargo, esto no va a funcionar para el enfoque que queremos dar a resuelve este problema. Lo ideal es que un administrador de uno de nuestros sitios cliente pueda ingresar y configurar un término de búsqueda adicional con un solo clic para ser incluido en todas y cada una de las búsquedas rápidas para ese tipo de entidad como actualmente podemos realizar con los filtros que usan el estándar .Where(...) "y" encadenando lógica.

+0

Tendrá que crear un árbol de expresiones dinámicamente. –

+0

¿Hay posibles lugares de partida para esto? Conozco los conceptos básicos de la generación de un árbol de expresión, pero me temo que este agujero de conejo es bastante profundo y rápido. Cualquier referencia sería muy apreciada. – VulgarBinary

+2

Puede consultar LinqKit/Expression Builder. EB funciona para EF y hace que sea muy fácil encadenar condiciones de 'OR'. –

Respuesta

6

La primera solución fue un fracaso, sin embargo, con un poco más de excavación, existe una solución increíblemente simple, verificada y que funciona.

Paso 1: instale el paquete NuGet para LinqKit.

Paso 2: Disfrute el código de abajo

using (ISampleRepository repo = new SampleRepository()) 
{ 
    var predicates = new List<Expression<Func<Customer,bool>>>(){ 
     (x => x.FirstName.Contains(searchValue)), 
     (x => x.LastName.Contains(searchValue)) 
    }; 

    var lambda = PredicateBuilder.False<Customer>(); 
    lambda = predicates.Aggregate(lambda, (current, p) => current.Or(p).Expand()); 

    var query = repo.QueryCustomers().AsExpandable().Include(x => x.Phones).Where(lambda); 
    return query.Take(500) 
     .ToList() 
     .Select(x => x.ToDTO()) 
     .ToList(); 
} 

Esto es sólo la muestra de pico, pero haciendo lo mismo con un método que tenga en ->

List<T> QuickSearch<T>(string input) ... 

Será capaz de utilizar el mismo enfoque Tiene una colección de predicados aún en forma de expresión pasada, luego utiliza los trucos del generador de predicados para desactivar la consulta. Luego, usar AsExpandable() le permite ejecutar el predicado combinado creado usando el generador de predicados.

Afortunadamente esto es útil para algo más que para mí, pero esta es la solución que voy a usar, ya que es un código bastante menos. Le permite construir sus predicados en otra parte ... y aun así combinarlos en una declaración "O" después del hecho.

+0

¿Ya ha probado esto en una memoria invocable? Me preocuparía que la expresión del parámetro no permanezca en el alcance entre cada expresión. – mclark1129

+0

En proceso de prueba mientras hablamos. Las expresiones de parámetros usan constantes para formar la lista de predicados. El otro es siempre que el param se defina como la misma variable en la que se encuentran dentro del alcance. Esto lo sé por el vudú que obtuvimos con el enfoque de filtro. Mi única preocupación aquí es el OR encadenado que tiene un problema y no compila en SQL nativo. Lo actualizaré tan pronto como verifique si esto funciona o no. – VulgarBinary

+0

Esto es probablemente solo un error tipográfico, pero su expresión ejecutable contiene 'Func ' mientras que el resto del ejemplo es 'T' genérico. – mclark1129

2

Como dice Ladislav, tendrá que generar dinámicamente sus expresiones LINQ.Aquí está un ejemplo sencillo de un programa que genera dinámicamente un predicado para una colección de números enteros:

class Program { 
    static void Main(string[] args) { 

     // Retreive your data source 
     List<int> numbers = new List<int>() { 0, 10, 20, 30, 40, 50, 60 }; 

     // Create a collection of predicates that you would like to chain together. 
     ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "x"); 
     List<Expression> predicates = new List<Expression>(); 

     // x >= 50 
     predicates.Add(Expression.GreaterThanOrEqual(parameterExpression, Expression.Constant(50))); 

     // x <= 20 
     predicates.Add(Expression.LessThanOrEqual(parameterExpression, Expression.Constant(20))); 

     // Build a single predicate by chaining individual predicates together in an OR fashion 
     Expression whereFilter = Expression.Constant(false); // Use false a base expression in OR statements 

     foreach (var predicate in predicates) { 
      whereFilter = Expression.OrElse(whereFilter, predicate); 
     } 

     // Once the expressions have been chained, create a lambda to represent the whole predicate 
     // x => (x >= 50) || (x <= 20) 
     Expression<Func<int, bool>> whereLambda = 
      (Expression<Func<int, bool>>)Expression.Lambda(whereFilter, 
              new List<ParameterExpression>() { parameterExpression }); 

     // To use an expression directly, the datasource must be an IQueryable 
     // Since I am using List<T> I must call AsQueryable. This is not necessary 
     // if your collection is already IQueryable, like in Entity Framework. 
     var results = numbers.AsQueryable().Where(whereLambda); 

    } 
} 

En esencia todo lo que hacemos aquí es crear varios statments booleanas (x> = 50) y (x < = 20) y colócalos en una colección. Luego al pasar por esa colección, tomo cada declaración y la repito hasta el último. El resultado es una serie de declaraciones booleanas, todas vinculadas entre sí por OR. Luego envuelvo esa declaración en una expresión Lambda para que pueda ser consumida por IQueryable.Where y la pase a mi colección consultable. Los resultados son un conjunto filtrado de enteros de mi conjunto completo.

Las expresiones de LINQ pueden ser confusas, por decir lo menos, pero son increíblemente poderosas y vale la pena conocerlas. Avíseme si hay algo que pueda hacer para ayudar a que tenga más sentido este ejemplo.

+0

"ejemplo simple" ... –

+0

tope para una respuesta muy bien redactada y solo unos momentos después de que llegué con la mía. Todavía muy bien organizado y muy detallado. ¡Gracias! – VulgarBinary

+0

Acepto, las expresiones LINQ tienen una sintaxis bastante prolija y confusa. Diría que es bastante simple en lo que respecta a las expresiones LINQ, simplemente encadenando unas simples expresiones booleanas. – mclark1129

Cuestiones relacionadas