none
Duda EF y AutoMapper RRS feed

  • Pregunta

  • Hola,

    no sé si este es el foro más adecuado para esta pregunta, pero es aquí donde me recomendaron automapper y espero me puedan ayudar.

    Tengo una entidad principal PROVEEDORES que tiene asociadas las entidades CATALOGOS, FACTURAS (esta a su vez tiene asociada LINEAS_FACTURA) y RELACION_PROVEEDOR_DELEGACION. Todas tienen definida la clave primaria por un campo ID autonumérico. La entidad RELACION_PROVEEDOR_DELEGACION tiene los campos IDproveeror e IDdelegacion y representa una relación Uno a Muchos.

    Para cada una de estas entidades he creado una clase DTO que tiene los mismos campos que la entidad a la que hace referencia, yo recupero los datos con esta consulta:

    var result = context.Proveedores
                       
    .Include(x=>x.Catalogos)
                       
    .Include(x=> x.Facturas).Include(x=> x.Facturas.Select(y=>y.Lineas_Factura))
                        .Where(x=>x.Delegaciones.Any(y=>y.IDdelegacion == valor));

    En AutoMapper tengo las siguientes definiciones de mapeo:

    Mapper.CreateMap<LINEAS_FACTURA, LINEAS_FACTURA_DTO>();
    Mapper.CreateMap<FACTURAS, FACTURAS_DTO>();
    Mapper.CreateMap<CATALOGOS, CATALOGOS_DTO>();
    Mapper.CreateMap<RELACION_PROVEEDOR_DELEGACION, RELACION_PROVEEDOR_DELEGACION_DTO>();
    Mapper.CreateMap<PROVEEDORES, PROVEEDORES_DTO>();

    La asignación la hago de la siguiente manera:

    List<PROVEEDORES> listaProveedores = Mapper.Map<List<PROVEEDORES_DTO>>(result);

    La cosa es que no me da ningún error y la información de proveedores la mapea bien en "listaProveedores", pero facturas y catalogos quedan a null.

    ¿Es posible que AutoMapper mapee esto automáticamente?, si es así, ¿cómo sería?

    Gracias anticipadas por la ayuda.

    jueves, 11 de diciembre de 2014 11:30

Respuestas

  • Varias cosas:

    1.- Los nombres de las propiedades deben ser iguales, si no son iguales tienes que mapearlas a mano:

    Mapper.CreateMap<LINEAS_FACTURA, LINEAS_FACTURA_DTO>()
    .ForMember(dest => dest.PropiedadDestino, opt => opt.MapFrom(src => src.PropiedadFuente));

    2.- Ahora, si tu variable "result" es una lista, el mapeo debe ser (si es IEnumerable deberá cambiarse):

     Mapper.Map<List<PROVEEDORES>, List<PROVEEDORES_DTO>>(result);

    3.- Si creaste una nueva clase de configuración de mapeo (Profile) asegúrate de haberla incluido en la clase "Configure" del automapper, donde agregas todos tus Profile de mapeos.

    4.- Si lo que intentas hacer es mantener la navegación, deberás crear clases dto para cada una de estas (como ya lo has hecho) y, por ejemplo, en la clase Proveedores-DTO debes tener una propiedad de tipo Factura-DTO. Primero hacer el mapeo de la entidad "principal" y en segundo lugar asignar las propiedades y mapear una a una.

    Espero te sea de ayuda.


    Miguel Salazar
    -> www.miguelsalazar.mx

    • Marcado como respuesta weatherby viernes, 19 de diciembre de 2014 8:22
    • Editado Mig Salazar viernes, 19 de diciembre de 2014 16:03
    miércoles, 17 de diciembre de 2014 0:18

Todas las respuestas

  • que pasa si usas

    List<PROVEEDORES_DTO> listaProveedores = Mapper.Map<PROVEEDORES, PROVEEDORES_DTO>(result);


    el resultado sera una lista de dto de proveedores

    por supuesto valida que "result" devuelve alguna respuesta, porque quzias no devuelve ningun item por eso no hay nada que convertir

    saluds


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina


    jueves, 11 de diciembre de 2014 14:38
  • Hola de nuevo Leandro,

    VS me lanza un mensaje de error "La mejor  coincidencia del métodos AutoMapper.Mapper.Map<...... tiene algunos argumentos no válidos.

    En "result" tengo todos los datos, lo he comprobado con el inspeccionador de objetos de VS.

    jueves, 11 de diciembre de 2014 15:06
  • puede que "result" no sea un List<>, intenta usando

    var result = context.Proveedores
                       
    .Include(x=>x.Catalogos)
                       
    .Include(x=> x.Facturas).Include(x=> x.Facturas.Select(y=>y.Lineas_Factura))
                        .Where(x=>x.Delegaciones.Any(y=>y.IDdelegacion == valor)).ToList();

    valida que result tien una List<PROVEEDORES>

    ----

    tambien podrias ser necesario definir una lista para el mapping

    List<PROVEEDORES_DTO> listaProveedores = Mapper.Map<List<PROVEEDORES>, List<PROVEEDORES_DTO>>(result);

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    jueves, 11 de diciembre de 2014 16:24
  • Hola,

    me siguen viniendo a nulos.

    No se si tendrá que ver, pero en lugar de usar expresiones lambda como en tu ejemplo estoy usando linq ya que al intentar hacerlo como tú indicas me da el error: "No se puede convertir expresión lambda en tipo el string porque no es un tipo delegado"

    De manera que lo tengo así:

    var result = (from p in context.Proveedores.Include("CATALOGOS").(... resto de includes...)

    where p.Delegaciones.Any(y=>y.IDdelegacion == valor))

    select p).ToList();

    Efectivamente me viene una lista con los datos de PROVEEDORES, pero al hacer el mapeo las entidades relacionadas de esta nueva forma que me indicas me siguen quedando a null.

    viernes, 12 de diciembre de 2014 7:43
  • Por defecto AutoMapper mapea contra propiedades con el mismo nombre....

    Ejemplo PROVEEDORES.Nombre contra PROVEEDORES_DTO.Nombre

    Verifica que en tus DTO las propiedades tengan el mismo nombre que en tus entidades


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos

    viernes, 12 de diciembre de 2014 8:00
  • List<PROVEEDORES_DTO> listaProveedores = Mapper.Map< List<PROVEEDORES_DTO>>(result);
    Prueba esta linea de código

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos

    viernes, 12 de diciembre de 2014 8:03
  • Hola Sergio,

    efectivamente las propiedades de las entidades y las clases DTO tienen el mismo nombre.

    Esta opción que me comentas de hacer el mapeo es la primera que había probado, antes de probar la que propone Leandro.

    viernes, 12 de diciembre de 2014 9:51
  • hola

    validaste que el linq este retornando datos ?

    que pasa si solo usas

    var result = context.Proveedores
                       
    .Include(x=>x.Catalogos)
                       
    .Include(x=> x.Facturas).Include(x=> x.Facturas.Select(y=>y.Lineas_Factura)).ToList();

    o sea quitas el where para que devuelva todo, inspecciona "result" para ver que haya una respuesta

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    viernes, 12 de diciembre de 2014 13:31
  • Hola, perdón por la tardanza en responder.

    Efectivamente el linq devuelve todos los datos correctamente, es después de hacer el mapeo en que las listas asociadas a PROVEEDORES se quedan a null, pero originariamente en 'result' si que están los datos.

    Es por eso que yo sospechaba que pudiera ser la forma de definir los mapeos en AutoMapper.

    ¿Debo entender que para una estructura como la que estamos barajando los mapas están correctamente creados?

    martes, 16 de diciembre de 2014 14:40
  • prueba a mapear un IList

    var listaProveedores = Mapper.Map<IList<PROVEEDORES>,  IList<PROVEEDORES_DTO>>(result);


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos

    martes, 16 de diciembre de 2014 23:36
  • Varias cosas:

    1.- Los nombres de las propiedades deben ser iguales, si no son iguales tienes que mapearlas a mano:

    Mapper.CreateMap<LINEAS_FACTURA, LINEAS_FACTURA_DTO>()
    .ForMember(dest => dest.PropiedadDestino, opt => opt.MapFrom(src => src.PropiedadFuente));

    2.- Ahora, si tu variable "result" es una lista, el mapeo debe ser (si es IEnumerable deberá cambiarse):

     Mapper.Map<List<PROVEEDORES>, List<PROVEEDORES_DTO>>(result);

    3.- Si creaste una nueva clase de configuración de mapeo (Profile) asegúrate de haberla incluido en la clase "Configure" del automapper, donde agregas todos tus Profile de mapeos.

    4.- Si lo que intentas hacer es mantener la navegación, deberás crear clases dto para cada una de estas (como ya lo has hecho) y, por ejemplo, en la clase Proveedores-DTO debes tener una propiedad de tipo Factura-DTO. Primero hacer el mapeo de la entidad "principal" y en segundo lugar asignar las propiedades y mapear una a una.

    Espero te sea de ayuda.


    Miguel Salazar
    -> www.miguelsalazar.mx

    • Marcado como respuesta weatherby viernes, 19 de diciembre de 2014 8:22
    • Editado Mig Salazar viernes, 19 de diciembre de 2014 16:03
    miércoles, 17 de diciembre de 2014 0:18
  • Hola Sergio,

    mapeando como me indicas también quedan a nulo las propiedades de PROVEEDORES que son una lista.

    miércoles, 17 de diciembre de 2014 8:22
  • Hola Miguel,

    Contesto a tus puntos:

    1.- efectivamente los nombres de las propiedades de las clases DTO y las entidades del modelo son iguales.

    2.- La variable result la declaro como var, pero si ago un GetType() me devuelve esto:

    System.Collections.Generic.List [EF.PROVEEDORES]

    3.- Aquí no entiendo muy bien a que te refieres. Yo he creado una clase pública y estática llamada AutoMapperConfig que tiene un método RegisterMappings() donde están todos los mapeos definidos. y en el Main() de Program.cs hago una llamada al método: AutoMapperConfig.RegisterMappings();

    Los otros mapeos definidos aquí me funcionan correctamente.

    4.- Efectivamente intento mantener la navegación, es decir, obtener una lista de los PROVEEDORES con todos sus datos relacionados, tal como comento al principio del post la clase PROVEEDORES_DTO tiene una propiedad que es una lista de CATALOGOS_DTO, etc. y en las entidades del modelo existen las relaciones (uno a muchos).

    He cambiado el orden de los mapeos como me indicas, yo los tenía al revés, primero CATÁLOGOS, FACTURAS, etc. y el último PROVEEDORES. Ahora están al revés, primero PROVEEDORES y luego los demás. Sin embargo en las pruebas que he realizado las propiedades que son List de PROVEEDORES me siguen quedando a null después del mapeo.

    En 'result' los datos están correctamente.

    Espero haber contestado tus cuestiones. Entiendo que debería ser posible que AutoMapper "rellene" las clases DTO correctamente no?

    miércoles, 17 de diciembre de 2014 8:42
  • Lo que te ocurre es muy raro... qué versión de Automapper estás usando? Y la del framework de tu aplicativo?

    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos

    miércoles, 17 de diciembre de 2014 10:55
  • Pues no lo sé con seguridad, la de automapper entiendo que la última, ya que me lo bajé e instalé hace poco, nunca antes lo había utilizado. La del framework de .NET es la 4 y la de EF creo que es la 4.1

    miércoles, 17 de diciembre de 2014 15:21
  • 2.- Eso está bien. El tipo que te indica es correcto. Los tipos "var" son implícitos, es decir, no está definido su tipo hasta que el compilador infiere el tipo que le va asignar a la variable. En tu caso es una lista lo cual está bien.

    3.- Si, me refería a eso.

    4.- Asegúrate que están todos los mapeos definidos con CreateMap entre todos los DTO y Modelos. Proveedores, Facturas, etc. Yo creo que por aquí está fallando, si puedes postear el código de las clases DTO y los modelos, sería de ayuda.

    Por ejemplo, en el model o tendrías algo como:

    public class Proveedores
    {
      public int ID { get; set; }
      public string Nombre { get; set; }
      //otras propiedades
      public List<Facturas> Facturas { get; set; }
    }
    public class Facturas
    {
      public int ID { get; set; }
      public string Folio { get; set;}
      ///otras propiedades
    }

    De esta manera hay una relación entre proveedores y facturas. Entonces tus DTO serían:

    public class ProveedoresDTO
    {
       public int ID { get; set; }
       public string Nombre { get; set; }
       public List<FacturasDTO> ListaFacturas { get; set; }
    }
    public class FacturasDTO 
    {
      public int ID { get; set; }
      public string Folio { get; set; }
    }

    Ahora los mapeos:

    public class ProveedoresProfile: Profile
    {
        protected override void Configure()
        {
             Mapper.CreateMap<Proveedores ,ProveedoresDTO>();
        }
    }
    public class FacturasProfile: Profile
    {
        protected override void Configure()
        {
             Mapper.CreateMap<Facturas ,FacturasDTO>();
        }
    }

    En la clase anterior no importa el orden. Incluso podrías tener una sola clase Profile para ambos mapeos, pero me parece mas limpio tener una clase por Profile. Una vez teniendo esto, lo que yo mencionaba en el punto 3, es que tuvieras lo siguiente:

    public static class AutoMapperConfiguration
    {
        public static void Configure()
       {
          Mapper.Initialize(cfg =>
          {
            cfg.AddProfile(new ProveedoresProfile());
            cfg.AddProfile(new FacturasProfile());
          });
       }
    }

    Como ya sabes, la clase anterior debe llamarse al iniciar la aplicación. Entonces teniendo esto, en el main de tu programa consola o en el controlador de tu mvc o con lo que sea que estes trabajando:

    //puedes usar: var proveedores = db.Proveedores.Incl.....

    List<Proveedores> proveedores = db.Proveedores .Include(x=>x.Facturas).ToList();

    List<ProveedoresDTO> proveedoresDTO = Mapper.Map<List<Proveedores>, List<ProveedoresDTO>>(proveedores);

    Cuando iniciaste el hilo de esta pregunta, la segunda asignación del código anterior la tenias de tipo Proveedores y el result también era de tipo proveedores. Esto no es así, aunque el result esté implícitamente (var), el resultado debe asignarse hacia el DTO; de otra manera déjalo como var, también es correcto.

    La teoría dice que de esta manera el mapeo debe ser automático. Pero supongamos que no es así, también tendrías la siguiente opción:

    var proveedores = db.Proveedores .Include(x=>x.Facturas).ToList(); var proveedoresDTO = Mapper.Map<List<Proveedores>, List<ProveedoresDTO>>(proveedores); //La idea es asignar "a mano" la propiedad ListaFacturas del proveedoresDTO proveedoresDTO.ListaFacturas = Mapper.Map<List<Facturas>, List<FacturasDTO>>(proveedores.Facturas);

    Cuéntame que parte de esto que te menciono te falta o no ves claro.

    _________________________________________________

    Edito: Te dejo esta url como referencia: http://taswar.zeytinsoft.com/2011/03/09/automapper-mapping-objects-part-3-of-7-nested-mapping/


    Miguel Salazar
    -> miguelsalazar.mx

    • Editado Mig Salazar miércoles, 17 de diciembre de 2014 16:40 urll
    miércoles, 17 de diciembre de 2014 16:36
  • Hola Miguel,

    lo primero gracias por contestar.

    Verás, yo las clases Profile no las tengo. En mi caso es, por un lado las clases del modelo (las que se representan en el archivo .edmx), y por otras las DTO que las he creado yo.

    Como te comenté, tengo una clase donde defino los mapeos, esta es:

    public static class AutoMapperConfig {
    	public static void RegisterMappings() {
    		//Proveedores
    		Mapper.CreateMap<PROVEEDORES, PROVEEDORES_DTO>();
    		//Facturas
    		Mapper.CreateMap<FACTURAS, FACTURAS_DTO>();
    		//Líneas factura
    		Mapper.CreateMap<LINEAS_FACTURA, LINEAS_FACTURA_DTO>();
    		//Relación proveedor delegación
    		Mapper.CreateMap<RELACION_PROVEEDOR_DELEGACION, RELACION_PROVEEDOR_DELEGACION_DTO>();
    		//Catálogos
    		Mapper.CreateMap<CATALOGOS, CATALOGOS_DTO>();
    	}
    }


    Después, esta clase la llamo al iniciar la aplicación de esta manera:

    static class Program {
    	/// <summary>
    	/// Punto de entrada principal para la aplicación.
    	/// </summary>
    	[STAThread]
    	static void Main() {
    		//Establecer mapeos DTOs
    		AutoMapperConfig.RegisterMappings();
    
    		//Iniciar aplicación
    		...
    	}
    }

    La clase DTO de proveedores (simplificada), básicamente es así:

    public class CuentaGarantiaDTO {
    	public int ID { get; set; }
    	public string Nombre { get; set; }
    	public List<FACTURAS_DTO> Facturas { get; set; }
    	public List<CATALOGOS_DTO> Catalogos { get; set; }
    }

    Lo demás ya lo he ido comentando a lo largo de los posts.

    ¿Crees que puede ser porque no utilizo las clases Profile?, según yo lo veo, al final es lo mismo no?

    PD.- Me estoy mirando la serie de artículos del link que me pones.

    jueves, 18 de diciembre de 2014 10:23
  • En algún proyecto que he realizado, tengo configurado de esta forma el mapeo...

    AutoMapper.Mapper
         .CreateMap<entidad, entidadModel>()
         .Bidirectional()
         .ForAllMembersOfType(typeof(ICollection<>), x => x.UseDestinationValue())        
         .ForAllMembersOfType(typeof(ICollection<>), x => x.Ignore())




    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos


    • Editado Sergio Parra jueves, 18 de diciembre de 2014 10:34
    jueves, 18 de diciembre de 2014 10:31
  • Hola Sergio,

    la versión de AutoMapper que uso no tiene definiciónes para Bidirectional ni ForAllMembersOfType.

    jueves, 18 de diciembre de 2014 11:06
  • Fallo mío...

    esos métodos son una extensión que se implementó en un ensamblado aparte...

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using AutoMapper;
    using System.Linq.Expressions;
    
    public static class AutomapperExtensions
    {
        public static IMappingExpression<TDestination, TSource> Bidirectional<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression)
        {
            return Mapper.CreateMap<TDestination, TSource>();
        }
    
        public static IMappingExpression<TSource, TDestination> ForAllMembersOfType<TSource, TDestination>(this IMappingExpression<TSource, TDestination> expression, Type type, Action<IMemberConfigurationExpression<TSource>> memberOptions)
        {
            var candidates = typeof(TSource).GetProperties().Where(c => type.IsAssignableFrom((c.PropertyType.IsGenericType && type.IsGenericType) ? c.PropertyType.GetGenericTypeDefinition() : c.PropertyType))
                                                            .Where(c => typeof(TDestination).GetProperties().Where(x => x.Name == c.Name).Any());
    
            foreach (PropertyInfo pi in candidates)
            {
                expression = expression.ForMember(pi.Name, memberOptions);
            }
    
            return expression;
        }
    
    }


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó vótala como útil. Saludos


    • Editado Sergio Parra jueves, 18 de diciembre de 2014 11:15
    jueves, 18 de diciembre de 2014 11:12
  • ¿Crees que puede ser porque no utilizo las clases Profile?, según yo lo veo, al final es lo mismo no?

    Para fines prácticos, si es lo mismo. Las clases profile son solo para organizar los mapeos.

    La verdad es que es muy raro lo que te sucede, algo no está bien configurado. Podría sospechar que la variable "result" no tiene las navegaciones llenas, pero supongo esto ya lo revisaste.

    Las propiedades Facturas y Catalogos de tu clase CuentaGarantiaDTO, tienen el mismo nombre que las del modelo de CuentaGarantia?

    Postea un poco más de código, sin ejemplo, el real... para poder ver que puede estar fallando.


    Miguel Salazar
    -> www.miguelsalazar.mx


    • Editado Mig Salazar jueves, 18 de diciembre de 2014 16:06
    jueves, 18 de diciembre de 2014 16:06
  • Hola,

    por fin conseguí que funcione.

    Realmente no se que será, pero lo que he hecho es mapear las propiedades tipo List de la entidad PROVEEDORES con ".ForMember" y ahora ya está.

    Por algún motivo, automapper no lo hacía si no le indicas explícitamente como, sin embargo las propiedades "normales" si que las mapea.

    En cualquier caso ya me funciona.

    Muchas gracias a todos por vuestra ayuda y... ¡¡Felices Fiestas!!

    viernes, 19 de diciembre de 2014 8:27