2010-09-21 20 views
15

Estoy tratando de encontrar una manera de consultar un objeto en mi modelo de datos e incluir solo aquellos parámetros que no son nulos. Como a continuación:Implementando sentencias 'if' condicionales dentro de una instrucción LINQ 'where'

public List<Widget> GetWidgets(string cond1, string cond2, string cond3) 
{ 
    MyDataContext db = new MyDataContext(); 
    List<Widget> widgets = (from w in db.Widgets 
          where 
           ... if cond1 != null w.condition1 == cond1 ... 
           ... if cond2 != null w.condition2 == cond2 ... 
           ... if cond3 != null w.condition3 == cond3 ... 
          select w).ToList(); 
    return widgets; 
} 

Desde la mesa widgets pueden ser muy grandes, me gustaría evitar hacer esto:

public List<Widget> GetWidgets(string cond1, string cond2, string cond3) 
{ 
    MyDataContext db = new MyDataContext(); 
    List<Widget> widgets = db.Widgets.ToList(); 

    if(cond1 != null) 
     widgets = widgets.Where(w => w.condition1 == cond1).ToList(); 

    if(cond2 != null) 
     widgets = widgets.Where(w => w.condition2 == cond2).ToList(); 

    if(cond3 != null) 
     widgets = widgets.Where(w => w.condition3 == cond3).ToList(); 

    return widgets; 
} 

He mirado en varios ejemplos, pero realmente no veo nada que coincide con lo que tengo que hacer.

Respuesta

29

Lo que se quiere evitar es en realidad la ejecución de la consulta hasta que esté listo:

public List<Widget> GetWidgets(string cond1, string cond2, string cond3) 
{ 
    MyDataContext db = new MyDataContext(); 
    var widgets = db.Widgets; 

    if(cond1 != null) 
     widgets = widgets.Where(w => w.condition1 == cond1); 

    if(cond2 != null) 
     widgets = widgets.Where(w => w.condition2 == cond2); 

    if(cond3 != null) 
     widgets = widgets.Where(w => w.condition3 == cond3); 

    return widgets.ToList(); 
} 

Nota cómo se eliminan los ToList llamadas. La consulta no se ejecuta hasta que empiece a iterar sobre ella. Invocar ToList forzará que eso suceda, de modo que el resultado se pueda poner en un List<> y se devuelva. Incluso sugeriría cambiar el valor de retorno del método para IEnumerable<Widget> y saltar la llamada ToList en el final:

public IEnumerable<Widget> GetWidgets(string cond1, string cond2, string cond3) 
{ 
    MyDataContext db = new MyDataContext(); 
    var widgets = db.Widgets; 

    if(cond1 != null) 
     widgets = widgets.Where(w => w.condition1 == cond1); 

    // [...] 

    return widgets; 
} 

De esta manera el código de llamada puede decidir cuándo ejecutar la consulta (que incluso puede añadir más condiciones antes de hacerlo).

+0

¿Es hasta el compilador JIT para deshacerse de los condicionales en la consulta resultante? ¿O 'linq' garantiza algunas optimizaciones? – xtofl

+0

@xtofl: ¿no estás seguro de lo que quieres decir? ¿De qué condicionales querría deshacerse? –

+0

Si no se cumplen las condiciones, no se agregan al árbol de expresiones. – Michael

2

¿Qué tal algo así?

 IEnumerable<Widget> condQuery = (from w in db.Widgets); 
     if(cond1 != null) condQuery = condQuery.Where(w=> w.condition1 == cond1); 
     if(cond2 != null) condQuery = condQuery.Where(w=> w.condition2 == cond2); 

etc ...?

+0

No compila. – Timwi

1

Actualmente está solicitando un despachador dentro de la consulta linq. El método Where toma un predicado, por lo que puede construir su predicado antes de crear la consulta.

- EDITAR - Al principio, pensé que era más fácil, escribí un pseudo código que ni siquiera compilaba. Ahora, sin embargo, creo que entiendo el punto. Este código funcionará; separa la construcción de la cláusula where de su aplicación.

static Predicate<Widget> combine( 
      Predicate<Widget> existing, 
      Predicate<Widget> condition) 
    { 
     var newpred = new Predicate<Widget>(w=> existing(w) && condition(w)); 
     return newpred; 

    } 

y utilizar esta funcionalidad 'edificio' así:

static void Main(string[] args) 
    { 
     string cond1 = "hi"; 
     string cond2 = "lo"; 
     string cond3 = null; 
     var pr = new Predicate<Widget>((Widget w) => true); 
     if (cond1 != null) pr = combine(pr, w => w.condition1 == cond1); 
     if (cond2 != null) pr = combine(pr, w => w.condition2 == cond2); 
     if (cond3 != null) pr = combine(pr, w => w.condition3 == cond3); 

lo probé con un poco de Asistente de Arreglo:

 var widgets = new Widget[]{ 
      new Widget(){ condition1 = "" }, 
      new Widget(){ condition1 = "hi", condition2 = "lo" } 
     }; 

     var selected = widgets.Where((w) => pr(w)); 

     foreach (var w in selected) { 
      Console.WriteLine(w); 
     } 
+0

No compila. – Timwi

+0

Además, cambia la lógica. OP comprueba todas las condiciones no nulas, esto solo comprueba el primero. OP también dijo que las condiciones son cadenas, mientras que esto los trata como booleanos. – curveship

+0

Incluso si solucionó los errores de compilación (las cadenas no son convertibles implícitamente a bool), esta versión del programa tiene una semántica muy diferente a la versión original. Esta versión encuentra la primera condición que puede probarse y solo la usa; la versión original aplica todas las condiciones posibles. –

20

utilizar una "o la puerta": prefacio Todos los artilugios prueba de condición con un "||" y un cheque para ver si estamos usando esa condición o no. Si no lo estamos, la segunda mitad de "o" no se evalúa. Es por eso que es una puerta: no vamos más lejos si la primera parte se evalúa como verdadera.

Si lo estuviera escribiendo, lo haría a continuación. Utilicé el azúcar sintáctico var para mantener la consulta LINQ y moví el ToList() hasta el final.

public List<Widget> GetWidgets(string cond1, string cond2, string cond3) 
{ 
    MyDataContext db = new MyDataContext(); 
    var widgets = from w in db.Widgets 
        where (cond1 == null || w.condition1 == cond1) 
        && (cond2 == null || w.condition2 == cond2) 
        && (cond3 == null || w.condition3 == cond3) 
        select w; 
    return widgets.ToList(); 
} 

edición: gramática

+0

Difícil de entender. Pero hermoso código. – Sagi

+1

Esta es la mejor solución y se ejecutará en una sola instrucción SQL. – cmartin

+1

Esta es la mejor solución, y +1 para señalar la parte ToList. –

0

podemos utilizar de forma muy sencilla, como a continuación.

(from e in employee 
join d in departments on e.departmentId equals d.departmentId 
Select new { 
e.name, 
d.name, 
getEmployeeContacts(e) 
} 
//return active contact if not return first . This is same like if else along with null check 
private contact getEmployeeContacts(Employee e) 
{ 
return e.Contacts.FirstOrDefault(x => x.Active == 1) ?? e.Contacts.FirstOrDefault(); 
} 
Cuestiones relacionadas