entity-framework-fluent-api

Fluent API en Entity Framework

  • 4 min

Fluent API en Entity Framework Core es una forma de configurar el modelo de datos usando código.

Es un enfoque imperativo para configurar Entity Framework, es decir, que la configuración se realiza a través de funciones (programáticamente).

Configuración básica con Fluent API

La configuración Fluent API se realiza en el archivo que contiene el DbContext, en el método OnModelCreating, que es invocado por EF Core cuando se inicia el contexto de la base de datos.

public class ApplicationDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configuraciones Fluent API aquí
    }
}

Principales configuraciones con Fluent API

Igual que hicimos con Data Annotations, vamos a ver cómo realizar con Fluent API algunas de las configuraciones más frecuentes que necesitar.

Configuración de tablas y columnas

La configuración del nombre de tabla permite personalizar cómo se nombran las tablas en la base de datos, independientemente del nombre de la clase en el modelo.

Usando el método ToTable, puedes especificar un nombre personalizado para la tabla

modelBuilder.Entity<Estudiante>().ToTable("Tbl_Estudiantes");

La personalización de nombres de columnas te permite asignar nombres específicos a las columnas en tu base de datos que pueden diferir de los nombres de las propiedades en tu modelo.

modelBuilder.Entity<Product>()
    .Property(p => p.Name)
    .HasColumnName("NombreProducto")
    .HasColumnType("varchar(100)");

Esta configuración permite excluir propiedades específicas del modelo de entidad para que no se mapeen a columnas en la base de datos.

Es útil para propiedades calculadas o temporales que existen solo en la capa de aplicación y no necesitan persistencia.

modelBuilder.Entity<Product>().Ignore(p => p.TemporaryData);

Configuración de claves

La definición explícita de claves primarias es esencial cuando Entity Framework no puede inferirlas automáticamente o cuando necesitas personalizar su comportamiento

modelBuilder.Entity<Product>().HasKey(p => p.ProductId);

Las claves compuestas permiten identificar registros únicos utilizando múltiples columnas en combinación.

modelBuilder.Entity<OrderDetail>()
    .HasKey(od => new { od.OrderId, od.ProductId });

La configuración de autoincremento especifica cómo se generan automáticamente los valores para las claves primarias.

modelBuilder.Entity<Product>()
    .Property(p => p.Id)
    .ValueGeneratedOnAdd();

Configuración de relaciones

La relación uno a uno establece una conexión directa entre dos entidades donde cada instancia de una entidad se relaciona exactamente con una instancia de otra entidad.

modelBuilder.Entity<Employee>()
    .HasOne(e => e.Profile)          // Un empleado tiene un perfil
    .WithOne(p => p.Employee)        // Un perfil pertenece a un empleado
    .HasForeignKey<EmployeeProfile>(p => p.EmployeeId);

Las relaciones uno a muchos son las más comunes en bases de datos relacionales, donde una entidad puede estar relacionada con múltiples instancias de otra entidad.

modelBuilder.Entity<Product>()
    .HasOne(p => p.Category)          // Un producto tiene una categoría
    .WithMany(c => c.Products)        // Una categoría tiene muchos productos
    .HasForeignKey(p => p.CategoryId); // Clave foránea

Las relaciones muchos a muchos permiten que múltiples instancias de una entidad se relacionen con múltiples instancias de otra entidad.

modelBuilder.Entity<Student>()
    .HasMany(s => s.Courses)         // Un estudiante tiene muchos cursos
    .WithMany(c => c.Students)       // Un curso tiene muchos estudiantes
    .UsingEntity<Dictionary<string, object>>(
        "EstudianteCurso",           // Nombre de la tabla de unión
        j => j.HasOne<Course>().WithMany().HasForeignKey("CursoId"),
        j => j.HasOne<Student>().WithMany().HasForeignKey("EstudianteId")
    );

Restricciones y validaciones

La configuración de campos requeridos es fundamental para garantizar que los datos esenciales siempre estén presentes. Esta restricción se traduce a nivel de base de datos en columnas NOT NULL, evitando registros incompletos.

modelBuilder.Entity<Product>()
    .Property(p => p.Name)
    .IsRequired();

El control de la longitud de los campos de texto ayuda a optimizar el almacenamiento de datos y a validar entradas. Esta configuración previene problemas de truncamiento al definir límites claros para los datos de texto.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Estudiante>()
        .Property(e => e.Nombre)
        .HasMaxLength(100);

    modelBuilder.Entity<Estudiante>()
        .Property(e => e.CorreoElectronico)
        .HasMaxLength(150);
}

Los valores predeterminados proporcionan una forma de asegurar que las columnas siempre tengan un valor cuando no se especifica uno explícitamente durante la inserción. Son útiles para campos como fechas de creación, estados iniciales o banderas.

modelBuilder.Entity<Product>()
    .Property(p => p.CreatedAt)
    .HasDefaultValueSql("GETDATE()");

Los índices mejoran significativamente el rendimiento de las consultas al optimizar la búsqueda de datos.

modelBuilder.Entity<Product>()
    .HasIndex(p => p.Name)
    .IsUnique(); // Índice único

Ejemplo completo

Veamos cómo quedaría todo lo que hemos visto, aplicado a un ejemplo sencillo de una Biblioteca, con Libros, Autores, y Lectores

public class LibraryContext : DbContext
{
    public DbSet<Book> Books { get; set; }
    public DbSet<Author> Authors { get; set; }
    public DbSet<Member> Members { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Configurar Book
        modelBuilder.Entity<Book>()
            .ToTable("Libros")
            .HasKey(b => b.BookId);

        modelBuilder.Entity<Book>()
            .Property(b => b.Title)
            .IsRequired()
            .HasMaxLength(200);

        modelBuilder.Entity<Book>()
            .HasOne(b => b.Author)
            .WithMany(a => a.Books)
            .HasForeignKey(b => b.AuthorId);

        // Configurar Author
        modelBuilder.Entity<Author>()
            .Property(a => a.Name)
            .IsRequired()
            .HasMaxLength(100);

        // Configurar Member
        modelBuilder.Entity<Member>()
            .HasIndex(m => m.Email)
            .IsUnique();
    }
}

La clase LibraryContext configura:

  • Tablas: Books (como “Libros”), Authors, Members.
  • Reglas:
    • Title (libro) es obligatorio y máximo 200 caracteres.
    • Name (autor) es obligatorio.
    • Email (miembro) es único.
  • Relaciones: Un autor (Author) puede tener muchos libros (Books).