Después de llevar un tiempo programando con EF, me gustaría compartir un par de trucos para intentar evitar round-trips innecesarios al servidor, y de paso optimizar en la medida de lo posible el impacto de las instrucciones UPDATE y DELETE en nuestra base de datos.
Para el post utilizaré una base de datos con una sola tabla llamada Clientes.
Imaginemos ahora unos típicos métodos para actualizar el nombre de un cliente y eliminar el registro.
static void EliminarCliente(int idCliente)
{
using (var context = new GestionEntities())
{
var cliente = context.Clientes.Find(idCliente);
context.Clientes.Remove(cliente);
context.SaveChanges();
}
}
static void ActualizarNombreCliente(int idCliente, string nombre)
{
using (var context = new GestionEntities())
{
var cliente = context.Clientes.Find(idCliente);
cliente.Nombre = nombre;
context.SaveChanges();
}
}
Aunque pueden parecer métodos válidos (de hecho lo son), tiene algún problema de ineficiencia que podríamos paliar reescribiendo un poco el código y sobre todo, conociendo como funciona EF.
El motivo por el que podría acusarse el anterior código como ineficiente es que, tanto para actualizar como para eliminar, primero se está realizando un acceso a datos para cargar la entidad completa (el método Find). Cierto es que Find nos devolvería la entidad desde el contexto si ya hubiera sido previamente cargada, pero para este caso (donde se crea y destruye el contexto en cada método) y para el propósito del post, pensaremos que Find accede a datos porque no encuentra la entidad en memoria.
Un punto a favor de EF y que no quisiera dejar de señalar es que la instrucción de actualización sólo incluye los campos que hemos modificado, es decir, ActualizarNombreCliente tira la siguiente sentencia SQL:
exec sp_executesql N'UPDATE [dbo].[Clientes]
SET [Nombre] = @0
WHERE ([IdCliente] = @1)
',N'@0 nvarchar(50),@1 int',@0=N'Sergio',@1=1
Sin embargo, como nosotros queremos que nuestra actualización y eliminación sean lo más óptimas posibles, terminaríamos escribiendo este otro código:
static void EliminarCliente(int idCliente)
{
var cliente = new Cliente { IdCliente = idCliente };
using (var context = new GestionEntities())
{
context.Clientes.Attach(cliente);
context.Clientes.Remove(cliente);
context.SaveChanges();
}
}
static void ActualizarNombreCliente(int idCliente, string nombre)
{
var cliente = new Cliente { IdCliente = idCliente };
using (var context = new GestionEntities())
{
context.Clientes.Attach(cliente);
cliente.Nombre = nombre;
context.SaveChanges();
}
}
Con este código ahora sólo ejecutamos 2 sentencias en su más mínima expresión y sin previo acceso a datos para cargar la entidad en memoria.
exec sp_executesql N'UPDATE [dbo].[Clientes]
SET [Nombre] = @0
WHERE ([IdCliente] = @1)
',N'@0 nvarchar(50),@1 int',@0=N'Sergio',@1=1
exec sp_executesql N'DELETE [dbo].[Clientes]
WHERE ([IdCliente] = @0)',N'@0 int',@0=1
De DELETE no hay mucho más que hablar, sólo la precaución (también aplicable a UPDATE) de incluir en nuestra entidad todas las propiedades que son clave principal (Entity Key) o que están incluidas como parte de la concurrencia pesimista (Concurrency Mode = Fixed).
Sin embargo para UPDATE sí hay que comentar algo más al respecto. Si en nuestra tabla Clientes hubiera algún campo que no admitiera nulos y que no fuera parte de nuestra entidad (por ejemplo Descripcion o Pais fueran no nulables), la llamada a SaveChanges fallaría por la validación de EF y lanzaría un error del tipo System.Data.Entity.Validation.DbEntityValidationException.
No sé si habrá muchas más formas de solucionar esto, pero por mi parte yo lo hago deshabilitando la validación antes de la llamada a SaveChanges.
Claro está que si el contexto no es de usar y tirar (que será lo más normal), deberíamos después restaurar el valor de la propiedad ValidationOnSaveEnabled a su valor original.
Dicho esto, el código de actualizar quedaría así:
static void ActualizarNombreCliente(int idCliente, string nombre)
{
var cliente = new Cliente { IdCliente = idCliente };
using (var context = new GestionEntities())
{
context.Clientes.Attach(cliente);
cliente.Nombre = nombre;
context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
}
}
Un saludo!