¡Hola a todos! Hoy vamos a explorar cómo trabajar con claves foráneas en Entity Framework Core utilizando .NET. Las claves foráneas son fundamentales en cualquier base de datos relacional, ya que permiten relacionar tablas y mantener la integridad de los datos. Entonces, ¿por qué no sumergirnos directamente en este apasionante tema?
Índex
1 - Relacionando entidades en entity framework core
Para este post vamos a utilizar el mismo contenido que hemos ido utilizando a lo largo del curso. Para nuestro caso tenemos las entidades User
, la cual representa a un usuario del sistema, y WorkingExperience
que representa una entidad de ese usuario. Ahora lo que vamos a hacer es enlazar ambas, para que a través de la entidad de usuario, podamos acceder a la experiencia profesional, sin la necesidad de hacer otra llamada a la base de datos.
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
[MaxLength(50)]
public string Email { get; set; }
public ICollection<Wokringexperience> Wokringexperiences { get; set; } // <- this line
}
Para ello, en nuestra clase User
añadimos un ICollection<WorkingExperience>
, utilizamos ICollection
porque nos da algo mas de versatilidad comparado con IEnumerable
, como puede ser añadir elementos, borrarlos o actualizarlo, si no vamos a utilizar ninguna de estas características podemos utilizar IEnumerable
, IList
es otra muy buena alternativa, pero como siempre depende de tu caso de uso.
Y luego en WorkingExperience
le podemos indicar también el UserId.
public class Wokringexperience
{
public int Id { get; set; }
public int UserId { get; set; } //<------ this line
[MaxLength(50)]
public string Name { get; set; }
public string Details { get; set; }
public string Environment { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
Con esto, lo que nos queda ahora es correr las migraciones una vez las migraciones han terminado, vemos como nos han añadido un index y la clave foránea:
public partial class LinkUserAndWorkingExperiences : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_Wokringexperiences_UserId",
table: "Wokringexperiences",
column: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_Wokringexperiences_Users_UserId",
table: "Wokringexperiences",
column: "UserId",
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Wokringexperiences_Users_UserId",
table: "Wokringexperiences");
migrationBuilder.DropIndex(
name: "IX_Wokringexperiences_UserId",
table: "Wokringexperiences");
}
}
Nota: antiguamente hacía falta indicar la relación en el OnModelCreating
de nuestro DbContext
, pero ya no hace falta, este era el código necesario:
modelBuilder.Entity<Wokringexperience>()
.HasOne(l => l.User)
.WithMany(a => a.Wokringexperiences)
.HasForeignKey(l => l.UserId);
2 - Insertar Datos relacionados en Entity Framework
En este ejemplo voy a añadir los datos en el controlador para simplificar, pero si estáis en un entorno laboral, por favor utilizar una estructura-https://www.netmentor.es/entrada/estrucutra-aplicacion- acorde para el proyecto.
Para eso simplemente inyectamos nuestro CursoEfContext y lo utilizamos, en este ejemplo podemos ver como estamos indicando el usuario primero y luego utilizamos ese mismo usuario para insertar las WorkingExperience.
private readonly CursoEfContext _context;
[HttpPost("InsertDataExample1")]
public async Task InsertDataExample1()
{
User user1 = new User()
{
Email = $"{Guid.NewGuid()}@mail.com",
UserName = "id1"
};
List<Wokringexperience> workingExperiences1 = new List<Wokringexperience>()
{
new Wokringexperience()
{
UserId = user1.Id,
Name = "experience 1",
Details = "details1",
Environment = "environment"
},
new Wokringexperience()
{
UserId = user1.Id,
Name = "experience 2",
Details = "details2",
Environment = "environment"
}
};
await _context.Users.AddAsync(user1);
await _context.Wokringexperiences.AddRangeAsync(workingExperiences1);
await _context.SaveChangesAsync();
}
Entity Framework core es suficientemente inteligente para comprender que nuestra columna UserId
hace referencia al Id de la tabla User
de forma atumática, y así lo podemos ver en la base de datos.
Y gracias a que en nuestra entidad User
hemos indicado WorkingExperiences
como ICollection<T>
, podemos insertar todo a la vez únicamente insertando el usuario:
User user1 = new User()
{
Email = $"{Guid.NewGuid()}@mail.com",
UserName = "id1",
Wokringexperiences = new List<Wokringexperience>()
{
new Wokringexperience()
{
Name = "experience 1 same object",
Details = "details1",
Environment = "environment"
},
new Wokringexperience()
{
Name = "experience 2 same object",
Details = "details2",
Environment = "environment"
}
}
};
3 - Consultar datos relacionados con Entity Framework
Finalmente nos queda ver cómo podemos consultar la información, para leer tablas relacionadas lo que vamos a hacer con entity framework core es utilizar el método .Include()
que contiene el DbSet
.
En este caso, vamos a consultar usuarios y le solicitamos que nos devuelva también las WorkingExperiences
[HttpGet("{userId}")]
public async Task<User?> GetExample(int userId)
=> await _context.Users
.Include(u => u.Wokringexperiences)
.FirstOrDefaultAsync(a => a.Id == userId);
Y cómo podemos ver, funciona perfectamente:
Antes de terminar con el post creo que es importante mencionar que hay que tener cuidado con el uso de los includes por todas partes, y esto es debido a que EF core siempre ha tenido mala fama por las consultas que crea por detrás, cosa que ciertamente ha mejorado, pero, da igual cuanto mejore la librería que si en cada consulta que haces, consultas todas las tablas relacionadas, vas a tener unos cuellos de botella tremendos en la base de datos.
Y por supuesto, cuidado con hacer bucles, en plan una entidad que llama a otra la cual llama a la original, eso también puede causar muchos problemas.