Actualizaciones de múltiples entidades con Entity Framework- EF Fetch Updates


[Updated Sample 22-07-2008 ]


Nuevas mejoras


Soporte de llamada a métdos


Soporte de llamada a métodos de miembros de entidades


Soporte de acceso a variables dentro de las expressiones


[Updated Sample 17-07-2008 ]


English Version 


Amigo mio, antes de empezar a leer este post asegúrese de que dispone de algo de tiempo, ya que puede que sea algo extenso J.


Seguramente muchos de los que habéis jugueteado con Entity Framework os habréis preguntado cómo actualizar o borrar un conjunto de entidades, es decir, como indicarle a EF que deseamos actualizar todos los clientes de Madrid a unos determinados valores. De hecho, una vez conocido que esta tarea no se puede realizar, algunas personas, después de llevarse las manos a la cabeza, se han puesto a investigar cuanto de malo es la realización de múltiples consultas de actualizaciones y/o borrados, aunque se realicen en batch, con respecto a una sentencia update o delete con un filtro específico y han posteado sobre estas diferencias en los foros de MSDN sobre Entity Framework. A lo largo de este post pretendo mostrar una posible implementación para realizar esta tarea, para ello me he servido de algunas ideas de Alex James, miembro del grupo de producto de EF, en el que hacía algunas consideraciones sobre cómo se podría realizar este trabajo.


Empecemos por el principio:


Acerca de la metadata


Uno de los puntos más importantes que necesitamos resolver para realizar esta tarea es como averiguar las relaciones de nuestras entidades dentro del modelo conceptual de EDM , Entity Data Model, con las tablas de la base de datos subyacente con la que estemos trabajando. Aunque esto pudiera parecer que lo deberíamos de tener resuelto de una forma sencilla, así lo pensaba yo cuando ya hace tiempo me puse a investigar esto, para nada es algo trivial. MetadataWorkspace, una de las clases de los contextos de EF, que nos permite trabajar con la metadata de los modelos conceptuales no nos permite obtener información del espacio CSSpace, espacio de mapeo entre objetos y almacenamiento. Llegamos por lo tanto al primer inconveniente…. Después de lanzar varias preguntas al grupo de producto, pobre Danny Simmons, tengo claro que no hay, ni habrá en esta primera versión, ninguna forma pública de acceder a la información del mapeo de las entidades con las tablas. Alex James, en uno de sus post hace una referencia sobre las consultas de los EntitySet presentes en los modelos conceptuales, en este post comenta que dada una entidad sencilla dentro de un modelo conceptual, si revisamos la consulta SELECT de esta podríamos ver una estructura de sentencia en forma SELECT [NombreColumna] AS [NombrePropiedad]…. FROM [TABLA]… Al conocer esto se abre una vía interesante para averiguar cómo asociar el nombre de una propiedad con el nombre de una columna dada con lo que tendríamos resuelto el problema de conocer la metadata. Para mi desgracia, después de la ilusión que esto supuso, pude comprobar que si modificábamos el nombre de una propiedad de una entidad, la consulta generada no reflejaba este cambio y seguía enviando [NombreColumna] as [NombrePropiedad] dónde [NombrePropiedad] era el valor antiguo de la misma.


Llegados a este punto, en el que me daba por medio perdido, decidí que una buena vía era atacar directamente a la especificación de la metadata, accediendo a los recursos incrustados que el modelador de entidades generaba, esta es la opción por defecto a partir de la primera beta del SP1 de VS 2008. Con esto lo único que tenía que hacer era interpretar un XML, el MSL que usaban también ellos dentro de la infraestructura de EF. El siguiente trozo de XML representa parte del contenido de uno de estos archivos incrustados, que por supuesto puede ser consultado de una forma muy sencilla usando Linq To XML, en el que vemos que la entidad eBook está asociada con una tabla eBook y los mapeos de las distintas propiedades de la entidad.


<EntitySetMapping Name=«eBook«>


<EntityTypeMapping TypeName=«IsTypeOf(MamazonModel.eBook)«>


<MappingFragment StoreEntitySet=«eBook«>


<ScalarProperty Name=«idProducto« ColumnName=«idProducto« />


<ScalarProperty Name=«ISBN_13« ColumnName=«ISBN_13« />


<ScalarProperty Name=«ISBN_10« ColumnName=«ISBN_10« />


<ScalarProperty Name=«Edicion« ColumnName=«Edicion« />


<ScalarProperty Name=«Paginas« ColumnName=«Paginas« />


<ScalarProperty Name=«Indice« ColumnName=«Indice« />


<ScalarProperty Name=«LenguajeLibro« ColumnName=«Lenguaje« />


<ScalarProperty Name=«CapituloEjemplo« ColumnName=«CapituloEjemplo« />


</MappingFragment>


</EntityTypeMapping>


</EntitySetMapping>


La expresividad de las consultas


Bien, ya tenemos la información relativa al mapeo de nuestras entidades conceptuales con las columnas y tablas de la base de datos. Ahora nos surge la pregunta sobre como implementar un mecanismo para que el desarrollador pueda especificar las distintas partes de una sentencia UPDATE, el SET y el WHERE, y de una sentencia DELETE, el WHERE. Ya que estamos trabajando con EF y Linq To Entities no queremos perder la capacidad de trabajar de una forma fuertemente tipada, nuestro objetivo es poder especificar estas partes usando elementos del lenguaje, no mediante el uso de simples secuencias de caracteres entrecomilladas. Con la llegada de Linq en .NET 3.5 tenemos la posibilidad de usar árboles de expresiones, los mismos que internamente usan EF y L2SQL por ejemplo. Gracias a estos árboles de expresiones podemos darle a los programadores una forma de escribir consultas de una forma tipada que posteriormente nosotros podremos evaluar revisando el árbol creado, le recomiendo encarecidamente desde aquí el libro de mi gran amigo y mejor persona Octavio Hernandez, C#3.0 y LinQ de Krasis Press.


Para la parte SET de nuestra sentencia UPDATE disponemos del tipo de expresión MemberInitExpression, la cual nos permitirá especificar cómo se inicia un determinado tipo, las siguientes líneas de código muestra un ejemplo de este tipo de expresión:


    Expression<Func<Customer, Customer>> memberExpression = c => new Customer() { FirstName=«Unai» };


Puede fijarse, como el uso de este tipo de expresión nos permite de una forma más o menos sencilla especificar el SET de nuestra sentencia, en este caso lo que querríamos decir sería algo similar a UPDATE [TABLA] SET FirstName=’Unai’.


Para la parte WHERE de las sentencias UPDATE y DELETE podríamos usar el tipo de expresión BinaryExpression, tal cual la siguiente:


Expression<Func<Producto, bool>> binary = p => p.idProducto > 21 && p.TitProduct != «Titulo»;


Al igual que con la expresión anterior es bastante intuitivo ver que lo que se quiere especificar es algo como WHERE idProducto > 21 AND TitProducto <> ‘Titulo’


Llegados a este punto ya conocemos como queremos darle al programador la forma de especificar las distintas partes de nuestras sentencias, con el uso de árboles de expresiones como los anteriores, por lo tanto toca el trabajo de crearse un analizador de estas expresiones, algo que podemos hacer fácil o muy complicado dependiendo de todas las posibilidades que estemos dispuestos a soportar. En el siguiente ejemplo de código podemos ver un ‘analizador’ muy sencillo y bastante limitado pero útil para que entienda realmente el trabajo que es necesario realizar.


static void ParseExpression(Expression<Func<Customer, Customer>> expression)


{


Expression<Func<Customer, Customer>> inner = expression as Expression<Func<Customer, Customer>>;


if (inner != null)


{


MemberInitExpression initExpression = inner.Body as MemberInitExpression;


if (initExpression != null)


{


var result = (from m in initExpression.Bindings.OfType<MemberAssignment>()


select new { PropertyName = m.Member.Name, Value = m.Expression.ToString() }).ToList();


StringBuilder sb = new StringBuilder();


sb.Append(«SET «);


sb.Append(result[0].PropertyName);


sb.Append(«=»);


sb.Append(result[0].Value);


for (int i = 1; i < result.Count; i++)


{


sb.Append(«,»);


sb.Append(result[i].PropertyName);


sb.Append(«=»);


sb.Append(result[i].Value);


}


Console.WriteLine(sb.ToString());


}


else


Console.WriteLine(«La expression no es una expresión valida»);


}


else


Console.WriteLine(«La expression no es una expresión valida»);


}


De entre los muchos trabajos que le quedarían a un analizador para este tipo de árboles serían como resolver asignaciones como new Customer (){Fecha= DateTime.Now} o el uso de variables dentro de las asignaciones, o llamadas a métodos…….


Cerrando el círculo… o abriéndolo!!


Ya sabemos cómo podemos acceder a toda la información de la metadata, de una forma poco elegante pero efectiva J y sabemos cómo proporcionar a los programadores una forma de especificar las operaciones de DML que queremos realizar, aunque el trabajo del analizador de los árboles sea realmente algo complicado si queremos que soporte muchas de las casuísiticas comunes… por lo que solamente queda ejecutar los comandos obtenidos de lo anterior. Para ello podemos crear métodos extensores de ObjectQuery<T> como UpdateAll y DeleteAll, al tener acceso a ObjectQuery también tenemos acceso a la cadena de conexión, la cual especifica el proveedor específico de conexión y la cadena de conexión contra el almacenamiento, así que otro problema resuelto.


<add name=»MamazonEntities» connectionString=»metadata=res://*/MamazonModel.csdl|res://*/MamazonModel.ssdl|res://*/MamazonModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;Data Source=.SQLEXPRESS;Initial Catalog=Mamazon;Integrated Security=True;MultipleActiveResultSets=True&quot;» providerName=»System.Data.EntityClient» />


Junto a este post os adjunto un ejemplo de todo lo comentado anteriomente, que podéis descargar desde aquí, en esta implementación los analizadores de árboles de expresiones son capaces de analizar algunos elementos extra como el uso de expresiones con asignaciones al estilo de las siguientes:


using (MamazonEntities entities = new MamazonEntities())


{


entities.Producto.UpdateAll(p => new Producto() { FechaLanzamientoProduco = new DateTime(2008,2,2), TitProduct = «Unai» }, p => p.idProducto > 21 && p.TitProduct != «hola»);


entities.Producto.UpdateAll(p => new Producto() { FechaLanzamientoProduco = DateTime.Now }, p => p.idProducto > 20);


entities.Producto.DeleteAll(p => p.idProducto > 20);


Console.ReadLine();


}


Sample entity Framework

2 comentarios sobre “Actualizaciones de múltiples entidades con Entity Framework- EF Fetch Updates”

  1. Excelente artículo!

    He de decir que las posibilidades que se abren con esta técnica son espectaculares, siempre teniendo cuidado de no reimplementar cosas que ya estén incluidas en LINQ… 😉

    Me lo guardo en favoritos, y este fin de semana le pegaré un buen repaso a fondo 🙂

    Saludos!

Responder a aruiz Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *