8.22.2010

AutoMapper 2 – Trabajando con Conversiones de Varios Niveles

Siguiendo con el post anterior, Automapper no solamente nos permite mapear clases con una relación de 1 a 1. En este post vamos a ver como podemos totalizar de diversas formas una relación entre dos clases utilizando AutoMapper. En primera instancia vamos a suponer una relación entre dos clases, una llamada Tarea la cual contiene información referente a las tareas que componen un proyecto y cuya estructura se puede ver aca.

public class Tarea
{
public int Id { get; set; }
public string Nombre { get; set; }
public double HorasAsignadas { get; set; }
public double CostoXHora { get; set; }

public double GetTotal()
{
return HorasAsignadas * CostoXHora;
}
}


En este caso simplemente tenemos 4 propiedades automáticas y una función que me retorna el costo total de esta tarea. Seguidamente vamos a tener una clase llamada proyecto, la cual contiene una lista de tareas, y dos métodos que nos permiten ver el total de horas del proyecto y el costo total en horas del proyecto. Ambos métodos utilizan el método de extensión SUM de las listas en .NET.



public class Proyecto
{
public int Id { get; set; }
public string Nombre { get; set; }
public List<Tarea> TareasDelProyecto { get; set; }
public DateTime FechaInicio { get; set; }
public DateTime FechaFinal { get; set; }

public double GetTotal()
{
return TareasDelProyecto.Sum(tarea => tarea.GetTotal());
}

public double ObtenerTotalHorasDelProyecto()
{
return TareasDelProyecto.Sum(tarea => tarea.CostoXHora);
}

public Proyecto()
{
TareasDelProyecto = new List<Tarea>();

}
}



Seguidamente vamos a mostrar la clase a la que quiero mapear las clases anteriores. Esta clase lo que quiero que tenga es un resumen del proyecto en lo que respecta a costos y tiempo, por lo tanto, esta clase va a tener una propiedad nombre para identificar el proyecto, una campo para mapear el total del horas empleadas en el proyecto, y un campo para mapear el costo total en horas del proyecto.



public class CostoTotalDeProyecto
{
public string NombreProyecto { get; set; }
public double TotalHorasDelProyecto { get; set; }
public double CostoTotalEnHorasDeProyecto { get; set; }
}



Ahora vamos a configurar AutoMapper para que me haga el mapeo de estas clases.



Mapper.CreateMap<Proyecto, CostoTotalDeProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, src => src.MapFrom( fuente=> fuente.ObtenerTotalHorasDelProyecto()))
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto, src => src.MapFrom ( fuente => fuente.GetTotal()));



Luego creamos las tres instancias para la clase tarea y se las agregamos a la clase proyecto.



Tarea t1 = new Tarea
{
Id = 1,
Nombre = "Reunión Diaria Seguimiento",
HorasAsignadas = 1,
CostoXHora = 20
};

Tarea t2 = new Tarea
{
Id = 2,
Nombre = "Diseño del plano",
HorasAsignadas = 80,
CostoXHora = 25
};

Tarea t3 = new Tarea
{
Id = 3,
Nombre = "Revisión de Avance",
HorasAsignadas = 160,
CostoXHora = 20
};

Proyecto p = new Proyecto
{
Id = 1,
Nombre = "Construcción Casa",
FechaInicio = DateTime.Now,
FechaFinal = DateTime.Now.AddDays(160)
};

p.TareasDelProyecto.Add(t1);
p.TareasDelProyecto.Add(t2);
p.TareasDelProyecto.Add(t3);


Ahora procedemos a generar el mapeo de la clase. Este procedimiento es el mismo que habiamos visto en el post antes mencionado.



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



Por último, vamos a escribir en la consola el resultado del mapeo.



Console.WriteLine("Nombre del Proyecto: {0} ", cp.NombreProyecto);
Console.WriteLine("Total de Horas del Proyecto: {0}", cp.TotalHorasDelProyecto.ToString());
Console.WriteLine("Costo total en horas del proyecto: {0}", cp.CostoTotalEnHorasDeProyecto.ToString());
Console.ReadLine();



El resultado de ejecutar este código se puede ver en la siguiente imagen.



image



Ahora, supongamos que no tenemos la posiblidad de tener los métodos de la clase proyecto que sumarizan los resultados que queremos exponer desde nuestro mapeo, y que lo único que vamos a tener en la clase Proyecto es el estado del objeto. En este caso, vamos a modificar las clases iniciales para que luzcan así:



public class Tarea
{
public int Id { get; set; }
public string Nombre { get; set; }
public double HorasAsignadas { get; set; }
public double CostoXHora { get; set; }

}

public class Proyecto
{
public int Id { get; set; }
public string Nombre { get; set; }
public List<Tarea> TareasDelProyecto { get; set; }
public DateTime FechaInicio { get; set; }
public DateTime FechaFinal { get; set; }

public Proyecto()
{
TareasDelProyecto = new List<Tarea>();

}
}


Como se puede ver en el código anterior, ya la clases no contiene los métodos que nos ayudan a sumarizar los valores que deseamos ver en la clase mapeada, es decir, total de horas del proyecto y costo total en horas del proyecto.  Para lograr que el mapeo funcione, movemos las operaciones de sumatoria a la expresión lambda  tal y como se muestra a continuación



Mapper.CreateMap<Proyecto, CostoTotalDeProyecto>()
.ForMember(dest => dest.NombreProyecto, src => src.MapFrom(fuente => fuente.Nombre))
.ForMember(dest => dest.TotalHorasDelProyecto, src =>
src.MapFrom(fuente => fuente.TareasDelProyecto.Sum(tarea => tarea.CostoXHora)))
.ForMember(dest => dest.CostoTotalEnHorasDeProyecto,
src => src.MapFrom(fuente => fuente.TareasDelProyecto.Sum(tarea => tarea.HorasAsignadas * tarea.CostoXHora)));



Como podemos ver en el texto en negrita, la expresión para procesar una sumatoria tanto del costo por hora del proyecto como del total de horas que requiere el proyecto se realiza de forma simple en el mapeo sin necesidad de depender de las clases para que ellas realicen este cálculo. El resultado de ejecutar esta operación es el siguiente:



image



Como podemos ver, el resultado es exactamente igual a que nos dio la ejecución anterior cuando utilizabamos las operaciones de totalización dentro de las clases.



Technorati Tags: ,,

1 comentario:

Unknown dijo...

Estimado. Cómo puedo pasar la lista entera? y no solo atributos de esta?

Mira, tengo que hacer lo siguiente:

Tengo una lista con datos en una objeto de tipo ImpresoraTO:

esta contiene principalmente los atributos:
Nombre (string)
Codigo (long)
listaFormatosImpresora (IList)


En la clase FormatoImpresoraTO tengo los atributos:

codigoFormato (long)
descripcionFormato (string)
switchFormato (long)

consumiendo un servicio web, obtengo una lista con datos con la misma estructura y necesito con automapper poder mapear esta lista que viene del servicio web a mi lista de tipo ImpresoraTO. Cuando mapeo solamente los datos (sin la lista de formatos) no tengo problemas, pero al momento de intentar mapear la lista de formatos dentro la lista de impresoras me lanza errores con un problema respecto al mapeo.

Como puedo hacerlo para mapear ademas de los atributos de la lista de impresora tambien poder mapear la lista de formatos?

Saludps y muchas gracias de antemano