entity-framework-relaciones-uno-a-muchos

Relaciones uno a muchos en Entity Framework

  • 4 min

Una relación uno a muchos (1) ocurre cuando una entidad A puede tener muchas instancias de una entidad B, pero cada instancia de B está relacionada con una sola A.

En otras palabras, un registro de una tabla puede estar relacionado con varios registros en otra tabla, pero un registro de la segunda tabla solo puede estar asociado a un registro de la primera tabla.

Este es el tipo de relación más común en las bases de datos

Vamos a modelar un sistema donde:

  • Un Blog puede tener muchos Posts.
  • Cada Post pertenece a un solo Blog.
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    
    // Propiedad de navegación (1 → N)
    public ICollection<Post> Posts { get; set; } = new List<Post>();
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    
    // Clave foránea implícita (BlogId)
    public int BlogId { get; set; }
    
    // Propiedad de navegación (N → 1)
    public Blog Blog { get; set; }
}

Configurar la relación

Con convenciones de Nombres

Entity Framework infiere automáticamente la relación 1si:

  • La entidad “uno” (Blog) tiene una colección de la entidad “muchos” (ICollection<Post>).
    -La entidad “muchos” (Post) tiene:
    • Una propiedad de navegación que apunta al “uno” (public Blog Blog).
    • Una clave foránea que sigue la convención {NombreClase}Id (BlogId).

Con Data Annotations

Si no seguimos las convenciones, podemos usar atributos:

[ForeignKey] en la clave foránea

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }

    [ForeignKey("Blog")] // Indica que BlogId es FK de Blog
    public int BlogForeignKey { get; set; } // Nombre no convencional

    public Blog Blog { get; set; }
}

[InverseProperty] para relaciones ambiguas. Útil cuando hay múltiples relaciones entre las mismas entidades.

public class Blog
{
    public int BlogId { get; set; }
    
    [InverseProperty("Blog")] // Aclara qué propiedad en Post corresponde
    public ICollection<Post> Posts { get; set; }
}

Con Fluent API

En el DbContext, usamos OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(b => b.Posts)       // Un Blog tiene muchos Posts
        .WithOne(p => p.Blog)        // Un Post pertenece a un Blog
        .HasForeignKey(p => p.BlogId); // Clave foránea en Post
}

Estructura de la base de datos generada

Al aplicar una migración, EF crea las tablas:

CREATE TABLE Blogs (
    BlogId INT PRIMARY KEY IDENTITY,
    Url NVARCHAR(MAX)
);

CREATE TABLE Posts (
    PostId INT PRIMARY KEY IDENTITY,
    Title NVARCHAR(MAX),
    Content NVARCHAR(MAX),
    BlogId INT NOT NULL,
    FOREIGN KEY (BlogId) REFERENCES Blogs(BlogId)
);

BlogId en Posts es una clave foránea no única (permite múltiples posts por blog).

Configuraciones avanzadas

Aparte de las relaciones básicas, Entity Framework permite configurar de manera avanzada aspectos de las relaciones entre entidades. Esto incluye:

Relación con eliminación en cascada

Cuando una entidad relacionada se elimina, la eliminación en cascada asegura que todas las entidades relacionadas se eliminen también.

modelBuilder.Entity<Libro>()
    .HasOne(l => l.Autor)
    .WithMany(a => a.Libros)
    .OnDelete(DeleteBehavior.Cascade);

Relación de sólo lectura

En algunas situaciones, puede que no quieras permitir que se modifique una relación en una entidad.

modelBuilder.Entity<Libro>()
    .HasOne(l => l.Autor)
    .WithMany(a => a.Libros)
    .OnDelete(DeleteBehavior.Restrict);

Relaciones opcionales

Algunas relaciones son opcionales, lo que significa que una entidad puede no tener una relación con la otra.

modelBuilder.Entity<Libro>()
    .HasOne(l => l.Autor)
    .WithMany(a => a.Libros)
    .IsRequired(false);  // Relación opcional

Ejemplos practicos