En el diseño de bases de datos, una relación uno a uno (1:1) ocurre cuando un registro en una tabla está asociado con exactamente un registro en otra tabla.
Este tipo de relación es útil para segmentación de información (por ejemplo, entidades que están íntimamente relacionadas, pero son cosas diferentes).
Supongamos que tenemos dos entidades:
User
Profile
Cada usuario tiene exactamente un perfil, y cada perfil pertenece a un solo usuario.
public class User
{
public int Id { get; set; }
public string Username { get; set; } // Para login
public string Email { get; set; } // Para autenticación
public string PasswordHash { get; set; } // Seguridad
public Profile Profile { get; set; } // Propiedad de navegación
}
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; } // Nombre completo
public DateTime BirthDate { get; set; } // Fecha de nacimiento
public string Bio { get; set; } // Biografía corta
public User User { get; set; } // Propiedad de navegación
}
¿Por qué no poner todo en User?
En este ejemplo podemos decidir tener las entidades separadas por segregación de datos (es decir, por limpieza)
User
contiene solo la información necesaria para la autenticación y seguridad del sistema,Profile
, en cambio, guarda datos sobre la persona asociada al User
Otro ejemplo claro lo verás en una relación 1:1 podría ser Empleado
y Ordenador
. Aunque un empleado pueda tener un solo ordenador, y un ordenador una única persona, son entidades separadas.
Configurar la relación
Convenciones de nombres
Entity Framework puede inferir la relación 1:1 si:
- Ambas entidades tienen una propiedad de navegación que apunta a la otra.
- Una de las entidades tiene una clave foránea que sigue la convención
{NombreClavePrimaria}Id
.
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
public int UserId { get; set; } // Clave foránea (UserId → User.Id)
public User User { get; set; } // Propiedad de navegación
}
EF detecta automáticamente que Profile.UserId
es clave foránea de User.Id
Con Data Annotations
Si no seguimos las convenciones, podemos usar atributos:
[ForeignKey]
en la clave foránea
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
[ForeignKey("User")] // Indica que UserId es FK de User
public int UserId { get; set; }
public User User { get; set; }
}
[ForeignKey]
en la propiedad de navegación
public class Profile
{
public int Id { get; set; }
public string FullName { get; set; }
public int UserId { get; set; }
[ForeignKey("UserId")] // Indica que UserId es la FK
public User User { get; set; }
}
Con Fluent API
O también podemos confgurarlo con Fluent API.
Clave foránea en Profile
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOne(u => u.Profile) // Un User tiene un Profile
.WithOne(p => p.User) // Un Profile pertenece a un User
.HasForeignKey<Profile>(p => p.UserId); // FK en Profile
}
Clave foránea en User
(menos común)
modelBuilder.Entity<Profile>()
.HasOne(p => p.User)
.WithOne(u => u.Profile)
.HasForeignKey<User>(u => u.ProfileId); // FK en User
Estructura de la base de datos generada
Al aplicar una migración, EF crea las tablas:
CREATE TABLE Users (
Id INT PRIMARY KEY IDENTITY,
Username NVARCHAR(100)
);
CREATE TABLE Profiles (
Id INT PRIMARY KEY IDENTITY,
FullName NVARCHAR(100),
UserId INT UNIQUE, -- ¡UNIQUE garantiza la relación 1:1!
FOREIGN KEY (UserId) REFERENCES Users(Id)
);
UserId
en Profiles
es UNIQUE, lo que evita que un mismo usuario tenga múltiples perfiles
Ejemplos practicos
Consulta de datos
Recuperamos un usuario con su perfil:
using (var context = new AppDbContext())
{
// Carga explícita (2 consultas)
var user = context.Users.First(u => u.Id == 1);
var profile = context.Profiles.First(p => p.UserId == user.Id);
// Carga ansiosa (1 consulta con JOIN)
var userWithProfile = context.Users
.Include(u => u.Profile) // JOIN automático
.First(u => u.Id == 1);
Console.WriteLine($"Usuario: {userWithProfile.Username}");
Console.WriteLine($"Nombre completo: {userWithProfile.Profile.FullName}");
}
Inserción de datos
Creamos un usuario con su perfil:
using (var context = new AppDbContext())
{
var newUser = new User { Username = "johndoe" };
var newProfile = new Profile { FullName = "John Doe" };
// Asignamos la relación
newUser.Profile = newProfile;
context.Users.Add(newUser);
context.SaveChanges(); // Guarda ambos registros en la BD
}