Nuevo blog: Pampa Notes on Software Trends

viernes, 29 de febrero de 2008

Finalmente después de preparar algunos artículos iniciales decidimos lanzar un nuevo blog, al cual decidimos telúricamente llamar: Pampa Notes on Software Trends

Contenidos

Este nuevo blog no viene a reemplazar el actual, sino que tiene como objetivo volcar puntos de vista, notas, conclusiones que irán surgiendo al incursionar en nuevas tecnologías.

De ninguna forma esto implica convertirnos en "evangelizadores" de los más recientes productos Microsoft, sin embargo tenemos la obligación de ensuciarnos un poco en las tecnologías pujantes para poder tener criterios justificados sobre su adopción en los escenarios que se nos presenten.

Consecuencia natural de esta actividad será el contenido de este nuevo blog.

Linq to SQL

Actualmente existen en Pampa Notes una serie de entradas sobre Linq to SQL, particularmente sobre como incorporar Linq to SQL en una arquitectura N capas.

Desde su lanzamiento ha habido muchas discusiones sobre modos de lograr esto. Y en estos artículos comentamos algunos puntos, como la necesidad de un control de cambios desconectado en las entidades (después de todo en su momento éste es uno de los principales features que los DataSets nos trajeron, véase RowState, HasChanges, etc.), dado que en Linq to SQL los cambios son detectados y registrados por un objeto DataContext encargado de materializar las entidades, se "engancha" a estos para poder luego aplicar todos los cambios realizados.

Sin embargo en una aplicación N capas queremos que nuestras entidades viajen a través de la red, o en Asp.Net queremos que persistan entre postbacks: Queremos entidades desconectadas de su DataContext. Si bien esto no está disponible en Linq-to-SQL "Out-of-the-box" está contemplado y soportado si se reemplazan algunas piezas, y agregan otras.

Como parte de las piezas a reemplazar para este "control de cambios desconectado", subimos a CodePlex una custom tool que nos permite auto-generar nuestro propio código para las entidades Linq, manteniendo la GUI del O/R Designer, más detalles en el sitio del proyecto: http://www.codeplex.com/ULinqGen

Un blog en inglés?

Pampa Notes estará escrito en ingles, con lo cual haremos eventualmente en este blog referencias a lo que ocurra en el otro. Para consolar nuestro espíritu nacional!, el nombre Pampa hace alusión a la llanura de Argentina donde se encuentra Buenos Aires, sede de Tercer Planeta.

Leer más...

El fin del ArrayList

martes, 26 de febrero de 2008

Desde la versión del Framework 2.0 llego el concepto de Generics, este nos da la posibilidad de definir clases genéricas, donde el cliente de la misma, es el encargado de definir que tipo desea utilizar.

Las ventajas más importantes son las siguientes

  • Nos evitamos el cast innecesario de objetos y en algunos casos el Boxing y Unboxing
  • Tenemos la posibilidad de hacer chequeo de tipos en tiempo de compilación
  • Ganamos claridad en el código

Uno de los lugares donde es utilizado, es en las colecciones de objetos, de ahí la aparición de un nuevo espacio de nombre System.Collections.Generics donde encontramos un grupo de clases muy útiles, referidas al manejo de colecciones con tipos genéricos.

Antes de Generics, la manera mas fácil de tener una colección en memoria, que nos permita

  • Recorrer sus elementos
  • Buscar un elemento por índice
  • Agregar y quitar elementos despues de haber definida la colección

es utilizar la clase ArrayList, que incluso nos deja definir una colección con elementos de tipos diferente, con el problema de no poder hacer ningún tipo de chequeo, además de los problemas de performance referidos a los Cast innecesarios.

Con la llegada de Generics, tenemos una nueva clase llamada List<T>, que viene a cumplir en gran medida con lo que la mayoría de los usuarios de ArrayList esperábamos.

La posibilidad de tener Colecciones de objetos realmente genéricas, donde al momento de utilizarlas uno pueda definir con que tipo quiere trabajar, aprovechando así el chequeo de tipos, Intellisense, sin Cast innecesarios, sin Boxing y Unboxing para tipos por valor, y hasta logrando un código mas legible.

Veamos algunos ejemplos utilizando clases cutom genéricas, para familiarizarnos con la sintaxis, y luego como utilizar estas nuevas listas.

Supongamos un ejemplo ilustrativo, donde queremos tener una propiedad Edad que pueda ser String o Int según la implementación.

Clases cutom sin utilizar Generics

La única manera de tener un tipo Genérico era definirlo como Object

public class Persona
{
private object edad;
public object Edad
{
get { return edad;}
set { edad = value;}
}
}

Caso 1, con un tipo por referencia

Persona per = new Persona();
per.Edad = "25";
// En este caso da un error de compilación
// int edad = (int)per.Edad;
// Aca funicona, pero tuvimos que hacer un CAST
string edad = (string)per.Edad;

Caso 2, con un tipo por valor

Persona per = new Persona();
// Boxing innecesario
per.Edad = 25;
// Aca funicona, pero tuvimos que hacer un CAST
int edad = (int)per.Edad;
// Esto falla por no ser un String
// string edad = (string)per.Edad;

Clases cutom con Generics

public class Persona<T>
{
private T edad;
public T Edad
{
get { return edad; }
set { edad = value; }
}
}

Caso 1, con un tipo por referencia

Persona<string> per = new Persona<string>();
per.Edad = "25";
// Esto funciona sin necesidad de CAST
string edad = per.Edad;
// Esto código no compila, evitando problemas posteriores
// int edad = per.Edad;

Caso 2, con un tipo por valor

Persona<int> per = new Persona<int>();
per.Edad = 25;
// Esto código no compila, evitando problemas posteriores
// string edad = per.Edad;
// Esto funciona sin necesidad de CAST
int edad = per.Edad;

Listas genéricas

Ahora apliquemos esto a las Listas genéricas, veamos un ejemplo muy común donde uno necesita definir una lista de un tipo de dato en particular, en este caso con un tipo entero.



int suma = 0;
ArrayList numeros = new ArrayList();
numeros.Add(1); // Boxing innecesario
numeros.Add(2); // Boxing innecesario
numeros.Add(3); // Boxing innecesario
numeros.Add(4); // Boxing innecesario
//numeros.Add("cadorna"); Esta linea es valida pero falla en tiempo de ejecucion
foreach (int num in numeros) // Unboxing innecesario
{
suma += num;
}
int valor = (int)numeros[0]; // CAST innecesario al recuperar un valor

En cambio utilizando la nueva clase List<T> este código se veria así

List<int> numeros = new List<int>(); // Se define el tipo a utilizar
numeros.Add(1);
numeros.Add(2);
numeros.Add(3);
numeros.Add(4);
numeros.Add("cadorna"); // Error en tiempo de compilación
foreach (int num in numeros) // Valida en tiempo de compilación
{
suma += num;
}
int valor = numeros[0]; // No necesita CAST
Este segundo caso como se ve es mucho más eficiente.

Algunas conclusiones finales

Como se ve a la hora de necesitar una colección fácil de manipular, de agregar y quitar ítems es mejor utilizar la clase List<T> y dejar de lado ArrayList que solo seria utilizable si queremos mezclar tipos de datos dentro de la misma colección, algo que no es muy recomendable y que seria lo mismo que definir una lista de objetos List<object>.

Leer más...

Linq: Asomándose detrás de escena

martes, 5 de febrero de 2008

Para entender un poco mejor lo que ocurre al utilizar LINQ to SQL (DLINQ), voy a mostrar con ejemplos prácticos un poco de lo que ocurre cada vez que ejecutamos un query LINQ sobre SQL

Utilizando SqlMetal o el O/R Designer, podemos crear para cada tabla existente en el servidor SQL una colección equivalente, contenida en un DataContext, que representa una vista de nuestra base de datos.

Además, estas tablas son IQueryables, lo que significan que son colecciones de elementos sobre los cuales pueden escribirse queries LINQ, Por Ejemplo:

var query = from p in VideoClubDataContext.Peliculas where p.Genero == "Terror" select p.ClienteConCopia


  






Enumerando resultados



Peliculas es un IQueryable, por lo tanto también es un IEnumerable. Pero a diferencia de una lista o array en memoria, al enumerar sobre éste, el trabajo de construir el enumerador, es delegado a un "Provider", que en el caso de LINQ to SQL, traduce la expresión C# (o VB.Net) a SQL, y enumera los resultados con un SqlCommand creado por el DataContext (quien conoce el ConnectionString necesario).



Si prestaron atención a las presentaciones de LINQ sabrán que hasta este punto, al construir mi objeto query, todavía ninguna conexión se abrió con el SQL, solo se construyó en memoria la estructura de la consulta (esto es un ExpressionTree que mencionaré más adelante).



Recién al enumerar este objeto query, se construye la sintaxis SQL, y se envía al SQL Server, trayendo los resultados para ser enumerados.




foreach (Cliente cliente in query)
EnviarMailRecordatorio(cliente.Email);


Una de las primeras dudas que tuvimos es: Qué ocurre si vuelvo a enumerar el objeto query? Existe algún tipo de caché de los resultados?



La respuesta es... no. Simplemente porque enumerar nuestro objeto query, es enumerar la consulta, con lo cual se construye nuevamente la sentencia SQL, se abre (o reutiliza) una conexión con el SQL, y se devuelven los nuevos resultados en la enumeración.



Si queremos enumerar los resultados anteriores nuevamente, podemos hacerlo de esta forma




Cliente[] clientes = query.ToArray();
foreach (Cliente cliente in clientes)
EnviarMailRecordatorio(cliente.Email);
foreach (Cliente cliente in clientes)
LlamarPidiendoDevolucionDeCopias(cliente.Nombre, cliente.Telefono);


y evitamos así viajar 2 veces al SQL.



 



El Árbol de Las Expresiones



Yendo un poco más lejos nos preguntamos que ocurre cuando los parámetros de un query se modifican luego de haberlo construido, y enumerado, dicho de otra forma, que ocurre si ejecuto el siguiente código:




txtDirector.Text = "Kubrik";

// solo por comodidad, utilizo en este query dot-notation
var query = VideoClubDataContext.Peliculas.Where(p => p.Director.StartsWith(txtDirector.Text)).Select(p => p.Titulo);

MessageBox.Show(query.ToArray()[0]);


// modifico una variable que sirve como parámetro en query
txtDirector.Text = "de la Iglesia";



// enumero nuevamente, con el método ToArray()
MessageBox.Show(query.ToArray()[0]);


 



El primer mensaje será "2001: Una Odisea del Espacio" (de Kubrik, claro).



La gran pregunta ahora es... que mostrará el segundo mensaje? Si apuestan a que el mensaje es el mismo, perdieron!



El segundo mensaje será "300 Balas" (de Alex de la Iglesia, claro)



Como se entera nuestro objeto query de que el texto de nuestro TextBox cambió? La respuesta es el Expression Tree



Un Expression Tree es un estructura en memoria que representa de forma jerárquica una expresión C# (o VB.Net), no es código compilado, de manera que permite recorrer la expresión del query, convirtiéndola a sintaxis SQL.



Expression Trees es un tema que requiere otros posts, con lo cual sin entrar en más detalles, voy a mostrar algo similar a una parte del árbol que se construye en memoria con nuestro query de más arriba:




  • LlamadaAFuncion

    • nombre = StartsWith


    • objeto = Atributo

      • nombre = "Director"


      • objeto = p (Elemento de la coleccion)




    • parametro1 = Atributo

      • nombre = "Text"


      • objeto = "txtDirector"







Es decir que el objeto query sabe que debe utilizar como parámetro el valor de la propiedad Text del objeto txtDirector, recién al enumerar el objeto query, esto se traducirá a algo como:




WHERE [t0].[Director] LIKE @p0

donde:

@p0 = 'Kubrik%'


Esto también nos permite comenzar a entender como sintaxis LINQ se traduce en código SQL, y lo que es muy importante, cuando.



Debe pensarse bien en que momento enumerarlos, y cuando utilizar ToArray(), ToList(), etc., para cachear los resultados.

Leer más...

LINQ y LINQ para SQL

viernes, 1 de febrero de 2008

Un par de meses después de la liberación pública del Microsoft .NET Framework 3.5 (y su plataforma de desarrollo preferida, Visual Studio 2008), es buen momento para presentar algunas de nuestras impresiones sobre lo que probamos para el uso de estas tecnologías en aplicaciones reales.

LINQ y sus providers

El Language INtegrated Query (consultas integradas al lenguaje) consiste en un conjunto de extensiones introducidas en el runtime de .NET y sus lenguajes principales (C# y VB) que permiten implementar una sintaxis flexible de consulta, y de paso varias cosas más.

La aplicación de LINQ a diversas fuentes de datos, tales como colecciones en memoria, bases de datos relacionales, documentos de estructura jerárquica (XML) y otras, depende de la disponibilidad de los "providers" específicos para ese tipo de origen de datos.

Por eso, al analizar las capacidades de LINQ hay que distinguir entre la tecnología (o sintaxis) LINQ genérica, y los distintos proveedores específicos (LINQ to SQL, LINQ to XML, LINQ to DataSet, etc.) que pueden encontrarse en distinto grado de evolución. Vamos a analizarlos por separado:

LINQ

Una de las ventajas que inmediatamente resaltan al empezar a usar sintaxis LINQ en las aplicaciones, es la enorme simplificación que significa usar una sola sintaxis (una sola API) para operar sobre colecciones absolutamente heterogéneas. Hace unos meses yo conocía perfectamente el método Sort -específico de la clase Array- que permite ordenarlos; una propiedad Sort -en los DataView- que con otra sintaxis logra el mismo efecto sobre las vistas de un datatable. Y recurria a la ayuda en línea cuando necesitaba ordenar un conjunto de nodos XML o cualquier otra cosa. Lo mismo para filtrar, etc.

Felizmente ya me estoy olvidando de todo eso, porque ahora sólo uso expresiones como estas, independientemente de cuál es la coleccion sobre la que opero:

var pedidos = from a in archivos
where a.Extension == ".xml"
order by a.Name
select a;

O su equivalente, no tan natural para leer pero más revelador de lo que ocurre tras la escena:

var pedidos = archivos
.Where(a => a.Extension == ".xml")
.OrderBy(a => a.Name)
.Select(a => a);"

Por supuesto, en ambos casos dejo que el Intellisense haga la mayor parte del trabajo.

Las extensiones que permiten esta sintaxis, como la declaracion anónima de tipos (new { Edad = m.edad, Nombre = ... }) y los métodos de extensión (ej. .Where(), .Select(), etc) tambien hacen que todo el manejo de colecciones propias y de clases de negocio representadas como objetos "custom" sea muchísmo mas fácil y abreviado.

Hasta acá, no hay ninguna duda. Esto es para usarlo, cuanto antes, y no hay retorno.

NOTA: El segundo ejemplo trae reminiscencias de los paradigmas de programación funcional, y eso es exactamente lo que se ha incorporado al runtime .NET, mediante las expresiones lambda (otra de las extensiones incluidas en la nueva versión).

LINQ para SQL

Acá el paisaje es muy atractivo, pero hay que estar atento al camino que empieza a volverse espinoso.

Lo primero que uno nota es que LINQ para SQL está integrado por distintos componentes, algunos de los cuales son más deseables (o están más maduros en su evolución) que otros.

  • Existe un O/R designer (diseñador Objeto / Relacional), integrado en la IDE de Visual Studio, que permite mapear visualmente las tablas, procedimientos almacenados, etc. de un SQL Server y genera un archivo .DBML (XML) con su estructura.

  • Hay una herramienta de generación de código ("custom tool") tambien integrada en la IDE, que a partir de ese "mapa" DBML genera código de clases, decoradas con atributos de LINQ, que permiten el posterior armado de consultas LINQ

  • Las clases generadas se integran con un "Data Context" y otras clases del nuevo namespace System.Data.Linq, que incluyen la lógica de acceso a datos (sin necesidad de utilizar explícitamente las clases de ADO.NET) para traer los objetos de la BD y para persistir los cambios, en una modalidad "conectada" que no siempre resulta la más apropiada.

  • Finalmente, existe un "provider" de LINQ para SQL que, en base a los atributos de las clases, convierte la consulta LINQ en una sentencia Transact-SQL para enviar al servidor

  • Para los primeros dos componentes enumerados en esta lista(que se integran en la IDE) existe una alternativa como comando de línea (SqlMetal.exe) que tiene una funcionalidad similar pero no idéntica.

En proximos artículos del blog avanzaremos más sobre las características y escenarios de uso de LINQ para SQL. Por ahora, es importante entender la distincion entre la tecnología LINQ en general -de indudable aplicación- y su actual implementación para SQL, que tiene claros y oscuros.

Leer más...