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.


 

Executing arbitrary queries in LINQ to SQL


A relative nuisance when working with LINQ to SQL is the need to a priori define a data type in order to represent the results of an arbitrary query executed through the data context. I don’t see defining a type as something specially complicated, and it could improve the readability of the code, but it is true that it could be avoided …


For instance (using an example taken from my book “C# 3.0 and LINQ”), if we would like to know how many soccer clubs from each city do belong to the Spanish league First Division, we should do something like this:


        class Tmp  // the type for the results


        {


            public string City { get; set; }


            public int ClubCount { get; set; }


        }


 


        static void ExecQuery1 ()


        {


            using (FutbolDataContext ctx = new FutbolDataContext())


            {


                var results = ctx.ExecuteQuery<Tmp>(


                   @”SELECT City, COUNT(*) AS ClubCount FROM Club


                                     GROUP BY City


                                     ORDER BY 2 DESC”);


 


                Console.WriteLine(“Clubs by city”);


                foreach (Tmp c in results)


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


            }


        }


 


The ExecuteQuery() method has another, non-generic variant, but that one also requires the type for the result as its first parameter (of type System.Type).


Here we present a proposal that will allow you to avoid the definition of the type. With that goal in mind, we will extend the System.Data.Linq.DataContext class by means of an extension method that implements a non-generic version of ExecuteQuery(), that will produce (on demand) a sequence of arrays with the values of each column for each row of the query results:


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;


                    }


                }


            }


        }


    }


}


 


After importing the namespace PlainConcepts.Linq, we will be able to read the result set produced by the query in a fashion similar to that we use when reading from a DataReader:


 


        static void ExecQuery2()


        {


            using (FutbolDataContext ctx = new FutbolDataContext())


            {


                var results = ctx.ExecuteQuery(


                   @”SELECT City, COUNT(*) AS ClubCount FROM Club


                     GROUP BY City


                     ORDER BY 2 DESC”);


 


                Console.WriteLine(“Clubs by city “);


                foreach (var c in results)


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


            }


        }


 


Of course, it is the programmer’s responsibility to apply the necessary casts depending on the types of columns of the result set.


In a future post I will expose some ideas I’ve been nurturing lately in relation to this subject.


 

Nothing runs like a fox (in the prairie)

No podía terminar esta serie dedicada a los diferentes “grupos de presión” que encuentro normalmente durante las charlas sobre LINQ sin mencionar a uno al que me une una relación muy especial: el grupo de programadores provenientes de FoxPro y que, al menos parcialmente, han comenzado a moverse hacia .NET Framework a raíz del anuncio de Microsoft de que no habrá más versiones del producto (sin bien la versión actual estará soportada hasta 2015).


La relación que me une a FoxPro es especial por varias razones:


a)      Fue el primer sistema con el que empecé a desarrollar cuando llegué a La Habana después de terminar la carrera; si bien aquello no duró mucho, porque al cabo de muy poco tiempo pasé a trabajar con Turbo Pascal y Turbo C/C++, ya más “en mi línea de preferencias” y de los que luego no me desprendería en muchos años.  De aquella época inicial, recuerdo que me inclinaba más por Clipper que por FoxPro, fundamentalmente por dos causas: 1. FoxPro era un intérprete, mientras que Clipper era un compilador a código nativo; y 2. Uno de los creadores de Clipper era Dennis (Denny) Dias, también guitarra solista de un excelente grupo de pop-rock de la época (y que aún sigue “dando guerra”), Steely Dan. No es que fuera mi ídolo ni mucho menos; los ídolos se forjan fundamentalmente en la adolescencia, y yo ya tenía mujer e hijo por aquel entonces. Pero esa combinación de ingeniero de software + guitarrista de rock, con notable éxito en ambas facetas, me inspiró siempre toda la admiración del mundo.


b)      Muchos buenos amigos, con los que tengo una excelente relación y a los que considero excelentes profesionales, utilizaron y siguen utilizando con notable éxito FoxPro.


Dicho lo anterior, debo ahora comentar sobre algunos “tópicos” que escucho frecuentemente con relación a FoxPro:


a)      FoxPro es un sistema de programación de propósito general. En mi modesta opinión, esto no es así. Para mí, FoxPro es una herramienta de desarrollo de propósito más bien específico, orientada al desarrollo de aplicaciones de gestión de bases de datos locales, de bases de datos cliente/servidor (dado el soporte para SQL incorporado al producto posteriormente a su surgimiento) o incluso de múltiples capas (dado el soporte también incorporado para la creación y consumo de servicios Web – ver la serie de posts de Ana María Bisbé sobre este tema y la interoperabilidad entre FoxPro y .NET en general); una clasificación en la que podría caer una buena parte de las aplicaciones de escritorio que se desarrollan hoy día. Según mi modo de ver las cosas, ésta es la causa fundamental por la que Microsoft ha decidido no continuar dedicando esfuerzos al desarrollo del producto – el gigante quiere centrar estos esfuerzos en el desarrollo de sistemas y lenguajes de propósito general.


b)      “Esto de LINQ ya lo tenemos en FoxPro desde hace mil años”. Lamentablemente, esto tampoco es así, según mi opinión. Quienes dicen esto pecan de tener una visión estrecha de LINQ, asociada únicamente a la ejecución de consultas contra bases de datos relacionales. Aquí remito al lector a un post mío anterior relacionado con este tema.


c)       “Esta aplicación con FoxPro yo la desarrollaría en un tiempo X,  y con .NET incluso a un experto le costaría 3 * XV. Podría ser muy cierto, en el caso de que la aplicación a desarrollar fuera del tipo de las aplicaciones para las que fue pensado FoxPro, y especialmente en el caso de un desarrollador con amplia experiencia en el desarrollo de aplicaciones similares utilizando FoxPro. A diferencia de FoxPro, C#, VB y las librerías de .NET Framework (incluyendo las de acceso a datos), así como las herramientas integradas en Visual Studio, sí son recursos de propósito general, pensados para ser utilizados en un contexto mucho más amplio, y no optimizados específicamente para el desarrollo de ningún tipo concreto de aplicaciones.


Es en esta última situación donde se abren diversas alternativas para un desarrollador con tales habilidades. ¿Cuál tomar si se nos presenta un proyecto con esas características? En mi humilde opinión, no es necesario ni recomendable dejar a un lado los conocimientos y habilidades ya adquiridos para mudarse a la plataforma predominante simplemente por el hecho de que “FoxPro ha perdido el apoyo de Microsoft”. Si la aplicación es una aplicación ideal para FoxPro, y no se prevé la necesidad a corto y mediano plazo de hacer un uso extensivo de las facilidades más novedosas que ofrece .NET (WPF, Workflow Foundation, por mencionar dos), probablemente la mejor opción será desarrollar la aplicación con Visual FoxPro, con la consecuente reducción de plazos de entrega y necesidad de aprendizaje “a marchas forzosas”. Después de todo, tampoco es como para “cortarse las venas”: el producto va a estar soportado hasta 2015, probablemente lo seguirá estando algún tiempo más y tiene una amplia base de seguidores que estoy seguro continuarán utilizándolo. Pienso además que bastante antes de esa fecha estarán disponibles herramientas de calidad que facilitarán la migración completa de aplicaciones FoxPro a .NET, e incluso hasta implementaciones completas de compiladores de FoxPro en código manejado, caso de que en el futuro interese acogerse al entorno de ejecución común, y también librerías que permitan acceder de una manera cómoda desde FoxPro a todas las funcionalidades de código manejado, en caso de que simplemente se desee “modernizar” la interfaz de usuario o hacer uso de ciertas facilidades de .NET desde FoxPro. Visite, por ejemplo, http://www.etecnologia.net o http://guineu.foxpert.com (mi agradecimiento a Pablo Roca por los enlaces).

En resumen, que si va a seguir “viviendo en la pradera”, mi consejo es que continúe, al menos de momento, con FoxPro. Nadie ha puesto en duda que, en la pradera, nadie corre como un zorro.

Nota: Como siempre, este post refleja única y exclusivamente mis puntos de vista personales, y no los de la empresa en la que trabajo ni de otras con las que colaboro.