[How To] Consultas dinámicas con Linq

Hola! Nuevamente surge un post por la inquietud de un amigo.

El problema

La situación es sencilla, necesito hacer un filtro pero desconozco el número, nombre y valor de parámetros que me llegaran pues los filtros son opcionales.

Papel y lápiz

El primer approach sería una serie de if con el número de combinaciones posibles y a partir de ahí construir ya sean distintas sentencias Linq por cada bloque condicional o intentar construir un árbol de expresión para luego correrla en el query. Pero sería algo tedioso de construir y de dar mantenimiento.

El segundo approach lo enfoqué entonces a algo más sencillo… reflection, un diccionario y dynamic linq. ¿Qué intento hacer? Tener un objeto que me sirva de filtro (perfectamente un Viewmodel de asp.net MVC) y tener las opciones de filtro ha querido usar el usuario (que no estén vacías), pasar estas a un diccionario que contenga el nombre de la propiedad y el valor de esta y finalmente emplear Dynamic Linq para que me entienda una consulta del tipo .Where(“Propiedad == @0”, valor).

El Ejemplo

Para el ejemplo me he hecho una aplicación en consola, donde tengo definida una clase, así:

    class Persona
    {
        public int Edad { get; set; }
        public string Nombre { get; set; }
        public string Apellido { get; set; }
    }

Y todo el resto lo he metido en el main() 😛 así:

        private static void Main(string[] args)
        {
            //Datos
            var personas = new List<Persona>
                               {
                                   new Persona {Edad = 19, Nombre = "Nicolas", Apellido = "Herrera"},
                                   new Persona {Edad = 15, Nombre = "Santiago", Apellido = "Herrera"},
                                   new Persona {Edad = 50, Nombre = "Pepe", Apellido = "Sierra"}
                               };

            //Creacion del objeto de filtro
            var personaFiltro = new Persona();
            Console.WriteLine("Ingrese el nombre, el apellido y la edad para buscar: ");
            personaFiltro.Nombre = Console.ReadLine();
            personaFiltro.Apellido = Console.ReadLine();
            personaFiltro.Edad = int.Parse(Console.ReadLine());

            //Separacion de las propiedades que el usuario no uso
            var properties = personaFiltro
                .GetType()
                .GetProperties()
                .Where(x => x != null &&
                            !string.IsNullOrEmpty(x.GetValue(personaFiltro).ToString()));

            //Creacion de un diccionario que contendrá nombre de la propiedad y valor
            var diccionario = properties.ToDictionary(propiedad =>
                                              propiedad.Name, propiedad =>
                                                              propiedad.GetValue(personaFiltro));

            //Creacion del predicado
            IEnumerable<string> filtros =
                diccionario.Keys.Select(
                (clave, index) => string.Format("{0} == @{1}", clave, index));
            string predicado = string.Join(" and ", filtros);

            //resultado
            var resultado = personas.AsQueryable()
                .Where(predicado, diccionario.Values.ToArray());

            //Publicacion de los resultados
            Console.WriteLine("Los resultados de la busqueda:");
            foreach (var persona in resultado)
            {
                Console.WriteLine("Nombre: {0} - Apellido: {1}", persona.Nombre, persona.Apellido);
            }
        }

NOTA: Para conseguir Dynamic Linq solo hace falta buscarlo en Nuget e instalarlo.

La prueba.

Conclusión

Como hemos visto el tema ha resultado un poco más sencillo gracias al uso de Dynamic Linq 😀

Como siempre, si alguien tiene un mejor enfoque, conoce alguna otra forma de resolver este problema, tiene críticas constructivas o destructivas NO DUDEN en comentar.

Espero les sea de utilidad.

Hasta el próximo post.

Anuncios
[How To] Consultas dinámicas con Linq

14 comentarios en “[How To] Consultas dinámicas con Linq

  1. Hey, muy interesante, yo estoy trabajando con Azure, para Just Fair DK, estamos desarrollando aplicaciones de trazabilidad para commodities como café y me gustaría compartir experiencias. Saludos.

  2. Andres dijo:

    Nicolas hermano primero que todo muchas gracias por los buenos post que coloca y en especial este estoy tratando de hacer unos filtros para unos formularios con asp.net y me pareció interesante hacerlos con la funcionalidad que expone en este articulo pero estoy teniendo problemas con la implementacion tengo algo asi :

    List paises = servicio.ObtenerPais(ref mensajeDeError);

    //Creacion del objeto de filtro
    var personaFiltro = new Pais();

    personaFiltro.Nombre = “Colombia”;
    personaFiltro.IdPais = 20;

    //Separacion de las propiedades que el usuario no us
    var properties = personaFiltro
    .GetType()
    .GetProperties()
    .Where(x => x != null &&
    !string.IsNullOrEmpty(x.GetValue(personaFiltro).ToString()));

    //Creacion de un diccionario que contendrá nombre de la propiedad y valor
    var diccionario = properties.ToDictionary(propiedad =>
    propiedad.Name, propiedad =>
    propiedad.GetValue(personaFiltro));

    donde personaFiltro me sale un error y pues no comprendo por que no se si me podría dar una orientación de cual sea mi problema

    Gracias por su atención

      1. Andres dijo:

        Nicolas los errores son estos

        GetValue’ takes 1 arguments

        Cannot convert lambda expression to type ‘System.Collections.Generic.IEqualityComparer’ because it is not a delegate type

        no si es por el tipo de datos o por que quede ahí loco

        Gracias por su atención

      2. Andres dijo:

        Bueno la respuesta a mi pregunta era esta pro si a alguien le pasa lo mismo

        var properties = personaFiltro
        .GetType()
        .GetProperties()
        .Where(x => x != null &&
        !string.IsNullOrEmpty(x.GetValue(personaFiltro, null).ToString()));

      3. Hola Andres.

        Perdón por tardar en la respuesta… si el ejemplo que construí lo hice con Framework 4.5 donde hay una sobrecarga que solo recibe el object 🙂 en el Framework 4.0 es distinto.

        Gracias por tu comentario y aclaración.

  3. Diego Nieto dijo:

    Maiii niggaaaaaa, leyendo su post alguna vez me encontré con una solución similar, y funcionó correctamente peeeeerooo… cuando traía datos en cantidades navegables, el performance se me iba al piso.

    Cómo se comporta Dynamic Linq con este tema??

    Thx

    1. May Nigggaaaa ese milagro de tenerlo por acá :PP.

      Pues que te dijera alá, lo que sucede es que estas operaciones se realizan en memoria, Supongo estas trabajando con una base de datos y alguna tecnología que te provee el acceso, digamos es EF, cuando tu haces la consulta y la materializas eso queda en memoria y en memoria realizas las operaciones… no he evaluado si la puedes trabajar antes de materializarla, pero igual, si se pudiera, prefiero pasar la consulta nativa con algún “CommandText ” del ORM, nadie sabe mas de datos que el motor de la base de datos 🙂

      Aunque igual me sigue pareciendo raro el problema en performance, debe ser una cantidad de datos la hppppppp para que te este reventando, has evaluado la posibilidad de “materializar” menos datos? incluir algo así como un paginador?

      Saludos.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s