2011-06-23 9 views
5

Estoy buscando implementar un sistema por el cual use las condiciones 'compilar' y luego devolver los datos resultantes de la base de datos. En la actualidad, hay un procedimiento almacenado que genera SQL sobre la marcha y lo ejecuta. Este es un problema particular que quiero eliminar.Consulta dinámica de linq con criterios múltiples/desconocidos

Mi problema proviene del hecho de que puedo tener múltiples campos dentro de mis criterios, y para cada uno de estos campos, podría haber 1 o más valores, con diferentes operadores potenciales.

Por ejemplo,

from t in Contacts 
where t.Email == "[email protected]" || t.Email.Contains ("mydomain") 
where t.Field1 == "valuewewant" 
where t.Field2 != "valuewedontwant" 
select t 

El campo, criterios y operador se almacenan en la base de datos (y List<FieldCriteria>) y habría alguna cosa como esta (basado en más arriba);

Email, Equals, "[email protected]" 
Email, Contains, "mydomain" Field1, 
Equals, "valuewewant" Field2, 
DoesNotEqual, "valuewedontwant" 

o

new FieldCriteria 
{ 
FieldName = "Email", 
Operator = 1, 
Value = "[email protected]" 
} 

Así, utilizando la información que tengo, quiero ser capaz de construir una consulta con cualquier número de condiciones. He visto enlaces anteriores a Dynamic Linq y PredicateBuilder, pero no puedo visualizar esto como una solución a mi propio problema.

Cualquier sugerencia sería apreciada.

actualización

A raíz de la sugerencia sobre Dynamic LINQ, se me ocurrió una solución muy básica, usando un solo operador, con 2 campos y múltiples criterios. Un poco crudo por el momento codificado en LinqPad, pero los resultados son exactamente lo que quería;

enum Operator 
{ 
    Equals = 1, 
} 

class Condition 
{ 
    public string Field { get; set; } 
    public Operator Operator { get; set;} 
    public string Value { get; set;} 
} 

void Main() 
{ 
    var conditions = new List<Condition>(); 

    conditions.Add(new Condition { 
     Field = "Email", 
     Operator = Operator.Equals, 
     Value = "[email protected]" 
    }); 

    conditions.Add(new Condition { 
     Field = "Email", 
     Operator = Operator.Equals, 
     Value = "[email protected]" 
    }); 

    conditions.Add(new Condition { 
     Field = "Field1", 
     Operator = Operator.Equals, 
     Value = "Chris" 
    }); 

    var statusConditions = "Status = 1"; 

    var emailConditions = from c in conditions where c.Field == "Email" select c; 
    var field1Conditions = from c in conditions where c.Field == "Field1" select c; 


    var emailConditionsFormatted = from c in emailConditions select string.Format("Email=\"{0}\"", c.Value); 
    var field1ConditionsFormatted = from c in field1Conditions select string.Format("Field1=\"{0}\"", c.Value); 

    string[] conditionsArray = emailConditionsFormatted.ToArray(); 
    var emailConditionsJoined = string.Join("||", conditionsArray); 
    Console.WriteLine(String.Format("Formatted Condition For Email: {0}",emailConditionsJoined)); 

    conditionsArray = field1ConditionsFormatted.ToArray(); 
    var field1ConditionsJoined = string.Join("||", conditionsArray); 
    Console.WriteLine(String.Format("Formatted Condition For Field1: {0}",field1ConditionsJoined)); 



    IQueryable results = ContactView.Where(statusConditions); 

    if (emailConditions != null) 
    { 
     results = results.Where(emailConditionsJoined); 
    } 

    if (field1Conditions != null) 
    { 
     results = results.Where(field1ConditionsJoined); 
    } 

    results = results.Select("id"); 

    foreach (int id in results) 
    { 
     Console.WriteLine(id.ToString()); 
    } 
} 

Con un SQL generado de;

-- Region Parameters 
DECLARE @p0 VarChar(1000) = 'Chris' 
DECLARE @p1 VarChar(1000) = '[email protected]' 
DECLARE @p2 VarChar(1000) = '[email protected]' 
DECLARE @p3 Int = 1 
-- EndRegion 
SELECT [t0].[id] 
FROM [Contacts].[ContactView] AS [t0] 
WHERE ([t0].[field1] = @p0) AND (([t0].[email] = @p1) OR ([t0].[email] = @p2)) AND ([t0].[status] = @p3) 

y la salida de la consola:

Formatted Condition For Email: Email="[email protected]"||Email="[email protected]" 
Formatted Condition For Field1: Field1="Chris" 

sólo necesitan limpiar esto y añadir los otros operadores y que se ve bien.

Si alguien tiene algún comentario sobre este hasta el momento, cualquier entrada sería apreciada

Respuesta

1

Creo que LINQ dinámica será uno de opción. DLINQ le permite especificar parte de la consulta LINQ como "cadena" y DLINQ luego compila esa cadena en el árbol de expresiones para que se pase al proveedor LINQ subyacente. Su necesidad también es la misma, es decir, necesita crear árboles Expression en tiempo de ejecución.

Le sugiero que haga la propiedad Operator en FieldCriteria como Enum que representan todas las operaciones requeridas (es decir, menos, etc.). Luego necesitará escribir una función que tome una lista de FieldCriteria y retorne una cadena de "expresión" que luego puede alimentar a DLINQ para obtener el árbol de expresiones.

+0

La idea general de "LINQ to " es convertir expresiones estáticas de tiempo de compilación de C# a algún tipo de cadena que se enviará a la base de datos subyacente para su ejecución. Su sugerencia de usar una cadena para convertirla en una expresión linq que a su vez se convierte en una cadena parece un poco redundante :) –

+0

Tiene razón acerca de la expresión "estática" en SQL, pero en la pregunta el usuario no sabe sobre la expresión en tiempo de compilación y, por lo tanto, necesita generar expresiones en tiempo de ejecución. DLINQ es una forma de hacerlo desde "cadenas" y de otra manera es usar Expression API para crear las expresiones necesarias en tiempo de ejecución. – Ankur

9

El truco con LINQ sería construir un Expression a partir de los datos.A modo de ejemplo, para ilustrar el ejemplo mostrado:

var param = Expression.Parameter(typeof(MyObject), "t"); 

var body = Expression.Or(
      Expression.Equal(Expression.PropertyOrField(param, "Email"), Expression.Constant("[email protected]")), 
      Expression.Call(Expression.PropertyOrField(param, "Email"), "Contains", null, Expression.Constant("mydomain")) 
     ); 

body = Expression.AndAlso(body, Expression.Equal(Expression.PropertyOrField(param, "Field1"), Expression.Constant("valuewewant"))); 
body = Expression.AndAlso(body, Expression.NotEqual(Expression.PropertyOrField(param, "Field2"), Expression.Constant("valuewedontwant"))); 

var lambda = Expression.Lambda<Func<MyObject, bool>>(body, param); 

var data = source.Where(lambda); 

En particular, observar cómo AndAlso puede ser usado para componer las diferentes operaciones (el mismo que múltiples Where, pero más simple).

+0

El código anterior no requiere que conozca los campos/criterios/operador. En mi escenario. no se conoce ninguno en el momento de la compilación y debe ser completamente dinámico a ese respecto. Perdóname estoy equivocado ya que las funciones de Lambda están un poco fuera de mi conocimiento actual. – ChrisBint

+0

@Chris no, pero debería escribir algún código que genere las expresiones –

+0

cómo usar IN inside where clause? – Neo

-2

Esto puede ser hecho simplemente por Linq donde se conectan operadores adicionales al objeto de consulta. Aquí hay un ejemplo.

query = db.Contacts.Where(...); 
query = query.Where(...); 
query = query.Where(...); 

Esta es una solución más simple y corta.

Cuestiones relacionadas