Ejecutando consultas arbitrarias en LINQ to SQL

(An English version of this post is available here)


Una restricción relativamente incómoda cuando se está trabajando con LINQ to SQL es la necesidad de definir a priori un tipo de datos para representar los resultados cuando se desea ejecutar una consulta SQL arbitraria a través del contexto de datos. No veo que definir un tipo sea algo especialmente costoso, y además podría ayudar a la legibilidad del código, pero es cierto que podría evitarse…


Por ejemplo (utilizando un ejemplo de mi libro “C# 3.0 y LINQ”), si se desea saber cuántos clubes de cada ciudad militan en la Primera División española, habría que hacer algo como lo siguiente:


        class Tmp  // el tipo de los resultados


        {


            public string Ciudad { get; set; }


            public int CantClubes { get; set; }


        }


 


        static void ConsultaDinamica1()


        {


            using (FutbolDataContext ctx = new FutbolDataContext())


            {


                var ciudades = ctx.ExecuteQuery<Tmp>(


                   @”SELECT Ciudad, COUNT(*) AS CantClubes FROM Club


                                     GROUP BY Ciudad


                                     ORDER BY 2 DESC”);


 


                Console.WriteLine(“Clubes por ciudad”);


                foreach (Tmp c in ciudades)


                    Console.WriteLine(c.Ciudad + ” – “ + c.CantClubes);


            }


        }


 


El método ExecuteQuery() tiene otra variante no genérica, pero ésta requiere igualmente como primer parámetro (de tipo System.Type) el tipo del resultado.


A continuación presentamos una propuesta que permite evitar la definición del tipo. Para ello, extenderemos la clase System.Data.Linq.DataContext con una versión no genérica de ExecuteQuery() que produce una secuencia de arrays con los valores de las columnas de cada fila del resultado de la consulta:


namespace PlainConcepts.Linq


{


    public static class Extensions


    {


        public static IEnumerable<object[]> ExecuteQuery(


            this DataContext ctx, string query)


        {


            using (DbCommand cmd = ctx.Connection.CreateCommand())


            {


                cmd.CommandText = query;


                ctx.Connection.Open();


                using (DbDataReader rdr =


                    cmd.ExecuteReader(CommandBehavior.CloseConnection))


                {


                    while (rdr.Read())


                    {


                        object[] res = new object[rdr.FieldCount];


                        rdr.GetValues(res);


                        yield return res;


                    }


                }


            }


        }


    }


} 


 


Después de importar el espacio PlainConcepts.Linq, podremos leer el conjunto de resultados producido por la consulta de un modo similar a como se hace en el caso de un DataReader:


 


        static void ConsultaDinamica2()


        {


            using (FutbolDataContext ctx = new FutbolDataContext())


            {


                var ciudades = ctx.ExecuteQuery(


                   @”SELECT Ciudad, COUNT(*) AS CantClubes FROM Club


                     GROUP BY Ciudad


                     ORDER BY 2 DESC”);


 


                Console.WriteLine(“Clubes por ciudad”);


                foreach (var c in ciudades)


                    Console.WriteLine(c[0] + ” – “ + c[1]);


            }


        }


 


Por supuesto, aquí es responsabilidad del programador aplicar las conversiones necesarias en función de los tipos de las columnas del resultado.


En un próximo post presentaré algunas ideas que me rondan la cabeza con relación a este asunto.


 

Octavio Hernandez

Desarrollador y consultor en tecnologías .NET. Microsoft C# MVP entre 2004 y 2010.

3 comentarios en “Ejecutando consultas arbitrarias en LINQ to SQL

  1. Uno de los problemas que veo es que var no se puede definir en un interface, para trasladar datos entre capas o utilizar funciones genericas, la solución utilizar un object con la consiguiente perdida de rendimiento al tener que implementar boxing/unboxing.

  2. Hola, Juan!

    Pues sí, aquí ocurre más o menos lo mismo que con la interfaz IDbDataReader de ADO.NET…

    Llevo días dándole vueltas a una idea relacionada, a ver si la logro concretar 🙂

  3. Hola, espinete!

    Prácticamente todas las características incluidas en C# 3.0 tienen en principio la posibilidad de ser mal utilizadas. Lo importante está en conocer las “buenas prácticas” al respecto (yo intento dar algunas en mi libro) y ceñirse a ellas.

    En este caso, me parece que está bien utilizado el método extensor :-).

    Precisamente este mes en dotNetMania, nuestro redactor Marino Posadas entrevista a Don Syme, de MS Research, creador de F# e investigador destacado en el área de los lenguajes y le hace esa pregunta. La respuesta es algo como esto:

    “Al final, será una cuestión de buenas prácticas en la forma en que se utilicen. Pero es indudable que podemos encontrar escenarios donde los métodos extensores son totalmente requeridos si queremos dar con una solución sencilla”.

    Saludo – Octavio

     

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *