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.