Drag and Drop en WinForms

viernes, 5 de septiembre de 2008

Hace unos días durante un curso de capacitación un cliente nos comentó que necesitaban implementar una grilla cuyas filas fueran reordenables mediante "drag and drop" (es decir arrastrando y soltando) y vergonzosamente tuvimos que reconocer que apenas teníamos una (muy vaga) idea teórica de como implementarlo.

A la semana siguiente con la intención de dar un respuesta un poco más sólida, dediqué unos minutos a leer un poco y dado que resultaba fácil, armé una pequeña demo para probar el uso de Drag and Drop en WinForms.

Demo

El código puede descargarse de aquí.

En el ejemplo hay un simple DataGridView al cual se puede arrastrar un archivo Xml, cuyo formato sea "convertible" a DataSet, con lo cual la tabla (la primera si hay más de una) se muestra en la grilla (en el ejemplo incluí un Items.xml que sirve a tal fin).

Una vez que tengo filas en la grilla, puedo arrastrar filas con el mouse para modificando el orden.

Esto muestra como realizar Drag n Drop, desde una aplicación externa (Explorador de Windows por ejemplo) hacia un control WinForms propio, y como hacerlo desde y hacia la misma aplicación.

Paso a Paso
  • Lo primero que necesitamos es iniciar el "arrastre", y una forma posible es en el evento MouseMove del control de origen, para esto agregue en el código de este evento una llamada al método "DoDragDrop" (cuando el botón izquierdo está presionado, etc.), al cual se le debe pasar como argumentos el objeto a arrastrar (en mi caso el DataGridViewRow que se esta arrastrando), que simplemente sirve como información para el control de destino, y la acción deseada, por ej. "Move".
  • Con esto ya podemos tomar una fila y llevarla por toda la pantalla, sin embargo todavía nadie acepta nuestro "DataGridViewRow", con la cual el cursor muestra el conocido símbolo de prohibido.
  • Lo que necesitamos ahora es que nuestra grilla diga "Vienes con un DataGridViewRow? Puedes soltarlo aquí!", lo que hace que el cursor se transforme en uno más amigable indicando la acción permitida.
  • Para esto hay que agregar código en el evento "DragOver", en este una vez que me aseguro que el objeto arrastrado es una DataGridViewRow, perteneciente a mi misma grilla, informo mediante un argumento del evento "DragOver" que la acción permitida es (como fue solicitado) "Move".
  • Finalmente tengo que hacerlo cuando el objeto es soltado, esto ocurre en el evento "DragDrop", en el cual verifico nuevamente que estoy recibiendo una DataGridViewRow de mi propia grilla, y realizo finalmente el movimiento de filas (lo cual dependerá de la fuente de datos de la grilla (DataTable, DataView, List, etc.).

De forma similar en DragOver y DragDrop espero un objeto con formato "FileName" (el cual nos envía el Windows Explorer) para aceptar archivos .xml sobre la grilla. Lo cual muestra que el origen y destino pueden estar en aplicaciones separadas, y tecnologías diferentes, ya que Drag and Drop es una funcionalidad propia del Sistema Operativo.

Esto por supuesto no abarca todas las posibilidades como por ejemplo mostrar un icono personalizado durante el arrastre, arrastrar información en diferentes formatos, etc. Pero es un buen ejemplito introductorio.

Leer más...

Operadores de conversión (Implícitos y Explícitos)

miércoles, 4 de junio de 2008

Es muy habitual realizar conversiones entre tipos de datos diferentes, por defecto muchos tipos de datos permiten la conversión implícita, esto se da en los casos en los cuales el tipo de dato de origen puede expresarse en su totalidad, como el Tipo de dato de destino, acá podemos ver un ejemplo:
byte valByte = 123;
int valInt = valByte;
double valDouble = valInt;
Como se ve en este caso no es necesario hacer ningún CAST, en cambio para el siguiente caso:
int valInt = 0;
byte valByte = valInt;
Tendriamos un error de compilación “Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?)”, que nos indica que podemos hacer una conversión explícita, quedando resuelto de esta forma:
int valInt = 0;
byte valByte = (byte)valInt;
"Claro está que de esta manera, el Desarrollador está asumiendo los riesgos de obtener en la variable destino “valByte” información no esperada, si el valor origen “valInt” es superior al soportado por el tipo de dato Byte."

Conversión para clases definidas por el usuario

Desde la versión del .NET 2.0 podemos definir operadores de conversión para los tipos definidos por el usuario, como se puede ver en el siguiente ejemplo:
public class MyClase
{
public double valor;

/// <summary>
/// Permite la conversion implicita
/// desde Double hacia MyClase
/// </summary>
/// <param name="arg">Desde
/// <returns>Hacia</returns>
public static implicit operator MyClase(double arg)
{
MyClase res = new MyClase();
res.valor = arg;
return res;
}

/// <summary>
/// Permite la conversion implicita
/// desde MyClase hacia Double
/// </summary>
/// <param name="arg">Desde
/// <returns>Hacia</returns>
public static implicit operator double(MyClase arg)
{
return arg.valor;
}
}

public class MyClase2
{
public double valor;

/// <summary>
/// Permite la conversion explicita
/// desde Double hacia MyClase
/// </summary>
/// <param name="arg">Desde
/// <returns>Hacia</returns>
public static explicit operator MyClase2(double arg)
{
MyClase2 res = new MyClase2();
res.valor = arg;
return res;
}

/// <summary>
/// Permite la conversion explicita
/// desde MyClase hacia Double
/// </summary>
/// <param name="arg">Desde
/// <returns>Hacia</returns>
public static explicit operator double(MyClase2 arg)
{
return arg.valor;
}
}


En este caso la clase MyClase permite la conversión implícita con double, mientras que en la clase MyClase2 la conversión es explícita, la utilización de estas clases podría ser la siguiente:

double valDouble = 22;

MyClase myClase = new MyClase();
// Conversión implicita en ambos casos
valDouble = myClase;
myClase = valDouble;


MyClase2 myClase2 = new MyClase2();
// En este caso se requiere del CAST
valDouble = (double)myClase2;
myClase2 = (MyClase2)valDouble;

En resumen

Vimos un modo sencillo de implementar mecanismos de conversión entre tipos definidos por nosotros, generalmente entre tipos numéricos.
Otro modo de hacerlo es implementar System.IConvertible para utilizar la conversión a través de System.Convert, lo cual nos permite especificar la conversión por cultura.
Leer más...

Conferencia online - IIS 7 para desarrolladores

lunes, 19 de mayo de 2008

En esta presentación online de MSDN (20/mayo/08) que realizamos con Carlos Walzer, destacamos algunos de los aspectos de la nueva plataforma de hosting en Windows Server 2008 y Windows Vista, enfocados desde la óptica de los desarrolladores.

Puede descargar aquí la presentación (.PPTX) y el ejemplo de código DemoWebsite con la extensión de consola para el administrador de IIS7. Los ejemplos están desarrollados con Visual Studio 2008.

NOTA: al abrir en Visual Studio la solucion de la consola administrativa, puede pedir una contraseña para el archivo de claves encriptadas. La misma es 123456

Leer más...

Linq to SQL y Stored Procedures

miércoles, 14 de mayo de 2008

Continuando con el artículo anterior sobre Linq to SQL en la Dal, donde explica el rol que siguen cumpliendo los Stored Procedures. Razón por la cual esta entrada se llama "Linq to SQL y Stored Procedures" y no "Linq to SQL vs. Stored Procedures").

Las razones más importantes son:

  • Las consultas Linq to SQL se traducen en queries SQL, no son procedimientos: con Linq to SQL no podemos aprovechar tablas temporales, estructuras de control (if-else, loops, cursores), devolver múltiples resultsets, operaciones por lotes (UPDATE, DELETE), etc.
  • Performance, desde hace varias generaciones SQL Server sabe como optimizar código Transact-SQL en Stored Procedures. Más aún, la traducción de Linq a SQL, aunque evolucione con el .Net Framework, no siempre genera los mejores resultados, existirán los casos en que necesitemos escribir nuestro propio Transact-SQL.
    Como ejemplo de esto el .Net Framework 3.5 SP 1 Beta reducirá la cantidad de tablas usadas en ciertos joins.
  • Seguridad, son limitados los escenarios en que nuestra aplicación tiene acceso total a las tablas de la base de datos. Es habitual que nuestro DBA, por razones de seguridad y desacoplamiento entre modelos de datos, limite el acceso al SQL Server a la ejecución de Stored Procedures.

Llegamos a la conclusión de que, en general, no podemos prescindir de los SP, la buena noticia es:

Linq to SQL incorpora un manejo simple de SP que permite realizar cualquier tipo de operaciones sobre los datos vía SP.

Pero la mala noticia es:

El O/R Designer con el cual editamos gráficamente nuestros Dbml, no soporta completamente el uso de SP.

Mientras esperamos que la mala noticia se solucione con actualizaciones del VS2008, en algunos puntos tendremos que tocar algo de código manualmente.

Generación de wrappers

Primero vamos a incorporar los SP que queremos invocar a nuestro dbml, para esto basta con arrastrarlos a la zona de métodos (a la derecha en el O/R Designer), esto generará en nuestro DataContext un método para cada SP, con el mismo nombre y lista de parámetros.

Ahora podemos invocar estos SP con intellisense, comprobación de tipos, y control sobre cada invocación (detección temprana de errores, mayor mantenibilidad y desacoplamiento).

Esto genera también para cada SP una clase que representa el tipo de resultado detectado, una clase con propiedades públicas análogas a las columnas del resultado devuelto, esto le permite a Linq to SQL devolver de la invocación de estos métodos wrappers una colección de estos objetos.

El nombre que tienen estas clases por defecto será "NombreDelStoredProcedureResult", y esto es invisible desde el O/R Designer, por lo tanto si queremos cambiar el nombre de la clase de resultados (por ej: ListarClientesPorZonaResult a Cliente), será necesario meterse en el Designer.cs asociado al dbml, cortar esta clase, pegarla en otro archivo (por ejemplo junto con el resto del modelo de datos), y aquí cambiarle el nombre (el código autogenerado se pisa cada vez que tocamos el dbml).

Ahora resta actualizar el wrapper del SP con el nombre del tipo devuelto, desafortunadamente el O/R Designer solo nos permite especificar como tipo devuelto una entidad que se encuentre visible en el mapa dbml, lo cual no siempre es posible si queremos compartir estas entidades entre otros dbml, o mover las entidades a un assembly separado. Inclusive tampoco es posible con el O/R Designer especificar múltiples resultados (típicamente utilizado para devolver entidades cabecera-detalle).

Si podemos hacer esto "tocando" los wrappers auto-generados, para lo cual tenemos 2 opciones:

  • Una vez generados, mover los wrappers a una partial class del DataContext, y modificando manualmente los atributos que decoran los wrappers, especificar tipos devueltos, inclusive múltiples resultsets. Ejemplo:
        [Function(Name = "dbo.LeerClienteConPedidos")] 
[ResultType(typeof(Cliente))]
[ResultType(typeof(Pedidos))]
public IMultipleResults LeerClienteConPedidos([Parameter(Name = "IdCliente", DbType = "Int")] System.Nullable<int> idCliente)
{
IExecuteResult result = this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), idCliente);
return (IMultipleResults)result.ReturnValue;
}

esto indica que el SP devuelve 2 resultsets, el primero con Clientes, y el segundo con Pedidos.


  • Reemplazar el generador de código por defecto MSLinqToSQLGenerator, por uno que genere los wrappers "a gusto", lo cual afortunadamente resulta más simple de lo que parece. Nuestro generador de código ULinqGen podría extenderse para realizar esto.

Aplicar cambios


Una vez obtenidas colecciones de entidades existentes en la base de datos, por defecto, Linq to SQL activa para todas ellas un control de cambios (cuando estos objetos notifican sus modificaciones mediante eventos). Cuando se detectan modificaciones, se mantiene en el DataContext un "change set", que permite luego aplicar todos esos cambios en la base de datos.


Cuando se llama al método SubmitChanges() del DataContext, Linq to SQL genera y ejecuta una serie de comandos UPDATE, INSERT y DELETE necesarios para reflejar los cambios en la base de datos.


Sin embargo es común que necesitemos realizar algún tipo de verificación o auditoría al momento de realizar estas operaciones. Es por esto que desde el O/R Designer es posible configurar el "Behavior" de una entidad, defiendo el método a invocar para realizar un UPDATE, un DELETE o un INSERT de esta entidad, en lugar del SQL auto-generado.


Carga de entidades asociadas


Por último, existe un uso de estos wrappers de SP un poco más desconocido, revelado por Dinesh Kulkarni, en su blog.

Cuando existen foreign keys entre 2 tablas del SQL, en el dbml se generan asociaciones, que se ven como colecciones de detalle (EntitySet) dentro las entidades maestro. Ej:



Cliente c = ...
for each (Pedido p in c.Pedidos)
{
...
}

La forma en que se obtienen los Pedidos para un Cliente, es por defecto mediante un query Linq generado a partir de la foreign key, sin embargo es posible definir esto mediante un método propio, definido en una partial class del DataContext.

Ejemplo:


public partial class VentasDataContext {

public IEnumerable<Pedido> LoadPedidos(Cliente c) {
return this.LeerPedidosPorCliente(c.IdCliente);
}

public Cliente LoadCliente(Pedido p) {
return this.LeerCliente(p.IdCliente);
}
}

El método LoadPedidos es llamado al cargar la propiedad Pedidos en un Cliente, y el segundo al cargar la propiedad Cliente en un pedido.

Esto funciona tanto para carga deferida (deferred loading) como para carga inmediata (utilizando el método LoadWith<T>()).


Conclusiones


Teniendo estos features podemos decir que el uso Linq to SQL para invocar Stored Procedures, nos atrae por estas ventajas:


  • Integración con Linq to SQL (a través de compartir un mismo modelo de datos).
  • Wrappers para invocación de SP, con las ventajas de tener comprobación de tipos en los parámetros, intellisense, y control sobre las invocaciones.
  • Materialización de objetos (POCO) para los resultados (en lugar de sólo DataSets).

y preocupan estas desventajas:

  • Una invocación a Stored Procedures un poco oscura, que no permite demasiada personalización. Por ejemplo no es posible interceptar errores, tocar la lista de parámetros, o forzar reintentos, etc. de forma global.
  • Solo funciona (en la versión actual) con MS SQL Server.

En el artículo anterior, José explica con más detalle razones que justifican mantener un mecanismo separado para las invocaciones a procedimientos almacenados.

Por lo tanto, exploramos la posibilidad de tener las ventajas mencionadas, sin las desventajas mencionadas.


Para esto trabajamos en el desarrollo de estas herramientas:

  • Un generador de wrappers, esto nos permitiría solucionar el problema de los tipos de retorno custom y/o múltiples, y la posibilidad elegir el mecanismo de invocación.
  • Un materializador de objetos que proyecte colecciones de objetos POCO de cualquier DataReader, permitiéndonos invocar SP con ADO.Net puro, EnterpriseLibraries, etc. En nuestro blog en inglés escribí un articulo con algunos prototipos de materialización de objetos

Hasta pronto,

Leer más...

LINQ para SQL, ¿reemplaza por completo a la DAL?

lunes, 3 de marzo de 2008

O dicho de otra forma: Si usamos LINQ, ¿podemos olvidarnos de nuestros componentes propios o genéricos de acceso a datos? (por ej. Data Access Block de Enterprise Library). Para dar una respuesta simple: NO, de ninguna manera.

Al menos si consideramos cualquier aplicacion basada en SQL Server que no sea trivial (en cuanto a su modelo de datos o la concurrencia de usuarios), no hay mucha duda de que los procedimientos almacenados siguen teniendo un rol clave en el acceso a los datos.

Su funcionalidad ahora puede ser complementada con consultas o actualizaciones directas via LINQ, en una mezcla cuya proporcion dependerá del contexto de la aplicación: desde "sólo procedimientos almacenados" en algunos entornos corporativos, hasta escenarios con una buena proporción de consultas LINQ en otros entornos. En cualquier caso, las consultas complejas y actualizaciones críticas requerirán ser implementadas como procedimientos almacenados.

Una vez establecido esto, volvemos a la pregunta inicial: ¿es necesario "duplicar" el código de acceso a datos, teniendo en cuenta que LINQ para SQL ya implementa la conexión con la base de datos y tiene un mecanismo para la invocación de procedimientos almacenados?

Se me ocurren varias razones que justifican mantener un mecanismo separado para las invocaciones a procedimientos almacenados:

  • Posibilidad de control: típicamente hay una serie de tareas y controles que se aplican antes y despues de las invocaciones, en el código genérico de invocacion de SP: revisar y completar parámetros (ej. Id del usuario autenticado), manejar excepciones, ajustar timeout de la conexion, etc.
  • Capacidad de retornar distintos tipos de resultado: ej. valores escalares, Data Readers, XML Readers
  • Posibilidad de retornar datasets en forma sencilla. Si, por supuesto. ¿o acaso la aparición de LINQ significa la extinción de los datasets? En muchas situaciones, un dataset sigue siendo una forma muy simple y eficiente de trasladar datos tabulares entre capas de la aplicación.
  • Capacidad de retornar tambien colecciones de objetos (o instancias simples) como resultado de la invocación a un procedimiento almacenado. Esto puede realizarse mediante código propio (examinando atributos de las entidades o utilizando Reflection sobre los miembros) o utilizando el método Translate del mismo DataContext de LINQ to SQL. Esto último requiere atributos LINQ en la definicion de las clases, pero estos atributos son creados automáticamente por nuestro generador de código asociado a un modelo DBML que se edita con el mismo diseñador O/R de Visual Studio 2008.

Como criterio de aplicación, nosotros en general utilizamos la siguiente regla:

  • Todas las invocaciones a procedimientos almacenados las realizamos a traves de la DAL (Data Access Layer) propia, aún las que retornan colecciones o instancias de entidades.
  • Para las consultas y actualizaciones de LINQ sobre SQL utilizamos el mecanismo de acceso a datos incluido en el Data Context.

Esta combinación le da, a nuestro "framework" de construcción de aplicaciones, la máxima flexibilidad para adaptarse a distintos escenarios y aún a distintos casos de uso dentro de la misma empresa (ej. un sistema de reserva de salas no requiere la misma complejidad de solución que un sistema de gestión de inventarios).

Leer más...

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...