DNM+ Online
dotnetmania 2.0
Entity Framework en aplicaciones con arquitectura N-Tier y N-Layer
ADO.NET Entity Framework, que es parte de .NET Framework a partir de la versión 3.5 SP1, es una plataforma de acceso a datos que hace transparente para el desarrollador el sistema gestor de bases de datos al que ataca (SQL Server, Oracle, etc.). El objetivo de este artículo es muy práctico: responder a la pregunta ¿cómo podemos utilizar Entity Framework en aplicaciones con arquitectura N-Tier o N-Layer?

Antes de nada, vamos a definir qué entendemos por arquitecturas N-Tier y N-Layer. Una aplicación N-Tier es una aplicación basada en tres o más niveles físicos, como nivel de cliente/presentación, nivel de servidor de componentes de negocio y nivel de datos (servidores de bases de datos, etc.). Actualmente también hacemos uso de servicios Web (o, mejor incluso, específicamente de servicios WCF) para comunicar el nivel cliente/presentación con el nivel de servidor de componentes.
La figura 1 muestra un diagrama típico de arquitectura N-Tier. Es un diagrama que me costó bastante encontrar, porque es de una presentación de Microsoft de aproximadamente 1998, en la época de Windows NT 4.0 y MTS (Microsoft Transaction Server), y poco después Windows DNA y COM+. Sin embargo, aunque las tecnologías internas han cambiado mucho en estos años, la arquitectura básica de estos niveles físicos (tiers) es la misma J. Como se puede observar, en una arquitectura N-Tier las aplicaciones cliente (bien basadas en Windows Forms, WPF o incluso Silverlight) necesitan acceder remotamente al nivel de servidor de aplicaciones haciendo uso, por ejemplo, de servicios WCF. Relativo a esto, en mi opinión, las aplicaciones RIA (Rich Internet Applications) son un tipo especial de aplicaciones N-Tier.
Otro tema diferente es que probablemente habremos diseñado nuestras aplicaciones como aplicaciones N-Layer (N-Capas), es decir, con varias capas lógicas en las que implementamos diferentes tipos de tareas. Por ejemplo, podemos tener capas como DAL (Data Access Layer), la capa BLL (Business Logic Layer), la capa BFLL (Business Façade Logic Layer), la capa de servicios WCF y una o varias capas de presentación (interfaz de usuario), dependiendo del patrón que usemos, como MVC (Modelo-Vista-Controlador), MVP (Modelo-Vista-Presentador), MV-VM (Modelo-Vista-Vista Modelo), etc. Por supuesto, dentro de dicha arquitectura N-Layer, usted podrá suponer que Entity Framework (en adelante EF) encaja inicialmente en la capa DAL (Data Access Layer), donde se accede a los orígenes de datos, así como también haciendo uso de entidades desconectadas de EF, las cuales se pasarán/comunicarán de unas capas de la arquitectura a otras. La figura 2 muestra un diagrama típico de arquitectura N-Layer. Y en la figura 3 vemos situadas en las capas las diferentes tecnologías de Microsoft, donde resalto la utilización de EF en la capa de acceso a datos en la parte inferior de la arquitectura. Por cierto, quiero resaltar que no todas las aplicaciones N- Layer tienen que ser aplicaciones N-Tier (niveles físicos, con nivel de presentación/cliente remoto), aunque todas las aplicaciones N-Tier sí deberían haber sido diseñadas en modo N-Layer (capas lógicas diferenciadas). Quiero decir con esto que, en algunos casos, en cuantos menos niveles físicos tengamos dividido nuestro modelo, mejor rendimiento tendremos; por ejemplo, en una aplicación ASP.NET tendremos la mayoría de las veces la capa de presentación y la de componentes de negocio en el mismo servidor, como parte de un único nivel físico. Muchos niveles físicos pueden ser buenos para conseguir una óptima escalabilidad, sin embargo no favorecen el rendimiento puro debido a razones de latencia en las llamadas remotas. En cualquier caso, recuerde que N-Layer tiene que ver con capas lógicas. Si volvemos a una arquitectura N-Tier (niveles físicos), como dije antes, necesitamos de mecanismos de acceso remoto (como WCF) para poder comunicar el nivel de cliente (por ejemplo una aplicación WPF o Silverlight) con el nivel de servidor de aplicación. Posteriormente, cuando se realizan consultas desde el servidor de componentes de negocio a la base de datos para obtener datos, como por ejemplo un pedido, lo obtendremos como una entidad de EF. Entonces lo desconectamos (detach) de su contexto de EF, se serializa automáticamente por WCF y se manda como entidad desconectada por la red (gracias a los servicios WCF) hasta llegar al nivel de presentación (aplicación y máquina cliente). Así pues, la mayoría de las aplicaciones empresariales usan patrones de arquitectura que necesita de una fachada sin estados para su lógica de negocio (como aplicaciones N-Tier y N-Layer). Tendremos pues escenarios con orientación a servicios, con servicios Web básicos o servicios WCF, aplicaciones ASP.NET, aplicaciones tradicionales de escritorio Windows (cliente rico basado en Windows Forms o WPF) o incluso aplicaciones RIA con Silverlight que consumen servicios WCF, etc. En la mayoría de estos casos se trabaja con una filosofía de acceso a datos basada en "entidades de datos desconectadas". Esto es bueno porque se consiguen aplicaciones altamente escalables, pues los datos de la base de datos (en las tablas reales) no sufren bloqueos mientras los usuarios están trabajando en el nivel cliente (por ejemplo, un formulario WPF o Silverlight). Por ejemplo, en dicha aplicación cliente el usuario podría estar trabajando, cambiando datos de forma "desconectada", y podría incluso irse diez minutos a tomarse un café mientras el formulario está abierto con los datos, volver, y finalmente actualizar los datos en el sistema central (servidor de aplicaciones). En ese momento, lo que pasará es que al final de una cadena de llamadas (pasando por servicios Web, componentes de negocio, etc.), una clase de la capa DAL, haciendo uso de "LINQ to Entity Framework", actualizará dichos datos en la base de datos. ¿Correcto? Bueno, si hacemos simplemente esto, sería una actualización de tipo "El último gana", o lo que yo llamo "Actualización muy optimista" J. Me explico. En muchas aplicaciones en las que no tengamos requisitos muy exigentes/complejos a nivel de actualizaciones, el sistema que hemos comentado puede ser suficiente. Pero en cambio, podemos tener otras aplicaciones donde el usuario necesita saber si los datos que está usando en la aplicación cliente han cambiado en la base de datos mientras él estaba trabajando (o tomando un café) con el formulario abierto y antes de actualizar los datos. En ese caso, deberemos hacer uso de "actualizaciones con concurrencia optimista", manejando las posibles "excepciones de concurrencia optimista". En este artículo nos referiremos al primer caso; vamos a ver cómo realizar actualizaciones de datos del tipo "El último gana" en aplicaciones N-Tier (con aplicación cliente remota y datos desconectados). Para cubrir el otro escenario, más avanzado, es posible que escriba un próximo artículo; en cualquier caso, al final de éste tenéis una referencia a mi blog [1], donde ese escenario lo tengo también contemplado. Bien, pues a nivel de tecnología, estamos hablando de: •  Uso de Entity Framework y LINQ to Entities en nuestra capa DAL. •  WCF como tecnología de comunicaciones remotas y orientación a servicios. •  Cualquier tecnología .NET remota (WPF, Windows Forms, OBA o incluso Silverlight) para el nivel de presentación (interfaz de usuario con ejecución en máquina cliente).

Opción base: actualización de datos en aplicaciones N-Tier haciendo uso de EF y entidades desconectadas Para entender completamente lo que quiero decir, inicialmente vamos a explicar la forma más sencilla, es decir, cómo implementar actualizaciones simples de datos en aplicaciones N-Tier haciendo uso de Entity Framework. Así pues, éste es el caso llamado "El último gana" o "Actualización muy optimista". En este caso, no es necesario gestionar las posibles situaciones de concurrencia optimista. Primeramente, tendremos una clase de acceso a datos, CustomerDal, donde haremos uso de EF y LINQ to Entities para realizar la consulta inicial a la base de datos; por ejemplo, digamos que vamos a obtener los datos de un cliente y devolver dichos datos al nivel cliente/presentación mediante un servicio WCF. Generalmente, se tiene una o más clases de negocio (BLL) entre el servicio WCF y la clase DAL (invocaciones intermedias); en aras de simplificar el artículo, aquí la obviamos. El código de dicha clase DAL sería similar al que muestra el listado 1.
Probablemente, una muy buena opción sería usar tipos genéricos en las clases de acceso a datos, para así no tener que repetir código parecido para distintas entidades; eso también lo obvio, por simplificar y hacer hincapié solo en la parte relativa a la actualización de datos desconectados en aplicaciones N-Tier. Si conoce algo de LINQ to Entities, estará de acuerdo conmigo en que el código de arriba es muy sencillo. Lo importante a destacar de este código es que estamos devolviendo un objeto de entidad desconectada, pasándolo a las capas lógicas superiores (DAB ->BLL -> Capa servicios WCF -> Presentación). Eso significa que de cara a EF estaremos tratando los objetos ObjectContext como "contextos de vida corta". Básicamente, cuando se devuelva dicha entidad al servidor con los datos modificados, tendremos que hacer una re-conexión (re-attach) de dicha entidad mediante un nuevo objeto de contexto, puesto que la entidad fue modificada en un estado desconectado. Este escenario es muy típico siempre que usemos fachadas sin estado (como hace la mayoría de las aplicaciones N-Tier y N-Layer). Volviendo a nuestro código, después podríamos tener un servicio WCF encargado de serializar y devolver dicha entidad desconectada. El código del contrato de la operación de servicio WCF para recuperar un cliente se muestra en el listado 2. Hay que destacar que los métodos de servicios Web deben ser lo más ligeros posibles (simplemente son un interfaz de entrada externa); por eso, nuestro método del servicio Web está simplemente invocando a una clase de negocio (CustomerBll). La clase BLL es donde situaríamos código de lógica de negocio, seguridad, transacciones, etc.; pero en este caso es tan simple que consiste en un único método que llama a la clase DAL y devuelve lo mismo (lo que normalmente se llama en inglés Man in the middle). Por esta razón obvio su presentación. Después de eso, la aplicación cliente consumirá dicho servicio WCF y mostrará los datos de la entidad (los datos de un cliente, en este caso), por ejemplo en un formulario WPF. Recordad ahora que la aplicación cliente está trabajando con un objeto de entidad que está desconectado, por lo que no tenemos ningún ObjectStateManager de EF registrando los cambios. Veremos después cómo afecta esto. Así pues, una vez que los datos están visibles en el formulario, el usuario puede modificar los datos hasta que decida actualizarlos, pulsando, por ejemplo un botón "Grabar". En este momento, la aplicación cliente llamará a otro método de WCF con un código similar al del listado 3. El método WCF llama a la clase de negocio y ésta a su vez a la clase de acceso a datos, que es donde tendremos el código interesante de LINQ to Entities y donde vamos a ver cómo utilizar EF para actualizar la entidad modificada que nos viene "desconectada" desde la aplicación cliente. El listado 4 muestra el método de actualización de la capa DAL (recuerde que en este caso no tenemos que tratar las situaciones de concurrencia optimista). Para poder aplicar cualquier actualización en la base de datos usando context.SaveChanges(), primero necesitamos tener la entidad ligada a ese contexto. Si simplemente utilizáramos una llamada al método predefinido context.Attach(customer), la cosa no funcionaría, porque la entidad customer es desconocida en el nuevo contexto que tenemos: este nuevo objeto de contexto no sabe nada de esta entidad, es decir, no tiene registrado ningún cambio que se haya producido sobre ella.
Por esta razón es por la que he creado un método extensor propio para ObjectContext llamado AttachUpdated. Este método permite enlazar/conectar objetos de entidad que vienen, desconectados, de la aplicación cliente remota. Su código se presenta en el listado 5. Bueno, con este código ya hacemos cosas más especiales. En primer lugar, en nuestro código tenemos que determinar qué datos nuevos hay en nuestra entidad, es decir, cuáles serían los cambios si lo comparamos con los datos actuales en la base de datos. Para saber eso, necesitamos hacer una consulta a la base de datos y obtener los datos actuales. Lo hacemos mediante el método context.TryGetObjectByKey, y eso es lo que establecemos como valores originales de esa entidad para el contexto actual. Entonces, llamamos al método context.ApplyPropertyChanges, proporcionando nuestra nueva entidad (con datos cambiados). Lo que hace EF es comparar los "datos originales" (datos actuales en la base de datos) con nuestros datos en la entidad que viene del cliente, y entonces actualiza en el contexto todas los cambios de propiedades/campos de nuestra entidad. Después de eso, ya podremos llamar a context.SaveChanges en el método principal. Ahí, EF sí detectará los cambios con respecto a la base de datos y será posible actualizar la base de datos real (SQL Server, por ejemplo) con los nuevos datos. Quiero destacar que después de llamar a ApplyPropertyChanges también estoy llamando a ApplyReferencePropertyChanges. Para este caso sencillo, no es realmente necesario, pero explico qué es. Este método es también otro método extensor que aplica los cambios de propiedades de mi entidad, pero en todas las entidades referenciadas dentro de nuestro modelo EF (Customer podría estar relacionado con Order, etc.), siempre y cuando estas otras entidades estén presentes en el grafo de datos del contexto actual. Este método es sumamente importante; en caso contrario, tendríamos algo que solo valdría para entidades completamente aisladas. El código de ApplyReferencePropertyChanges se presenta en el listado 6. Con esto, ya habríamos acabado. Pero aún mostraremos otra alternativa para implementar la estrategia "El último en actualizar gana". El resultado final es similar, pero sin embargo nos ahorraríamos hacer una consulta a la base de datos para conocer los datos actuales (no ejecutaríamos el método context.TryGetObjectByKey). En cambio, lo que hacemos es "decirle" al contexto que "todas las propiedades de mi entidad han cambiado, simplemente porque yo lo digo". Después de eso, simplemente llamaremos de igual forma a context.SaveChanges, y en este caso se actualizarán todas las propiedades/campos de la entidad. Simplemente "porque yo lo he dicho" J. He implementado esta técnica por medio de otro método extensor, SetAllModified, en este caso asociado a la entidad en lugar del contexto (listado 7). Usando este método extensor, el código para la actualización de un cliente quedaría tan simple como se ve en el listado 8. Esta sería la otra opción que tenemos para actualizaciones simples de tipo "El último gana". La ventaja, como ya hemos mencionado, estriba en que no necesita consultar a la base de datos para conocer los datos originales. La desventaja: que si tenemos muchas propiedades/campos en la entidad, actualizaremos siempre todas las propiedades del registro, aun cuando el usuario hubiera cambiado solo un campo o unos pocos. Dependiendo de cada caso concreto, podemos utilizar un camino u otro. ¡Fenómeno! Pues eso parece sencillo, "y vivimos en un mundo siempre feliz". ¿Seguro? Bueno, pues depende, porque como dije al principio del artículo, esta forma de actualizar los datos no cubre escenarios en los que queramos gestionar actualizaciones con concurrencia optimista. Retomando nuestro caso de uso, supongamos que mientras el usuario original estaba tomándose su café, otro usuario utiliza la aplicación y actualiza datos del mismo cliente en la base de datos. ¿Qué pasará cuando el primer usuario vuelva y pulse el botón "Grabar"? Pues lo dicho, el usuario original ni se enterará de va a sobrescribir modificaciones que ha hecho un segundo usuario, y de hecho, machacará dichos datos ("El último gana"). En muchos casos, esto sería incluso lo deseable; pero en otros puede ser inaceptable. Todo depende del caso concreto.
Como ya hemos mencionado, para un análisis de este segundo tipo de situación, en la que es necesario gestionar las actualizaciones con concurrencia optimista y sus excepciones, podéis consultar mi blog [1], donde explico en detalle una implementación de dicho escenario.

blog comments powered by Disqus
autor
referencias