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:
- https://bravedeveloper.com/2022/04/22/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-1-de-5/
- https://bravedeveloper.com/2022/04/27/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-2-de-5/
- https://bravedeveloper.com/2022/05/02/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-3-de-5/
- https://bravedeveloper.com/2022/05/13/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-4-de-5/
- https://bravedeveloper.com/2022/05/23/creando-un-api-restful-completo-con-net-core-para-un-sistema-crm-desde-cero-5-de-5/
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:
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 😉
Espectacular trabajo !!
Soy nuevo en este lenguaje y cada día me intereso más en el tema
antes programaba con Visual Basic 6.0 ahora, después de muchos años con Visual Code
Dapper / Entity Framework!
Felicitaciones!!!
el video final lo dijo todo!! jajaja
Saludos desde Argentina.
Hey Carlitos! Mucho gusto, que bueno que te haya gustado la entrada!! Para mi significa bastante lo que dices, me impulsa a seguir creando más contenido, compártelo en todas tus redes sociales, eso ayuda un montón. Sigue practicando capo, llegarás lejos si lo haces, un abrazo dev argento. 🤝🦊