Una relación uno a muchos (1
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 1
- 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
).
- Una propiedad de navegación que apunta al “uno” (
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
Carga explícita
using (var context = new AppDbContext())
{
var blog = context.Blogs.Find(1); // Consulta 1: Obtener Blog
var posts = context.Posts.Where(p => p.BlogId == blog.BlogId).ToList(); // Consulta 2: Obtener Posts
}
Eager loading
var blogWithPosts = context.Blogs
.Include(b => b.Posts) // JOIN automático
.FirstOrDefault(b => b.BlogId == 1);
Lazy loading
Requiere que las propiedades de navegación sean virtual
.
public class Blog
{
public virtual ICollection<Post> Posts { get; set; }
}
// Cuando se accede a blog.Posts, EF carga los posts automáticamente
var blog = context.Blogs.Find(1);
var posts = blog.Posts; // Carga bajo demanda
Insertar Blog con Posts
using (var context = new AppDbContext())
{
var blog = new Blog { Url = "https://example.com" };
blog.Posts.Add(new Post { Title = "Primer Post", Content = "Contenido..." });
blog.Posts.Add(new Post { Title = "Segundo Post", Content = "Más contenido..." });
context.Blogs.Add(blog);
context.SaveChanges(); // Guarda Blog y Posts en una transacción
}
Insertar Post en un Blog existente
var blog = context.Blogs.Find(1);
var newPost = new Post { Title = "Nuevo Post", BlogId = blog.BlogId };
context.Posts.Add(newPost);
context.SaveChanges();