Ir al contenido principal

Introducción a Entity Framework

Continuando con nuestra saga .NET Framework, vamos a hablar un poco acerca de lo que es un ORM y en particular del ORM de Microsoft: Entity Framework. La traducción literal de Entity Framework al español sería algo así como "Marco de Trabajo de Entidades". Sin embargo, en el contexto más técnico, se prefiere mantener el nombre en inglés porque es un término propio y ampliamente reconocido en el mundo del desarrollo de software. Ahora sí vamos con la definición.

¿Qué es Entity Framework?

Bien, si partimos desde lo básico Entity Framework (EF) es un ORM (Object-Relational Mapper) que permite a los desarrolladores trabajar con bases de datos relacionales utilizando objetos de C# o VB en lugar de escribir código SQL directamente. Forma parte del ecosistema de .NET Framework y está diseñado para facilitar el acceso y manejo de datos hacia y desde la base de datos.

Sería como si el EF actuara como un "puente" entre la aplicación y la base de datos, generando automáticamente las consultas necesarias y convirtiendo los datos en objetos de C# que se pueden manipular fácilmente desde el código. 

Fecha de lanzamiento: agosto de 2008 con .NET Framework 3.5 SP1. Versión que fue oficialmente conocida como EF 1.0 y formaba parte del entorno de Visual Studio 2008.

Características iniciales:

  • Introdujo el modelo EDM (Entity Data Model), que permitía mapear clases de .NET a tablas de bases de datos.
  • Soportaba consultas LINQ to Entities para interactuar con los datos de forma declarativa.
En el 2010, se incluyó en el .NET Framework 4.0 y VS 2010 la versión Entity Framework 4.0 donde se liberaron algunas mejoras en las criticas recibidas por la comunidad.

Novedades principales:
  • Soporte para el enfoque Model First: Permitir diseñar el modelo y luego generar la base de datos.
  • Mejora en el soporte para consultas LINQ y modelos complejos.
  • Introducción del Lazy Loading.
Nota: No existió una versión EF 2.0 o 3.0 debido a la sincronización con el número de versión de .NET Framework.

En el 2011, se marca el lanzamientoe de Entity Framework 4.1, con el enfoque Code First donde los desarrolladores podían definir el modelo directamente en el código C# o VB sin depender de herramientas gráficas o XML. También incluyó el concepto de DbContext, simplificando la gestión de datos que ya hablaremos más delante.

Por último, cerrando una etapa se lanzó en el 2013 Entity Framework 6 junto con Visual Studio 2013, una de las versiones más estables y preferidas por la comunidad. Además de que se separó de .NET Framework para pasara a ser un proyecto de código abierto alojado en Github.

Características principales:
  • Asincronía con soporte para async/await.
  • Interceptores y comandos SQL personalizados.
  • Mejoras en el rendimiento y en las migraciones de bases de datos.
De ahí en más, el desarrollo de EF pasó a evolucionar junto con .NET Core, pero esta entrada no va a abarcar aún esa información.

Ahora bien, todo esto queda un poco "suelto" si no sabemos qué es un ORM..

Definamos ORM

Un ORM es una herramienta informática que facilita el trabajo con bases de datos al permitir que los desarrolladores interactúen con los datos mediante objetos en lugar de usar directamente consultas SQL o queries. Esto reduce el esfuerzo en la manipulación de dichos datos y la escritura de código. Por supuesto no todo son pros, también tiene sus contrapartes que ya hablaremos de ellas.

Nota: Bases de Datos Relacionales, esto quiere decir que no se puede trabajar con bases de datos NO-SQL.  No confundir con Entity Framework .NET Core.

Diferencias clave entre EF clásico y EF Core

Característica          .NET Framework         .NET Core (5/6/7)
Soporte para NoSQL        ❌ No ✅ Sí, con proveedores como Cosmos DB
Arquitectura extensible        ❌ Limitada ✅ Diseñada para extensibilidad
Multiplataforma        ❌ Solo Windows ✅ Windows, Linux, macOS
Rendimiento         Más lento Más rápido, gracias a optimizaciones
Soporte a Cosmos DB        ❌ No ✅ Proveedor oficial


Otros lenguajes o sistemas también tienen su propio ORM, como Django para Pyton o Springboot para Java.

Una de las ventajas de utilizar EF, es que mejora la productividad y abstrae gran parte del trabajo repetitivo asociado con el acceso a datos, permitiendo que los desarrolladores se concentren en la lógica del negocio. Además de que tiene gran compatibilidad con LINQ, permitiendo usar consultas para interactuar con la base de datos, lo que resulta más intuitivo para algunos desarrolladores que escribir SQL puro.

Desventajas de Usar Entity Framework

Aunque Entity Framework (EF) es una herramienta poderosa y facilita el desarrollo, también tiene ciertas limitaciones y desafíos que los programadores deben tener en cuenta al trabajar con él. 

  • No soporta transacciones lógicas.
  • Manejo de Estados y Cambios en las Entidades.

Entity Framework (EF) utiliza un modelo de seguimiento de estados (Change Tracking) para identificar y aplicar cambios en las entidades, que son las clases en el código que representan tablas en la base de datos. Este modelo permite que EF reconozca si una entidad necesita ser insertada, actualizada o eliminada en la base de datos. Sin embargo, este sistema puede complicar algunos escenarios, especialmente cuando una entidad cargada previamente se utiliza en un contexto diferente, lo que puede generar excepciones de estado, como InvalidOperationException.

En EF, una misma entidad puede tener múltiples instancias en diferentes estados, lo que puede dar lugar a problemas comunes. Por ejemplo, un objeto puede quedar en estado Detached (desacoplado), lo que significa que EF no lo está rastreando. Si intentas guardar una entidad en este estado sin volver a adjuntarla al contexto, se generarán errores.

Otro aspecto importante es que, aunque EF soporta validaciones básicas a través de atributos como [Required], [StringLength], y otros, estas validaciones son insuficientes para cubrir reglas de negocio más complejas. Por esta razón, es común utilizar una clase "validadora" personalizada para ejecutar validaciones adicionales de forma manual, permitiendo un control más detallado sobre la lógica de negocio.

Modelos de desarrollo

A nivel de diseño, Entity Framework nos ofrece tres enfoques principales para modelar datos, cada uno adecuado para diferentes escenarios:

Code First: Este enfoque permite definir el modelo de datos directamente en el código, utilizando clases y atributos de C#. Posteriormente, EF genera la base de datos a partir de estas definiciones.

Ventajas: Ideal para nuevos proyectos donde aún no existe una base de datos.
Control total sobre el diseño del modelo y es compatible con migraciones para manejar cambios en la DB.

Con Code First, Entity Framework traduce cada clase en una tabla de base de datos, asignando el nombre de la clase a la tabla. Las propiedades públicas de la clase se mapean a las columnas de la tabla, mientras que los métodos y constructores son ignorados.

Para configurar las entidades, se utilizan anotaciones, como:
  • [Key]: Define la clave primaria.
  • [DatabaseGenerated(Identity)]: Indica que el campo es una identidad numérica autoincremental.
  • [Required]: Marca una propiedad como obligatoria.
  • [MaxLength(xx)] y [MinLength(xx)]: Establecen restricciones de longitud.
  • [NotMapped]: Excluye una propiedad del mapeo a la base de datos.
Entre otras.

Database First: Con este modelo, se parte de una base de datos existente, y EF genera las clases necesarias para interactuar con ella.

Ventajas: Perfecto para proyectos donde la base de datos ya está diseñada.
Menor tiempo de configuración inicial y debo reconocer que funciona de manera sorprendentemente eficiente (tener presente que lo digo yo, que soy lo más reacio a la automatización de código).

Model First: Aquí se utiliza un diseñador para modelar las tablas y relaciones, generando luego tanto el código como la base de datos a partir del modelo visual. Son herramientas visuales como EDMX Designer, el creador de diagramas de SQL SSMS o Microsoft Visio por ejemplo.

Ventajas: Útil para quienes prefieren trabajar con estas herramientas. Entre las etapas automáticas de esta opción, se encuentra la de generar las entities y crear la base de datos desde el modelo.

Características

EF automatiza las operaciones básicas sobre las entidades, pero también permite usar procedimientos almacenados (SP, Stored Procedure) personalizados. Si bien se pueden invocar SP desde EF, un problema común es que no reconoce los valores de retorno. Para solucionarlo, se debe cargar un parámetro de tipo output para procesar correctamente estos valores.

En ocasiones, es necesario recurrir a ADO .NET de manera manual, como "puerta trasera" de EF, para manejar resultados específicos (por ejemplo, valores de retorno). Sin embargo, en estos casos, EF no detecta los cambios realizados directamente en la base de datos, por lo que se requiere actualizar manualmente el contexto de EF.

Cómo gestiona las consultas en la base de datos

EF ofrece diferentes estrategias de carga de datos:
  1. Eager Loading: Trae todos los datos relacionados cuando se carga un objeto contenedor (por ejemplo, una factura con sus líneas de artículos). Esto es útil cuando se necesita acceder a toda la información.
  2. Lazy Loading: Solo carga el objeto contenedor y sus dependencias se cargan bajo demanda. Es más eficiente en cuanto a uso de memoria, ya que no trae datos adicionales hasta que se solicitan explícitamente.
  3. Explicit Loading: Permite cargar explícitamente solo lo que se necesita, como ciertos campos de un objeto, sin traer dependencias completas.
Se utilizan varias clases esenciales para interactuar con la base de datos. Algunas de las principales son:
  • Database: Representa la base de datos relacional desde donde se consultan y hacia donde se persisten las operaciones de ABM (Alta, Baja, Modificación). En algunos casos, esta clase se utiliza para impactar la base de datos o crearla.

  • DbContext: Es la clase que gestiona el comportamiento de Entity Framework. Contiene el modelo de trabajo y se encarga de la interacción con la base de datos. A través de esta clase, se manejan los DbSet, que representan las tablas de la base de datos y las operaciones de acceso a datos.
Algunas de las funcionalidades del DBContext y que son imprescindibles conocerlas, son las siguientes:
  • SaveChanges(): Esta operación afecta directamente a la base de datos. Revisa todos los DbSet y dependiendo del estado de las entidades, realiza diferentes acciones:

  • Added: Si una entidad está marcada como añadida, se genera un INSERT en la base de datos. Después de ejecutar correctamente la operación, el estado de la entidad se cambia a Unchanged.
Ejemplo:
using (var context = new MyDbContext())
{
    // Crear una nueva instancia de la entidad
    var nuevoProducto = new Producto
    {
        Nombre = "Nuevo Producto",
        Precio = 100.0
    };

    // Agregar el producto al DbSet
    context.Productos.Add(nuevoProducto);

    // Guardar los cambios en la base de datos
    context.SaveChanges();

    // El estado del objeto cambia a Unchanged
    Console.WriteLine("Estado después de guardar: " + context.Entry(nuevoProducto).State);
}




Deleted:
Si una entidad está marcada como eliminada, se genera un DELETE. Si la operación es exitosa, el objeto se elimina del contexto.

Ejemplo:
using (var context = new MyDbContext())
{
    // Buscar un producto existente
    var productoExistente = context.Productos.FirstOrDefault(p => p.Nombre == "Producto a eliminar");

    if (productoExistente != null)
    {
        // Marcar el producto para eliminación
        context.Productos.Remove(productoExistente);

        // Guardar los cambios en la base de datos
        context.SaveChanges();

        // El objeto es removido del contexto
        Console.WriteLine("Estado después de eliminar: " + context.Entry(productoExistente).State);
    }
    else
    {
        Console.WriteLine("Producto no encontrado.");
    }
}




Modified: Si una entidad está modificada, se genera un UPDATE. Una vez realizado, el estado también se cambia a Unchanged.

Ejemplo:
using (var context = new MyDbContext())
{
    // Buscar un producto existente
    var productoExistente = context.Productos.FirstOrDefault(p => p.Nombre == "Producto a modificar");

    if (productoExistente != null)
    {
        // Modificar una propiedad de la entidad
        productoExistente.Precio = 120.0;

        // Guardar los cambios en la base de datos
        context.SaveChanges();

        // El estado del objeto cambia a Unchanged después de guardar
        Console.WriteLine("Estado después de modificar: " + context.Entry(productoExistente).State);
    }
    else
    {
        Console.WriteLine("Producto no encontrado.");
    }
}


El estado Unchanged indica que el objeto es una representación fiel de la base de datos, es decir, no ha sufrido cambios pendientes.
  • Entry.State: La operación Entry se usa para obtener el estado de una entidad en el DbContext. Si una entidad está en el estado Detached, significa que el objeto ha sido eliminado del contexto, y no tiene más seguimiento. Si se encuentra el objeto, se puede modificar su estado, lo que es útil si se quiere sacar un objeto de un DbSet.

    Por ejemplo, se puede cambiar manualmente el estado de una Entity a detached:

    contexto.Entry(articulo).State = System.Data.Entity.EntityState.Detached;


  • Database.ExecuteSqlCommand: Esta función permite ejecutar procedimientos almacenados (SP) con parámetros a través del contexto de la base de datos. Sin embargo, el contexto no está al tanto de lo que hace el SP, por lo que se debe forzar la carga de datos después de ejecutar el SP.



En resumen, los puntos que hemos tocado hoy son fundamentales para obtener un conocimiento introductorio sobre Entity Framework. Desde entender el modelo de seguimiento de estados hasta la definición de clases esenciales como DbContext y Database. No es menor mencionar que, si algún día aspirás a trabajar en grandes empresas tecnológicas, como Microsoft (porque soñar en grande es válido), contar con un manejo sólido de herramientas como Entity Framework puede marcar la diferencia. 

No solo te va a ayudar a desenvolverte mejor en tu día a día, sino también a responder con seguridad ante cualquier pregunta técnica que pueda surgir en una entrevista.

En la próxima entrada, vamos a explorar cómo crear un proyecto desde cero utilizando el enfoque Database First, acompañado de ejemplos prácticos de consultas LINQ para manejar datos de forma eficiente. Hasta entonces 👍

Nota especial: Mis agradecimientos al proyecto de Code Beautify que de forma gratuita nos permite generar fácilmente el formato de código y te lo escribe como html para tu página. Muy bien logrado, con varios templates de diseño para elegir.

Otros artículos

Principio de Responsabilidad Única (SRP) – SOLID explicado con ejemplos

Introducción a S.O.L.I.D En esta entrada, intentaremos abordar un nuevo ciclo de conceptos que tienen que ver sobre los principios SOLID , que son fundamentales en la programación orientada a objetos o POO . Estos principios fueron formulados, en principio, por Robert C. Martin , también conocido como " Uncle Bob ", con el objetivo de mejorar la mantenibilidad y escalabilidad del código de software. SOLID es un acrónimo que representa cinco principios de diseño: Single Responsibility Principle (SRP) – Principio de Responsabilidad Única Open/Closed Principle (OCP) – Principio de Abierto/Cerrado Liskov Substitution Principle (LSP ) – Principio de Sustitución de Liskov Interface Segregation Principle (ISP) – Principio de Segregación de Interfaces Dependency Inversion Principle (DIP) – Principio de Inversión de Dependencias Estos principios nos ayudan a crear software más ...

Open/Closed Principle (OCP) – SOLID explicado con ejemplos

Continuando con el repaso de los principios de S.O.L.I.D. que inició en el hilo anterior - si no lo viste hacé click acá  Principio de Responsabilidad Única (SRP) - vamos a ver el segundo en orden de aparición: Principio de Abierto/Cerrado (OCP por sus siglas en inglés). Definición Formal El principio OCP (Open/Closed Principle) establece que el código de una clase o un módulo debe estar abierto para la extensión, pero cerrado para la modificación. Esto significa que no se deben realizar cambios en el código existente cuando se requiere alterar alguna funcionalidad. En lugar de modificar el código existente, se debe crear una nueva implementación que extienda la funcionalidad. La única excepción a esto son los arreglos de bugs, donde está permitido modificar el código existente. Si se desea introducir una nueva funcionalidad, como la ordenación en un método existente, en lugar de modificar el código, se crearía una nueva implementación q...

Roadmap para Desarrolladores Backend en .NET en 2025

El mundo del desarrollo backend está en constante evolución, y mantenerse actualizado con las mejores prácticas y tecnologías es clave para seguir siendo competitivo en el mercado. Si estás buscando una guía clara y estructurada para mejorar tus habilidades en . NET backend , el sitio roadmap.sh ofrece un excelente punto de partida. Para esta entrada vamos a explorar lo siguiente: ¿Qué es roadmap.sh y por qué es relevante? El roadmap backend para .NET en 2025 Tecnologías y habilidades esenciales para backend a considerar ¿Qué es roadmap.sh y quién lo creó? roadmap.sh es una plataforma ampliamente reconocida dentro de la comunidad de desarrolladores. Fue creada por Kamran Ahmed, un Google Developer Expert y contribuidor en múltiples proyectos de código abierto. Desde su lanzamiento, la plataforma ha crecido exponencialmente, acumulando más de 300,000 estrellas en GitHub y una comunidad activa de...

Codewars

Como algunos ya saben, soy un gran entusiasta de las plataformas en línea dedicadas a la educación y al entrenamiento de habilidades, lo que podríamos llamar "gimnasios mentales". Hoy quiero presentarles, o tal vez recordarles, una de mis favoritas: Codewars, conocida también como " Guerras de Código " en español. Si aún no la conocían, los invito a explorarla. Y si ya la conocían, este es un buen momento para redescubrirla y sacarle más provecho. ¿Qué es Codewars? Codewars es una plataforma educativa en línea diseñada como un juego para entrenar habilitades de programación. fue fundada en noviembre de 2012 por Nathan Doctor y Jake Hoffner . La idea surgió durante una competencia de Startup Weekend ese mismo año, donde desarrollaron un prototipo que obtuvo el primer lugar. En la actualidad, Codewars es propiedad de Qualified , una empresa tecnológica que ofrece una plataforma para evaluar y entrenar habilidades en ingeniería de software. Con esta herramienta pod...

Principio de Sustitución de Liskov (LSP) – SOLID explicado con ejemplos

¿Qué es el Principio de Sustitución de Liskov? Imaginá que tenés un control remoto universal diseñado para funcionar con cualquier televisor. Si un nuevo modelo de TV no responde a los mismos comandos, el control deja de ser útil. El Principio de Sustitución de Liskov es como una garantía de que cualquier 'televisor' (o clase derivada) va a funcionar correctamente con el 'control remoto' (o clase base). El Principio de Sustitución de Liskov ( LSP ) es el tercer principio de SOLID , representado por la letra L y establece que: Los objetos de una clase derivada deben poder sustituir a los objetos de su clase base sin afectar el comportamiento correcto del programa. En otras palabras, si una clase hija hereda de una clase padre, cualquier instancia de la clase hija debería poder usarse en lugar de una instancia de la clase padre sin alterar la funcionalidad esperada. ¿Quién es Bárbara Liskov? Bárbara Liskov es una destacada científi...

Principio de Segregación de Interfaces (ISP) – SOLID explicado con ejemplos

El Principio de Segregación de Interfaces es otro de los principios SOLID y establece que: Una clase no debería verse obligada a depender de métodos que no utiliza .  En otras palabras, en lugar de crear una interfaz grande con muchos métodos, es mejor dividirla (segregar) en interfaces más pequeñas y específicas. Pero.. ¿Por qué? Obliga a implementar métodos innecesarios Si una clase solo necesita una "parte" de la funcionalidad de una interfaz, pero esta interfaz es muy grande, se va a ver obligada a implementar métodos que no usa. Esto es casi que inevitable si no buscamos la manera de separar mejor las responsabilidades. Imaginemos una interfaz IVehiculo que tiene los siguientes métodos: public interface IVehiculo { void Conducir () ; void Volar () ; void Navegar () ; } Si una clase Auto implementa esta interfaz, se ve obligado a definir métodos como Volar() o Navegar() , aunque un auto no vuela ni navega. public c...