Creando un API RESTFul Completo con Net Core para un Sistema CRM desde cero (3 de 5)
Tercera entrega: Aprende a hacer una Relaci贸n uno a muchos entre dos entidades y a utilizar las propiedades de navegaci贸n! 馃槈

Hey devs! esta es la tercera de cinco entradas donde estaremos creando un API con arquitectura RESTFul completo para hacer el backend de un sistema de gesti贸n de relaci贸n con los clientes o m谩s conocido por sus siglas en ingl茅s: CRM (Customer relationship management). 

Para que no te pierdas, aqu铆 est谩n todas las entradas de la serie:

En esta ocasi贸n aprender谩s a hacer una relaci贸n de uno a muchos entre dos entidades: Prospectos y contactos.

Los contactos son las veces que un agente visita o contacta a un prospecto, entonces si te fijas bien, un agente puede contactar una o mil veces a un prospecto hasta hacerlo cliente. Empezamos entonces 馃槈.

Actualizando Entidades

Creando entidad Contacto:

    public class Contacto
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public DateTime Fecha { get; set; }
        [Required]
        [StringLength(25)]
        public string Medio { get; set; }
        [StringLength(250)]
        public string Descripcion { get; set; }
        public int ProspectoId { get; set; } //sirve de nexo con la entidad Prospecto

        //Propiedades de navegaci贸n
        public Prospecto Prospecto { get; set; } //es para poder navegar hasta los datos del prospecto
    }

Si no ponemos la anotaci贸n StringLength, al momento de migrar a la base de datos se har铆a con el tipo de datos nvarchar(MAX) lo cual ser铆a un desperdicio de recursos. En este proyecto dej茅 la propiedad UrlPerfil de la clase entidad Prospecto sin esa anotaci贸n y f铆jate su tipo de datos c贸mo qued贸 馃憖

Nota adem谩s que a帽ad铆 una propiedad al final del tipo Prospecto, esta es una propiedad de navegaci贸n, ya que esta entidad est谩 relacionada con la entidad Prospecto, de esta forma le estoy diciendo a Entity Framework que estoy relacionando ambas tablas.

PRO TIP: Entity Framework Code first dice: Cuando dos entidades est谩n relacionadas con una relaci贸n uno a muchos, la entidad desde la cual parte la relaci贸n (osea entidad origen) llevar谩 una propiedad tipo List<> y la entidad destino, en este caso Contacto, llevar谩 una propiedad del mismo tipo que la entidad origen. Esto lo notar谩s a continuaci贸n...

Ahora entonces toca actualizar la entidad Prospecto, a帽adi茅ndole la propiedad tipo List<Contacto>

    public class Prospecto
    {
        [Key]
        public int Id { get; set; }

        [Required]
        [StringLength(70, ErrorMessage = "El campo {0} no debe exceder de {1} caracteres.")]
        public string Nombre { get; set; }
        public string UrlPerfil { get; set; }

        //Propiedades de navegaci贸n
        public List<Contacto> Contactos { get; set; }
    }

Actualizar Contexto

Cada vez que a帽ades una nueva clase entidad, como ahora con el caso de Contacto, debes actualizar el contexto, en este proyecto es la clase ApplicationDBContext:

    public class ApplicationDBContext : DbContext
    {
        public ApplicationDBContext(DbContextOptions options) : base(options)
        {

        }
        public DbSet<Agente> Agentes { get; set; }
        public DbSet<Prospecto> Prospectos { get; set; }
        public DbSet<Contacto> Contactos { get; set; }
    }

Aplicando migraci贸n

Como hemos actualizado nuestras entidades, eso significa nuevas tablas en la base de datos, adem谩s hemos inclu铆do relaciones entre tablas, para que todo esto se vea reflejado en la base de datos, ejecutamos una nueva migraci贸n, y as铆 debes ejecutar migraciones cada vez que quieras actualizar tu modelo f铆sico de base de datos.

Para aplicar una nueva migraci贸n debes crear una migraci贸n, yo la llamar茅 Contacto, el comando es el siguiente:

add-migration Contacto

Ahora ejecutamos la migraci贸n con:

update-database

Si revisas tu base de datos, deber铆as tener 3 tablas y dos de ellas relacionadas, con una relaci贸n tipo uno a muchos.

Para ver esto mejor, cr茅ate un diagrama de base de datos en SSMS.

Si tienes problemas para crear un nuevo diagrama de base de datos en tu base de datos CRM, haz clic derecho a tu base de datos, anda a Propiedades > Archivos > Propietario y agrega al usuario sa.

Para crear un diagrama, clic derecho a Diagramas de base de datos > Crear nuevo, a帽ade las tablas, excepto una tabla de metadatos creada por EF llamada _EFMigrationsHistory, ordena las tablas, elige una vista de tabla est谩ndar para cada una y guarda el diagrama:

Bien hecho, nuestras tablas se han creado y relacionado bien! 馃槉

A帽adiendo los DTO para Contacto

Agregamos por el momento dos DTO para la entidad nueva: ContactoDTO y ContactoCreacionDTO

ContactoDTO:

    public class ContactoDTO
    {
        public int Id { get; set; }
        public DateTime Fecha { get; set; }
        public string Medio { get; set; }
        public string Descripcion { get; set; }
    }

ContactoCreacionDTO:

    public class ContactoCreacionDTO
    {
        [Required]
        public DateTime Fecha { get; set; }
        [Required]
        [StringLength(25)]
        public string Medio { get; set; }
        [StringLength(250)]
        public string Descripcion { get; set; }
    }

Como notar谩s no estoy incluyendo la propiedad ProspectoId, y es porque 茅sta ir谩 inclu铆da en la ruta del controlador ContactosController como ver谩s a continuaci贸n 馃槈

Controlador ContactosController

Agregar esta clase que ser谩 el controlador para la entidad Contactos

Por el momento este controlador tendr谩 dos endpoints: Un Get(id) y un Post

    [ApiController]
    [Route("api/prospectos/{prospectoId:int}/comentarios")] //Ojo con la ruta dependiente, desde aqu铆 se obtiene el prospectoId
    public class ContactosController : ControllerBase
    {
        private readonly ApplicationDBContext context;
        private readonly IMapper mapper;

        public ContactosController(ApplicationDBContext context, IMapper mapper)
        {
            this.context = context;
            this.mapper = mapper;
        }

        [HttpGet]
        public async Task<ActionResult<List<ContactoDTO>>> Get(int prospectoId)
        {
            var existeProspecto = await context.Prospectos.AnyAsync(x => x.Id == prospectoId);

            if (!existeProspecto)
                return NotFound("El prospecto no existe");

            var contactos = await context.Contactos.Where(x => x.ProspectoId == prospectoId).ToListAsync();

            return mapper.Map<List<ContactoDTO>>(contactos);
        }

        [HttpPost]
        public async Task<ActionResult> Post(int prospectoId, ContactoCreacionDTO contactoCreacionDTO)
        {
            var existeProspecto = await context.Prospectos.AnyAsync(x => x.Id == prospectoId);

            if (!existeProspecto)
                return NotFound("El prospecto no existe");

            var contacto = mapper.Map<Contacto>(contactoCreacionDTO);
            contacto.ProspectoId = prospectoId;

            context.Contactos.Add(contacto);
            await context.SaveChangesAsync();
            return Ok();
        }
    }

A帽adir los perfiles de mapeo AutoMapper

Vamos a la clase AutoMapperProfile y a帽adimos:

    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            //Aqu铆 van las reglas de mapeo <origen,destino>
            CreateMap<AgenteCreacionDTO, Agente>();
            CreateMap<Agente, AgenteDTO>();
            CreateMap<ProspectoCreacionDTO, Prospecto>();
            CreateMap<Prospecto,ProspectoDTO>();
            CreateMap<ContactoCreacionDTO, Contacto>();
            CreateMap<Contacto, ContactoDTO>();            
        }
    }

Pruebas en Swagger

Una vez hecho todo esto, toca que pruebes en Swagger, veamos:

Aprovechamos para actualizar la propiedad Title dentro de la configuraci贸n de Swagger en la clase Startup y el m茅todo ConfigureServices le pondr茅 Sistema CRM馃槈

Tenemos esto, genial!

He creado tres prospectos:

Probaremos entonces un error, tratar茅 de obtener los contactos del prospecto con Id que no existe, digamos Id = 4

Muy bien, est谩 validando bien 馃槈.

Ahora creo un par de contactos y luego obtendr茅 los contactos realizados a nuestro prospecto con Id = 3 llamada Rosa Mart铆nez (el nombre de mi madre 馃槝)

Y se nos retorna lo correcto, los dos contactos que tiene, GENIAL! 馃槑

Nuestro API ya va tomando forma crack! 馃榾

Seguiremos en la pr贸xima entrega aprendiendo m谩s cosas interesante, y que te dar谩n de comer mi estimado lector as铆 que a echarle ganas 馃槈

Seguro que est谩s feliz como estos devs 馃榿

Si esta entrada te ha encantado, seguro que s铆, entonces ya sabes qu茅 hacer crack! Comparte 馃槈

2 comentarios en 芦Creando un API RESTFul Completo con Net Core para un Sistema CRM desde cero (3 de 5)

Deja una respuesta

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *