Entity Framework: To Track or Not To Track

[forTips] Entity Framework

Hoy toca hablar de Entity Framework y dar a conocer un truquillo que no es muy conocido. Para aquellos que desarrolláis en .NET no será nada nuevo y para los que usáis otras tecnologías quizás hayáis oído hablar de él.

¿Que es Entity Framework?

Entity Framework (EF), es un ORM (Object Relational Mapping) desarrollado por Microsoft; lo cual significa que tiene compatibilidad 100% con SQL Server, aunque también es posible usarlo con otros SGBD como Oracle, MySQL,… mediante un conector o data provider de terceros.

Básicamente, esto lo que nos aporta a nivel de desarrollo es realizar consultas contra la base de datos sin escribir una sola sentencia SQL. Nosotros operamos con nuestro modelo de objetos y EF se encarga de realizar la transformación a consulta y realizar las transformaciones necesarias.

Cabe decir que no es el único ORM, hay otros como NHibernate, Massive, … que realizan el mismo trabajo. Cada uno tiene sus peculiaridades, ventajas y desventajas obviamente.

Vamos a ver un ejemplo

Como ya comentamos en su día en .NET hay una sintaxis especifica para la realización de consultas: LINQ.

Gracias a LINQ se facilita el acceso a datos a nivel de desarollo tal que podemos tener algo así para obtener una lista de personas mayores de edad de nuestra base de datos.

var database = new MyDbContext();
var personas = database.Personas.Where(p=> p.Edad>= 18).ToList();

Como veis es tan sencillo como instanciar la base de datos, usar la tabla Personas filtrar con la condición que queramos y obtenemos así la lista (filas de base de datos) que cumplen con los criterios específicados.

Tracking

Y ahora vamos al meollo de la cuestión. Para realizar una correcta gestión de nuestros datos, evitar realizar múltiples instancias del mismo objeto y demás optimizaciones para no generar un uso abusivo de memoria; EF por defecto realiza un tracking de las entidades que obtiene de base de datos.

Para entendernos, cuando obtiene una entidad persona de la base de datos, convierte ese objeto de base de datos a un objeto de nuestro modelo .NET con las conversiones que ello implica. Entonces esa entidad persona pasa a estar cacheada internamente por EF de tal modo que si creamos un objeto en memoria con la misma clave primaria e intentamos insertarlo al contexto (base de datos) nos dará un error puesto que son objetos distintos (referencias distintas) pero con misma clave en base de datos.

Caso 1: Esto funcionaria sin problemas
var database = new MyDbContext();
var persona1 = database.Personas.First(p=> p.Id == 1);
persona1.Edad = 50;

/* do some stuff */

database.Personas.AddOrUpdate(persona1);
database.SaveChanges();
Caso 2: Mientras que lo siguiente daría error
var database = new MyDbContext();
var persona1 = database.Personas.First(p=> p.Id == 1);
persona1.Edad = 50;

/* do some stuff */

var persona2 = new Persona();
persona2.Id = 1;
persona2.Edad = 34;
//La siguiente linea nos dará un error dado que no podemos
//asociar una entidad existente al modelo con el mismo Id
database.Personas.Attach(persona2);

Cabe aclarar que el caso 2 fallaría de todos modos cuando persistieramos los datos a la base de datos debido a que habría una clave primaria duplicada, siempre y cuando tengamos una base de datos consistente.
Tampoco es un caso normal asociar entidades al modelo, se suele usar directamente los métodos Add()AddOrUpdate()Remove(). De todos modos para el propósito que nos atañe nos sirve de ejemplo.

Caso 2 Corregido
var database = new MyDbContext();
var persona1 = database.Personas.AsNoTracking().First(p=> p.Id == 1);
persona1.Edad = 50;

/* do some stuff */

var persona2 = new Persona();
persona2.Id = 1;
persona2.Edad = 34;
database.Personas.Attach(persona2); //Ya no nos da ninguna excepción

Como veis la inclusión de la extensión AsNoTracking() nos ha sido de ayuda para leer datos sin que sean cacheados por EF y no se de cuenta de que existen en nuestro contexto.

¿Y esto para que nos sirve?

El uso de esta extensión tiene un par de usos interesantes, donde podemos sacarle provecho.

Clonado de entidades
var database = new MyDbContext();
var persona = database.Personas.AsNoTracking().First(p => p.Id == 1);
database.Personas.Add(persona);
database.SaveChanges(); //Al persistir los datos crea una persona identica pero con otro Id autogenerado
Lectura de entidades en tiempo real
var database = new MyDbContext();
var persona = database.Personas.AsNoTracking().First(p => p.Id == 1);
//En estos momentos otra aplicación realiza cambios en base de datos
//En la siguiente instrucción obtenemos los datos con esos cambios y no la copia cacheada
var persona2 = database.Personas.AsNoTracking().First(p => p.Id == 1);

Conclusiones

Si tenemos una aplicación que debe convivir con otras, sería bueno analizar los puntos donde necesitamos datos en tiempo real para que no parezca que las aplicaciones no se comunican bien y hacer uso de AsNoTracking(). También nos puede resultar interesante para realizar clones de nuestros mantenimientos a golpe de ratón, una función que el usuario agradecerá cuando debe crear muchos datos massivamente.