9.03.2010

DataSets o POCOs – Plain Old CLR Object

Al día de hoy no había querido ahondar en este tema porque es un tema un poco dificil de aclarar sin estar presente y sin la posibilidad de “hacer garabatos” en una pizarra para aclarar más las cosas; sin embargo, dado que el tema se repite una y otra vez, voy a tratar de explicar cuales son las razones por las cuales yo siempre prefiero utilizar POCOs a DataSets en este post.

Definiciones

En primera instancia, vamos a definir un POCO. Un POCO es un Plain Old CLR Object – un concepto traido desde Java desde el famoso POJO ( como muchos otros ) – y en el cual simplemente se define una clase que contiene los atributos de las entidades y sus propiedades. Además, la clase contiene un constructor vacío y un constructor para inicializar todos sus elementos.

El DataSet, como ya muchos saben es una clase bastante sofisticada que se encuentra definida en el namespace System.Data y es neutral al repositorio de datos que se este utilizando; – igual que el POCO – esta es la razón por la cual el DataSet esta definido en este Namespace. El DataSet es un contenedor que tiene la capacidad de mantener datos en memoria y de forma desconectada ( sin necesidad de tener una conexión a la base datos viva ) por lo que son útiles en algunos contextos donde se desea seguir trabajando si la base de datos no esta disponible – posible pero arriesgado.

Mi Opción

Yo siempre voy a preferir trabajar con POCO o entidades de negocios planas a trabajar con dataset por las razones que a continuación enumero:

  1. El costo de instanciación de un dataset vrs un POCO es muy alto. El dataset tiene una cantidad enorme de relaciones con clases que le permiten llevar a cabo todas sus tareas cuando se necesita. Por ejemplo, hay colecciones que se instancian para manejo de tablas, relaciones, vistas default, etc. Por lo tanto si vamos a hacer una simple consulta que nos devuelve un registro, el costo de inicialización del dataset supera por mucho el verdadero conjunto de datos del dominio de la aplicación que necesitamos para trabajar.
  2. Si no se usan dataset tipados para trabajar en nuestra aplicación, vamos a tener operaciones de boxing y unboxing cada vez que cargamos el dataset o que modificamos los elementos existentes dentro del mismo. Esto se da porque el Dataset para poder contener cualquier esquema de datos utiliza el tipo object en cada uno de las columnas y por lo tanto cada valor que se obtiene de la fuente de datos se convierte a un object – boxing – y cuando se requiere sacar del dataset se convierte de object al value type requerido – unboxing. Como ya algunos saben, las operaciones de boxing y unboxing son de las más caras que existen en el framework – solo por debajo de algunas como cargar documentos usando el XmlDocument o hacer transformaciones con XSLT.
  3. A la hora de consumir servicios si el servicio expone DataSets la cantidad de XML que se serializa es mucho mayor a la que se genera en el contrato SOAP a la hora de serializar una entidad plana – POCO – y por poco que parezca, cualquier ganancia en lo que respecta a los datos que se envían por el canal es relevante.
  4. Si diseñamos la aplicación y utilizamos UML, todos nuestros esquemas diseñados son inaplicables a este tipo de entidades porque no se puede crear patrones como el adapter o no se puede trabajar directamente sobre el contrato y no la implementación del contrato para tener más libertad a la hora de hacer crecer la aplicación hacia nuevas funcionalidades.
  5. Por la razón anterior, el uso de contenedores de IoC como Unity se vuelve practicamente imposible a nivel de entidades de negocio.
  6. El código desarrollado cuando se trabaja con dataset no tipados es dificil de mantener ya que por lo general las columnas se acceden a través de índices y no a través de nombres en la colección de columnas, por lo que si se agrega o se quita una columna en la base de datos vamos a tener problemas en tiempo de ejecución y no en tiempo de compilación.
  7. Mucha gente utiliza los dataset para escenarios desconectados donde no se tiene acceso al repositorio de datos en un momento determinado, sin embargo estos escenarios son un poco riesgosos ya que si el cliente se queda sin acceso al repositorio por una cantidad de tiempo considerable, puede que su aplicación falle y termine perdiendo todas las operaciones hechas en el dataset durante ese tiempo. Para este tipo de escenarios es mejor tener una base de datos local – SQL server Express y utilizar el Sync Framework de Microsoft para tener los datos sincronizados de manera segura.

Por ahora me quedo con estas razones, aunque tengo algunas más relacionadas a temas de IoC y Transaccionabilidad, pero ocuparía otro post completo para detallarlas. Debo aclarar que la mayoria de las razones expuestas aquí para algunas personas no son relevantes puesto que utilizan diferentes formas de trabajar a las que yo estoy considerando aquí – arquitectura n-layer –; en otros casos no modelan sus aplicaciones en UML y/o tampoco exponen la funcionalidad de su aplicación vía servicios.

Technorati Tags: ,,,

9.01.2010

ValueResolver<T> y AutoMapper

En el post pasado acerca de AutoMapper, se expuso como hacer resúmenes de datos – sumatorias en este caso – utilizando Automapper. Entre los correos y comentarios que me llegaron acerca del post,  hubieron varios que me indicaban que la conversión en la configuración del mapeo, era un código un poco complicado de entender y dificil de dar mantenimiento, por lo cual me decidí a escribir este post en el cual explico como hacerlo de una forma “más mantenible" y entendible”.

AutoMapper tiene una clase abstracta llamada ValueResolver la cual nos permite configurar como queremos mapear a un tipo destino. La definición de esta clase dentro de AutoMapper es la siguiente:

using System;

namespace AutoMapper
{
public abstract class ValueResolver<TSource, TDestination> : IValueResolver
{
protected ValueResolver();

public ResolutionResult Resolve(ResolutionResult source);
protected abstract TDestination ResolveCore(TSource source);
}
}



Esta clase nos permite definir el tipo destino y el tipo fuente a través de la definición genérica de la clase. Además tiene un método crucial < ResolverCore> el cual debe de ser implementado cuando se utiliza al clase abstracta; este método es el que va a contener la lógica que nosotros queramos agregarle a la conversión de nuestros tipos. En nuestro caso, queremos tener un poco mas de control respecto a la forma en que se mapea el campo TotalDeHorasDelProyecto y CostoTotalDelProyecto, los cuales están definidos como atributos en la clase Resumen del Proyecto. Para lograr una configuración de la conversión un poco mas sencilla, vamos a crear un par de clases CostTotalDelProyecto y TotalDeHorasDelProyecto las cuales van a implementar el metodo ResolveCore. La definición de las clases es la siguiente:



public class TotalDeHorasDelProyecto : ValueResolver<Proyecto, double>
{

protected override double ResolveCore(Proyecto source)
{
return source.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas);
}

}

public class CostTotalDelProyecto : ValueResolver<Proyecto, double>
{

protected override double ResolveCore(Proyecto source)
{
return source.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas * tarea.CostoXHora);
}
}



Como se ve en el código anterior, en la implementación del método ResolveCore se codifico el código que antes estaba en la configuración del Mapeo. Ahora procedemos a reconfigurar nuestro mapeo en la aplicación de la siguiente manera:



Mapper.CreateMap<Proyecto, ResumenDelProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, opt => opt.ResolveUsing<TotalDeHorasDelProyecto>())
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto, opt => opt.ResolveUsing<CostTotalDelProyecto>());

var cp = Mapper.Map<Proyecto, ResumenDelProyecto>(p);



Analizando el código en negrita, vemos que estamos utilizando el método ResolveUsing<T> de AutoMapper, el cual nos permite indicarle el tipo de alguna clase que tenga implementada la clase abstracta ValueResolver, esto por cuanto el método ResolveUsing va a invocar por contrato la implementación del método ResolveCore provisto por la clase enviada como parámetro en T.



Technorati Tags: ,