Ir al contenido principal

Introducción a Entity Framework Core

En el desarrollo de aplicaciones modernas, trabajar con bases de datos es una tarea fundamental. Hasta ahora hemos visto cómo manejar archivos de texto y JSON, pero las aplicaciones empresariales requieren sistemas de almacenamiento más robustos y escalables. Entity Framework Core (EF Core) es un mapeador objeto-relacional (ORM) moderno y ligero que nos permite trabajar con bases de datos utilizando objetos de C#, eliminando la necesidad de escribir gran parte del código de acceso a datos.

EF Core actúa como un puente entre el mundo orientado a objetos de C# y el mundo relacional de las bases de datos. Esto significa que podemos trabajar con tablas, filas y columnas utilizando clases, objetos y propiedades, lo que hace que el desarrollo sea más intuitivo y productivo. Además, EF Core se integra perfectamente con LINQ, permitiendo escribir consultas de base de datos utilizando sintaxis familiar de C#.

En este artículo aprenderemos los conceptos fundamentales de Entity Framework Core, cómo configurarlo en un proyecto, crear nuestro primer modelo de datos y realizar operaciones básicas de conexión con la base de datos.

¿Qué es Entity Framework Core?

Entity Framework Core es un framework de acceso a datos de código abierto desarrollado por Microsoft. Es la evolución moderna del Entity Framework tradicional, diseñado para ser multiplataforma, ligero y de alto rendimiento.

Características principales de EF Core

Característica Descripción
Multiplataforma Funciona en Windows, Linux, macOS
Proveedores múltiples Soporta SQL Server, SQLite, PostgreSQL, MySQL y más
Code First Permite definir el modelo usando clases de C#
Database First Puede generar modelos desde bases de datos existentes
Migraciones Sistema automático para evolucionar el esquema de la base de datos
LINQ integrado Consultas fuertemente tipadas usando sintaxis familiar
Seguimiento de cambios Detección automática de modificaciones en entidades
Carga perezosa Carga datos relacionados solo cuando se necesitan

Conceptos fundamentales

Entidad (Entity): Una clase de C# que representa una tabla en la base de datos. Cada instancia de la entidad corresponde a una fila en la tabla.

Contexto (DbContext): La clase principal que coordina la funcionalidad de EF Core. Representa una sesión con la base de datos y permite consultar y guardar datos.

Modelo (Model): El conjunto de clases de entidad y la configuración que define la estructura de la base de datos.

Proveedor de base de datos: Un plugin que permite a EF Core comunicarse con un tipo específico de base de datos.

Instalación y configuración inicial

Para comenzar a trabajar con Entity Framework Core, necesitamos instalar los paquetes NuGet correspondientes. En este tutorial utilizaremos SQLite por su simplicidad, ya que no requiere instalación de servidor.

Instalación de paquetes NuGet

Desde la consola del administrador de paquetes de Visual Studio o la terminal, ejecutamos:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools

Estos paquetes nos proporcionan:

  • Microsoft.EntityFrameworkCore: El núcleo de EF Core
  • Microsoft.EntityFrameworkCore.Sqlite: El proveedor para bases de datos SQLite
  • Microsoft.EntityFrameworkCore.Tools: Herramientas de línea de comandos para migraciones

Configuración básica del proyecto

Vamos a crear un ejemplo práctico con un sistema simple de gestión de libros. Primero definimos nuestras entidades:

using System.ComponentModel.DataAnnotations;

namespace BibliotecaApp.Models
{
    // Entidad que representa un libro en la base de datos
    public class Libro
    {
        public int Id { get; set; }
        
        [Required]
        [MaxLength(200)]
        public string Titulo { get; set; }
        
        [Required]
        [MaxLength(100)]
        public string Autor { get; set; }
        
        [Range(1, int.MaxValue)]
        public int Paginas { get; set; }
        
        public DateTime FechaPublicacion { get; set; }
        
        public decimal Precio { get; set; }
        
        public bool Disponible { get; set; } = true;
    }
}

Creación del contexto de base de datos

El DbContext es el corazón de cualquier aplicación EF Core. Esta clase coordina todas las operaciones con la base de datos:

using Microsoft.EntityFrameworkCore;
using BibliotecaApp.Models;

namespace BibliotecaApp.Data
{
    // Contexto que representa nuestra sesión con la base de datos
    public class BibliotecaContext : DbContext
    {
        // Constructor que recibe las opciones de configuración
        public BibliotecaContext(DbContextOptions<BibliotecaContext> options) 
            : base(options)
        {
        }
        
        // DbSet representa una tabla en la base de datos
        public DbSet<Libro> Libros { get; set; }
        
        // Método para configurar el modelo de datos
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            
            // Configuración adicional del modelo
            modelBuilder.Entity<Libro>(entity =>
            {
                entity.HasKey(e => e.Id);
                entity.Property(e => e.Precio)
                      .HasPrecision(10, 2); // 10 dígitos, 2 decimales
            });
        }
    }
}

Configuración de la cadena de conexión

En aplicaciones de consola, configuramos la conexión directamente en el programa principal:

using Microsoft.EntityFrameworkCore;
using BibliotecaApp.Data;
using BibliotecaApp.Models;

class Program
{
    static async Task Main(string[] args)
    {
        // Configuración del contexto con SQLite
        var optionsBuilder = new DbContextOptionsBuilder<BibliotecaContext>();
        optionsBuilder.UseSqlite("Data Source=biblioteca.db");
        
        // Creación del contexto
        using var context = new BibliotecaContext(optionsBuilder.Options);
        
        // Asegurar que la base de datos existe
        await context.Database.EnsureCreatedAsync();
        
        Console.WriteLine("Base de datos creada y configurada correctamente.");
        
        // Ejemplo de uso básico
        await EjemploBasico(context);
    }
    
    static async Task EjemploBasico(BibliotecaContext context)
    {
        // Agregar un libro de ejemplo
        var libro = new Libro
        {
            Titulo = "El Quijote",
            Autor = "Miguel de Cervantes",
            Paginas = 863,
            FechaPublicacion = new DateTime(1605, 1, 16),
            Precio = 25.99m,
            Disponible = true
        };
        
        context.Libros.Add(libro);
        await context.SaveChangesAsync();
        
        Console.WriteLine($"Libro '{libro.Titulo}' agregado con ID: {libro.Id}");
        
        // Consultar todos los libros
        var libros = await context.Libros.ToListAsync();
        Console.WriteLine($"\nTotal de libros en la biblioteca: {libros.Count}");
        
        foreach (var l in libros)
        {
            Console.WriteLine($"- {l.Titulo} por {l.Autor} ({l.FechaPublicacion.Year})");
        }
    }
}

Convenciones y configuraciones

Entity Framework Core utiliza un conjunto de convenciones para mapear las clases de C# a las tablas de la base de datos. Sin embargo, también podemos personalizar este comportamiento.

Convenciones predeterminadas

Convención Comportamiento
Nombre de tabla Nombre de la propiedad DbSet (ej: Libros)
Clave primaria Propiedad llamada Id o [ClaseName]Id
Tipo de columna Inferido del tipo de propiedad de C#
Longitud de cadena Máxima permitida por la base de datos
Valores nulos Permitidos para tipos nullables

Configuración mediante atributos

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

[Table("LibrosCatalogo")] // Nombre personalizado de tabla
public class Libro
{
    [Key] // Clave primaria explícita
    public int LibroId { get; set; }
    
    [Required] // Campo obligatorio
    [MaxLength(200)] // Longitud máxima
    public string Titulo { get; set; }
    
    [Column(TypeName = "varchar(100)")] // Tipo específico de columna
    public string Autor { get; set; }
    
    [Range(1, 10000)] // Validación de rango
    public int Paginas { get; set; }
    
    [Column("FechaPublicado")] // Nombre personalizado de columna
    public DateTime FechaPublicacion { get; set; }
    
    [Precision(10, 2)] // Precisión para decimales
    public decimal Precio { get; set; }
}

Configuración mediante Fluent API

La Fluent API ofrece más flexibilidad para configurar el modelo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // Configuración de la entidad Libro
    modelBuilder.Entity<Libro>(entity =>
    {
        // Configuración de tabla
        entity.ToTable("LibrosCatalogo");
        
        // Configuración de clave primaria
        entity.HasKey(e => e.Id);
        
        // Configuración de propiedades
        entity.Property(e => e.Titulo)
              .IsRequired()
              .HasMaxLength(200);
              
        entity.Property(e => e.Autor)
              .IsRequired()
              .HasMaxLength(100)
              .HasColumnType("varchar(100)");
              
        entity.Property(e => e.Precio)
              .HasPrecision(10, 2);
              
        // Configuración de valores predeterminados
        entity.Property(e => e.Disponible)
              .HasDefaultValue(true);
              
        // Configuración de índices
        entity.HasIndex(e => e.Titulo)
              .IsUnique();
    });
}

Operaciones básicas con el contexto

Una vez configurado nuestro contexto, podemos realizar operaciones básicas de base de datos:

public class BibliotecaService
{
    private readonly BibliotecaContext _context;
    
    public BibliotecaService(BibliotecaContext context)
    {
        _context = context;
    }
    
    // Verificar conexión a la base de datos
    public async Task<bool> VerificarConexionAsync()
    {
        try
        {
            await _context.Database.CanConnectAsync();
            Console.WriteLine("Conexión exitosa a la base de datos.");
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error de conexión: {ex.Message}");
            return false;
        }
    }
    
    // Obtener información del esquema
    public async Task MostrarInformacionEsquema()
    {
        var tablas = _context.Model.GetEntityTypes()
            .Select(t => t.GetTableName())
            .ToList();
            
        Console.WriteLine("Tablas en la base de datos:");
        foreach (var tabla in tablas)
        {
            Console.WriteLine($"- {tabla}");
        }
        
        // Verificar si la base de datos fue creada
        var existe = await _context.Database.CanConnectAsync();
        Console.WriteLine($"Estado de la base de datos: {(existe ? "Existe" : "No existe")}");
    }
    
    // Ejemplo de transacción básica
    public async Task EjemploTransaccionAsync()
    {
        using var transaction = await _context.Database.BeginTransactionAsync();
        
        try
        {
            // Múltiples operaciones en una transacción
            var libro1 = new Libro 
            { 
                Titulo = "1984", 
                Autor = "George Orwell", 
                Paginas = 328,
                FechaPublicacion = new DateTime(1949, 6, 8),
                Precio = 18.99m
            };
            
            var libro2 = new Libro 
            { 
                Titulo = "Cien años de soledad", 
                Autor = "Gabriel García Márquez", 
                Paginas = 417,
                FechaPublicacion = new DateTime(1967, 5, 30),
                Precio = 22.50m
            };
            
            _context.Libros.AddRange(libro1, libro2);
            await _context.SaveChangesAsync();
            
            // Confirmar transacción
            await transaction.CommitAsync();
            Console.WriteLine("Transacción completada exitosamente.");
        }
        catch (Exception ex)
        {
            // Deshacer cambios en caso de error
            await transaction.RollbackAsync();
            Console.WriteLine($"Error en transacción: {ex.Message}");
            throw;
        }
    }
}

Ejemplo práctico completo

Vamos a crear un ejemplo completo que demuestre el uso básico de Entity Framework Core:

using Microsoft.EntityFrameworkCore;
using BibliotecaApp.Data;
using BibliotecaApp.Models;

class Program
{
    static async Task Main(string[] args)
    {
        Console.WriteLine("=== Introducción a Entity Framework Core ===\n");
        
        // Configurar el contexto
        var optionsBuilder = new DbContextOptionsBuilder<BibliotecaContext>();
        optionsBuilder.UseSqlite("Data Source=biblioteca_ejemplo.db");
        
        using var context = new BibliotecaContext(optionsBuilder.Options);
        var servicio = new BibliotecaService(context);
        
        try
        {
            // 1. Crear la base de datos
            Console.WriteLine("1. Creando base de datos...");
            await context.Database.EnsureCreatedAsync();
            
            // 2. Verificar conexión
            Console.WriteLine("2. Verificando conexión...");
            var conectado = await servicio.VerificarConexionAsync();
            
            if (conectado)
            {
                // 3. Mostrar información del esquema
                Console.WriteLine("\n3. Información del esquema:");
                await servicio.MostrarInformacionEsquema();
                
                // 4. Ejemplo de transacción
                Console.WriteLine("\n4. Ejecutando transacción de ejemplo...");
                await servicio.EjemploTransaccionAsync();
                
                // 5. Verificar datos insertados
                Console.WriteLine("\n5. Libros en la base de datos:");
                var libros = await context.Libros.ToListAsync();
                
                if (libros.Any())
                {
                    foreach (var libro in libros)
                    {
                        Console.WriteLine($"ID: {libro.Id}, Título: {libro.Titulo}");
                        Console.WriteLine($"   Autor: {libro.Autor}");
                        Console.WriteLine($"   Páginas: {libro.Paginas}, Precio: ${libro.Precio:F2}");
                        Console.WriteLine($"   Publicado: {libro.FechaPublicacion:dd/MM/yyyy}");
                        Console.WriteLine($"   Disponible: {(libro.Disponible ? "Sí" : "No")}\n");
                    }
                }
                else
                {
                    Console.WriteLine("No se encontraron libros en la base de datos.");
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
        
        Console.WriteLine("\nPresiona cualquier tecla para salir...");
        Console.ReadKey();
    }
}

Resumen

Entity Framework Core es un mapeador objeto-relacional potente y flexible que simplifica significativamente el trabajo con bases de datos en aplicaciones .NET. En este artículo hemos cubierto los conceptos fundamentales: qué es EF Core y sus características principales, cómo instalarlo y configurarlo, la creación de entidades y contextos, y las operaciones básicas de conexión con la base de datos.

Los conceptos clave que hemos aprendido incluyen la definición de entidades como clases de C#, la creación del DbContext como coordinador central de operaciones, la configuración del modelo mediante atributos o Fluent API, y la realización de operaciones básicas como verificación de conexión y transacciones. En el siguiente artículo profundizaremos en las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) que nos permitirán manipular datos de manera efectiva utilizando Entity Framework Core.