lunes, diciembre 31, 2007

Ornitorrinco

... and the mome raths outgrabe.No todo lo que camina como un pato, nada como un pato y pone huevos como un pato, tiene necesariamente que ser un pato. Podría tratarse de un ornitorrinco, ¿verdad?
Suponga que yo le digo que esto es Freya:
IStack = interface[X]
Count: Integer;
Top: X;
method Push(Value: X);
method Pop;
requires Count > 0;
    IsEmpty: Boolean => Count = 0;
end;
Para que le resulte más fácil detectar el elemento extraño, utilizaré una sintaxis equivalente, algo más verbosa:
IStack = interface[X]
property Count: Integer;
property Top: X;
method Push(Value: X);
method Pop;
requires Count > 0;
    property IsEmpty: Boolean;
begin
Result := Count = 0;
end;
end;
Y ahora, antes de subir la otra mitad del artículo, dígame si lo anterior le parece bien o mal...

Efectivamente, como mencionaba Marto en un comentario, se supone que las interfaces no "implementan". Sin embargo, es precisamente ese efecto lo que logran los métodos de extensión de C# 3.0:
interface IStack<X>
{
int Count { get; }
X Top { get; }
void Push(X value);
void Pop();
}
static class StackExt
{
public static bool IsEmpty<X>(this IStack<X> s)
{
return s.Count == 0;
}
}
Gracias a estos métodos, es posible escribir código como el siguiente, suponiendo que tenemos ya una clase que implementa el tipo de interfaz:
IStack<int> st = new StackImpl<int>();
if (st.IsEmpty()) { ... }
¿Es bueno, o es malo? No veo nada malo en permitir manipulaciones predefinidas sobre el estado "público" ofrecido por el contrato de la interfaz: lo mismo se puede lograr, incluso sin métodos de extensión, aunque al precio de escribir muchísimo más.
Ahora bien, mi duda va más allá. ¿Merece la pena implementar métodos de extensión (Freya ya los tiene), o sería suficiente con añadir al lenguaje implementaciones predefinidas en interfaces, como la del ejemplo de Freya? El caso es que los métodos de extensión son nada elegantes:
  • El mecanismo de selección es horrible: basta con introducir una cláusula using para activarlos, dándole un protagonismo inapropiado a estas cláusulas.
  • Estos métodos, cuando se emplean para extender una clase, plantean un grave problema de estabilidad respecto al versionado: si el implementador de la clase introduce un método de instancia en la clase extendida, dejará de funcionar la extensión, porque el método de instancia tiene la prioridad. Lo peor es que sería muy difícil detectar estos problemas.
De momento, a falta de más análisis, me inclino por sustituir este recurso por implementaciones en interfaces. Es verdad que no son recursos equivalentes, pero la suciedad de los métodos de extensión me aterra.

Etiquetas: , , ,

domingo, diciembre 30, 2007

Vade retro, LINQ pro SQL

Hágame caso, y evitará malgastar sus fuerzas: si quiere desarrollar una aplicación seria, apártese de LINQ para SQL. Al menos, de momento...
Llevo varios días descubriendo cosas que no me gustan, pero me he contenido por prudencia. El descubrimiento que ha puesto el último clavo sobre el ataúd del invento tiene que ver con la forma en que se manejan las relaciones maestro/detalles. Pongamos por caso que tenemos dos tablas relacionadas, como Clientes y Pedidos:
LINQ para SQL crea dos clases, con un formato predecible:
public partial class Customer
{
public EntitySet<Order> Orders { ... }
}

public partial class Order
{
public Customer Customer { ... }
}
¿En qué momento se leen los pedidos de un cliente? Por omisión, no cuando se crea el cliente; ni siquiera cuando se accede a la propiedad Orders. Hay que intentar hacer algo con el contenido de la colección para que se dispare la consulta SQL correspondiente, que se parecerá a la siguiente:
select * from orders
where CustomerID = @CustomerID
Hasta aquí, todo es esperable, predecible y aceptable... suponiendo que estamos creando una aplicación con la vetusta arquitectura cliente/servidor. Si vamos a tener cuatro gatos conectados a un servidor local de SQL Server (pues LINQ para SQL sólo soporta, de momento, SQL Server), las cosas nos irán bien. Pero para eso no hacía falta gastar dinero en una subscripción anual a la MSDN. Cómprese un Delphi 7 de segunda o tercera mano, o incluso un Visual Basic "clásico", y sea feliz.
Pero, ¿qué pasa con la programación seria? Cuando usted se trae los datos de un grupo de clientes a la capa de presentación, tiene dos opciones, teóricamente: la carga incremental, o por demanda, de sus pedidos, o traer todos los pedidos en la misma operación que trae el grupo de clientes. En mi libro particular de estilo, la carga "por demanda" no sería aceptable. Cuando alguien pide clientes y sus pedidos, es porque realmente necesita ambas entidades, y en programación multicapas, el pecado mortal de necesidad se llama round-trip; es decir, viaje de ida y vuelta al servidor de capa intermedia. Pero mantengamos las opciones abiertas, y supongamos que cada técnica tiene su uso. Pues bien:
  1. LINQ para SQL no permite implementar la carga por demanda cuando hay una capa intermedia. Para transmitir un registro de cliente, hay que "serializarlo", y eso implica serializar sus pedidos. Adiós lectura incremental de los pedidos.
  2. Y ahora viene lo peor: supongamos que ya estamos resignados a no usar la carga por demanda. El caso es que LINQ para SQL implementa la lectura, en esos casos, de manera asquerosamente ineficiente.
Aquí aparecerá el listo de siempre, que alegará la existencia de unas DataLoadOptions que permitirían desactivar la carga por demanda. En efecto, LINQ para SQL ofrece algo así:
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Customer>(c => c.Orders);
dataContext.LoadOptions = dlo;
Pero, si se molestase en comprobar qué es lo que realmente hace dicha opción, comprobaría que, si se leen cuatrocientos clientes, esta opción provocaría que el servidor de capa intermedia lanzase otras cuatrocientos consultas SQL para leer individualmente los pedidos de cada cliente. Sí, es verdad que esta avalancha se produciría de golpe, en vez de incrementalmente...
Las cosas son buenas o malas en comparación con otras. ¿Cómo se resuelve este problema en ADO.NET con los conjuntos de datos de toda la vida? Muy simple: usted sólo necesita lanzar dos consultas, y es ADO.NET quien se encarga de tejer la relación en memoria, una vez que dispone de todos los registros. ¿Quiere, por ejemplo, los clientes de Madrid y sus pedidos? En un mismo comando (¡para ahorrar aún más!) se incluirían dos consultas:
select * from Customers
where City = "Madrid";
select * from Orders
where CustomerID in (
select CustomerID from Customers
where City = "Madrid")
A SQL Server se le da muy bien responder a este tipo de consultas, a pesar de su aparente complejidad. Y no le digo nada sobre recuperar los clientes que hicieron pedidos la semana anterior: la ganancia comparativa sería escandalosa.
¿Conclusión? Pues que LINQ para SQL no está preparado para la vida real. Es un juguetito con ínfulas, que a la hora de la verdad se queda muy corto... y es lamentable que la comunidad de programadores no esté diciéndolo en voz alta. No se trata sólo del problema aquí explicado: hay que sumar los problemas que ya la propia Microsoft explica para el funcionamiento del sistema en tres capas. Por ejemplo: los valores originales de cada registro se almacenan en el contexto, y al serializar un registro se pierden, no existe una funcionalidad comparable al Merge de los conjuntos de datos...
Es cierto que Microsoft se reserva eso del ADO.NET Entity Framework para febrero o marzo (es significativo que hayan publicado Visual Studio 2008 sin esperar por él). Habrá que esperar al producto final antes pronunciarse. Pero cada vez creo menos en la bondad de los ORMs: tengo razones para esperar muy poco de este tipo de sistemas. De todos modos, seré prudente: ya veremos qué tal se comporta el nuevo invento. A lo mejor esta es la vez en que un ORM acierta por primera vez en la Historia...

... y que ahora alguien me diga cuál es la urgencia para aprender a usar LINQ. ¿Para aplicarlo al famoso LINQ para SQL?
Mucho cuidado: no estoy diciendo que no merezca pasarse a VS2008. Todo lo contrario, ¡pásese urgentemente a la nueva versión! Hay suficientes mejoras como para merecer la pena, y la principal de ellas, en mi humilde opinión, es la nueva técnica de generación de clases para conjuntos de datos, que facilita enormemente la creación de aplicaciones en tres capas.

Etiquetas: , , , ,

jueves, diciembre 27, 2007

Novedades en Visual Studio 2008

Era un poco extenso para colgarlo de la bitácora:
Se trata, más bien, de los cambios que simplifican las aplicaciones del curso de ADO.NET. Son pocos, pero interesantes.

Por cierto, ¡qué "divertido" es programar con Java! Acabo de darme cuenta de que Java no tiene mecanismos para la solución de conflictos de nombre al implementar interfaces. En C# se utilizan las implementaciones explícitas para este fin. Y en Delphi existía un mecanismo parecido.
En particular, me gusta usar implementaciones explícita cuando quiero evitar que alguien (casi siempre, yo mismo), accidentalmente, acceda a un método o una propiedad a través de la clase, en vez de usar el tipo de interfaz. La experiencia me dice que introduce menos dependencias el uso de interfaces que el uso de referencias de clase.
Naturalmente, he añadido el "detalle" a uno de los artículos en mi página que comparan Java con C#.

Etiquetas:

domingo, diciembre 23, 2007

Disponible: Volumen II, para Visual Studio 2005

Pensé que iba a tardar más tiempo, pero la migración ha sido sencilla. Ya está disponible el Volumen II (antes, serie D) de nuestro curso a distancia sobre ADO.NET, para Visual Studio 2005:
Como siempre, las actualizaciones son gratuitas, a través de descargas personalizadas. Si ha usted adquirido el Volumen I en estos días, también le enviaremos el CD "físico" con la actualización, gratis.
Con esta actualización, el curso completo está disponible para Visual Studio 2005. Seguimos incluyendo en el CD, no obstante, las versiones de las series A y D para VS2003. Son 444 páginas (566, en realidad, si se cuentan las de Intuitive C#, incluido en el CD), en formato A4, de texto y código sobre desarrollo para .NET. ¿Quién da más?

¿Y Visual Studio 2008?

Como ya anuncié, está en preparación una serie sobre LINQ for SQL. Luego se migrarán las series ya disponibles hacia la nueva versión.
Debo advertirle, sin embargo, que los dos volúmenes ya disponibles son completamente compatibles con Visual Studio 2008. De hecho, han sido muy pocos los cambios en la migración de VS2003 a VS2005: la rejilla, el sistema de menúes, y sustituir ciertos usos de DataView y BindingContext por el componente BindingSource, y las diferencias entre VS2005 y VS2008, fuera de LINQ, son muy pocas.
De todos modos, cualquier compra del curso ahora se considera, a efectos de las actualizaciones, como si se hubiese comprado ya el curso para VS2008, de manera que le queda la actualización gratuita a la versión que venga tras .NET 3.5.

Combinaciones

Aunque no he tenido tiempo aún para escribir las reseñas, estamos también comercializando tres nuevos libros sobre .NET:
Además de estar disponibles por separado en nuestro catálogo de productos, es posible también adquirirlos en combinación con cursos y libros nuestros... y beneficiarse de un suculento descuento (en nuestros cursos: la ley española prohibe descuentos a libros por encima del 5%).
Voy a tardar todavía un poco en tener disponibles las reseñas y todos los formularios de combinaciones, pero si está interesado en la posibilidad, contacte conmigo, a través de mi cuenta de correo en IntSight. También enviamos estos libros al extranjero. El coste de envío es similar al de La Cara Oculta de C#, pues cada libro pesa menos de un kilogramo.
¡Combine y ahorre!

Etiquetas: , ,

sábado, diciembre 22, 2007

Docking en Volumen II

Pantalla principal del Ejercicio 25/Volumen II
Incluido como "extra", al final del Volumen II, este ejercicio adapta el layout manager usado a lo largo de la serie para que, en vez de utilizar ventanas MDI, utilice los componentes de docking de Weifen Luo (que dicho sea de paso, también se usan en Blade, el IDE de Freya).

Etiquetas: ,

martes, diciembre 18, 2007

Conciliación en presencia de identidades

Copio a continuación un pequeño fragmento de un ejercicio del Volumen II, para que os hagáis una idea del tipo de problemas prácticos al que se enfrenta una aplicación preparada para las tres capas, y que tienen que tratarse obligatoriamente como parte de un curso serio sobre bases de datos.
Programación con ADO.NET en C# - Volúmenes I y IILa implementación que hemos proporcionado a SaveChanges es mínima, y no es difícil encontrar casos para los que tendremos que complicarla. El primer ejemplo: la inserción de registros en tablas que contienen columnas con el atributo identity. De hecho, todas nuestras tablas tienen como clave primaria una columna con dicho atributo. Para entender en qué consiste el problema, sigamos el rastro de una inserción de registros. Supongamos que el usuario ha tecleado el siguiente registro para la tabla de países:
<  -1, 'PN', 'Polo Norte'... >
He puesto –1 como clave primaria del nuevo registro, para evitar confusiones. Supondremos que este valor ha sido asignado automáticamente en el lado cliente. No obstante, al grabar el registro, el algoritmo de grabación recupera el valor asignado a la clave por el servidor. Este sería el estado del registro una vez insertado y actualizado:
<1234, 'PN', 'Polo Norte'... >
Recuerde, sin embargo, que la grabación se ejecuta sobre un conjunto de datos auxiliar que sólo contiene las filas con algún tipo de cambio. Para que el conjunto de datos original se actualice, tenemos que ejecutar la operación Merge. Este método utiliza las claves primarias para identificar los registros originales con sus copias más recientes. Y esto es un problema, precisamente porque la clave primaria ha sido modificada durante la inserción. Después de ejecutar Merge tendríamos dos copias del mismo registro, con valores diferentes en la clave primaria:
<  -1, 'PN', 'Polo Norte'... >
<1234, 'PN', 'Polo Norte'... >
¿Se da cuenta de que nos sobra el primero de los registros? Para evitar esta duplicación de las inserciones, tendríamos que eliminar los registros añadidos al conjunto de datos original antes de mezclar el contenido de delta. Esta es una versión más completa de SaveChanges:
public virtual bool SaveChanges()
{
DataSet delta = Data.Instance.Write(
dataset.DataSetName, dataset.GetChanges());
foreach (DataTable tab in dataset.Tables)
{
foreach (DataRow row in tab.Select(
null, null, DataViewRowState.Added))
{
row.Delete();
}
}
dataset.Merge(delta, false);
dataset.AcceptChanges();
return true;
}
No importa si alguna tabla no utiliza el atributo de identidad. Si borramos una fila añadida dentro del conjunto de datos original, de todos modos volveremos a incorporarla al mezclar el delta. Observe que, para cada tabla, detectamos los registros añadidos gracias a su estado, aplicando el método Select a la tabla.
Naturalmente, el código que hay que incluir para resolver estos problemas eminentemente prácticos no siempre es sencillo o evidente. De ahí la dificultad de escribir un curso de ADO.NET verdaderamente útil, y mi decisión de mantener la línea argumental del actual curso en su actualización.

Actualización: Para Navidad, actualización de la serie D a VS2005...

Etiquetas: ,

domingo, diciembre 16, 2007

La maldita serie D

Me he puesto a migrar, en paralelo con la escritura de la serie sobre LINQ for SQL, la vieja serie D. Es relativamente fácil adaptar los ejercicios, incluso a Visual Studio 2008. En tres días he adelantado tanto como el ejercicio 10 (de 24 ejercicios en total). Y entonces me he dado cuenta del problema.
La línea de razonamiento y el desarrollo del curso son estupendos, pero parten de una premisa: el estudiante está interesado en aplicaciones multicapas, y está, además, convencido de la necesidad de las mismas. Por eso, supuse yo al escribir el original, aceptaría el uso de la compleja infraestructura que se desarrolla en el curso. En la práctica, supongo que lo más frecuente es que, quien lee los ejercicios, se pregunte si todo eso tiene que ser tan complicado, por necesidad, o si se trata de fuegos de artificio del autor, que intenta demostrar lo mucho que sabe.
Para que se comprenda lo que digo, aquí va un ejemplo concreto:
  • Normalmente, una grabación de los cambios en un conjunto de datos se resuelve en ADO.NET con una simple llamada al método Update de un adaptador, una sucesión de llamadas de este tipo, o el código equivalente, si se usan los adaptadores de tablas.
  • En cambio, en mi curso se añade código para eliminar filas insertadas en el conjunto de datos. Y si no se ha seguido la línea del curso, uno de repente se pregunta: ¿y por qué? Bueno, porque en mi curso la actualización no tiene lugar directamente sobre el conjunto de datos conectado a la rejilla, sino sobre una copia del mismo enviada a un servidor de capa intermedia. La famosa y sencilla llamada a Update transcurre allí, y lo que el estudiante tiene que aprender es a sincronizar el conjunto de datos modificado con los registros originales. Es una tarea típica de la programación multicapas.
Ahora me encuentro en una encrucijada. O termino la traducción de lo que hay, o corto por lo sano y saco una serie "práctica" inicial, que no considere la posibilidad de convertir la aplicación en un sistema multicapas. Parecerá absurdo, pero me atrae más esta segunda opción. La nueva división del curso en Volumen I y Volumen II obedece, en definitiva, a un propósito original: mantener dos línea paralelas, una más "teórica" y otra completamente práctica. El Volumen II final podría contener, en su fase final, dos series: la primera, para resolver aplicaciones en dos capas, y la tercera, que se ocupase de los temas específicos de la programación remota, que no tienen que ver tanto con la técnica de remoting en sí, sino con la sincronización.

Decisión final
Ya existe un Volumen I que muestra perfectamente cómo desarrollar en dos capas, usando las técnicas más que probadas que ofrece directamente Microsoft. Por lo tanto, la serie D se actualiza tal cual, mostrando técnicas propias de la programación multicapas. ¿Que es más complicado? Bueno... eso es valor añadido. Era una decisión sencilla, ¿verdad?

Etiquetas: , ,

jueves, diciembre 13, 2007

Next big thing

Próxima gran actualización del curso de ADO.NET: una serie dedicada a LINQ for SQL. Voy a experimentar esta vez con un formato que creo que será mejor para esta materia: el curso estará basado en el vídeo (narrado y subtitulado).
El problema que veo con LINQ para SQL es que no es tan completo como la programación con datasets para mover y actualizar datos entre capas. Por una parte, me he tropezado con la sorpresa de que los árboles de expresiones no vienen con soporte para la transmisión remota. Uno puede añadirlo por su cuenta, pero es bastante trabajo. Por lo tanto, cuando se piden datos a la capa intermedia, o regresas a la época en que pasabas una cadena, o te montas todo un tinglado para evaluar consultas remotas por tu cuenta y riesgo.
El otro problema es más sutil: hasta que no te pones a hacer un ejemplo real no tropiezas con él. El caso es que las colecciones de LINQ for SQL (los tipos genéricos Table<T> y EntitySet<T>) no ofrecen algo parecido al Merge de tablas y conjuntos de datos. Sí ofrecen la posibilidad de reasignar entidades al data context local, y de serializar las colecciones de entidades, pero eso sólo simplifica las aplicaciones que no modifican datos en la capa de presentación: vale sólo para aplicaciones básicamente de sólo lectura. Nuevamente, uno puede programar lo que falta... pero ya son dos subsistemas bastante complejos que hay que añadir por cuenta y riesgo propio.
Pero hay que contarlo todo, para verlo con perspectiva: a la actual versión de Visual Studio 2008 se le debe sumar, en relativamente poco tiempo, lo que en Microsoft llaman ADO.NET Entity Framework, que es la verdadera continuación del viejo proyecto ObjectSpaces, que sufrió un cambio radical con la aparición de LINQ. Al parecer, este sistema será mucho más completo y ambicioso que el actual.
A la espera del próximo gran invento, creo que lo mejor es ir explicando el trabajo con LINQ para SQL, que se parece bastante, sobre todo para evitar malas decisiones inducidas por las prisas. La actualización de las tres primeras series del curso deben ser también sencillas, porque son pocas las adiciones (la más notable, quizás, los nuevos coordinadores de grabación para relaciones que ahora se generan para los table adapters).

... de ahí la importancia del cambio de nombre de las series. La nueva serie sobre LINQ/SQL será la serie D, y la actual serie D se quedará en "volumen 2", como ya se refleja en los nuevos CDs del curso.

Etiquetas: , , , ,

viernes, diciembre 07, 2007

La espera

The waiting is the hardest part
Every day you see one more card
You take it on faith, you take it to the heart
The waiting is the hardest part
Ya está terminado el "Volumen I" del curso de ADO.NET, que incluye las series A, B y C completamente actualizadas. La serie B finalmente se ha quedado en unos pocos pero intensos ejercicios. Queda ahora "empaquetar" todo el contenido: vídeos, ejercicios y el manual del nuevo "volumen".
Y hay una estupenda noticia: a partir de la semana que viene, la oferta de libros "físicos" de nuestra tienda en Internet se amplia dramáticamente, con una nueva hornada de libros sobre ASP.NET, LINQ/C# 3.0 y WWF. Será una sorpresa agradable, se lo prometo :)


Adelanto, sobre formas de actualizar o adquirir el curso de ADO.NET:
  • Si adquirió el curso en agosto del 2007 o después: escríbame un email a la cuenta de IntSight, y recibirá el nuevo CD por correo certificado (paquete azul) sin coste alguno.
  • Si adquirió el curso antes de esa fecha: tampoco le costará nada. Pero la forma de actualización es mediante una descarga personalizada (ocupa unos 11MB). Escríbame para darle su dirección de descarga.
  • Alternativamente, si prefiere tener un CD "físico" con la actualización: voy a dar la posibilidad de recibir el CD por un coste de 10 euros (dentro de España), y pago por tarjeta, transferencia o ingreso en cuenta (no reembolso).
  • Posibilidad interesante: vamos a vender, en nuestra tienda, nuevos libros sobre LINQ, ASP.NET y WWF. Si le interesa alguno de estos temas, compre cualquiera de estos libros, pague un euro más por el CD, y se ahorra los gastos de envío del CD (porque ya se incluyen en el coste del libro). Puedo tardar dos o tres días en crear los formularios de compra: si le urge, o quiere más información mientras tanto, deme un toque por email.

Etiquetas: , ,