Pequeños detalles sobre Entity Framework (2ª parte)

23. July 2011 13:27 by Oscar.SS in Desarrollo Empresarial  //  Tags:   //   Comments (0)

En el primer artículo de esta serie vimos una serie de comportamientos muy elementales a la hora de trabajar con el contexto de Entity Framework. Ahora vamos a complicar la cosa un poco. En realidad, como sucede muchas veces, será más complicado para el que lo intenta explicar que para el que lo entiende  ;-)

Estado del contexto entre capas

Supongamos por un momento que estamos trabajando en una aplicación N-Layer. Una de las funcionalidades de nuestra aplicación es permitir que el usuario modifique los datos de un Cliente existente (ver Modelo EF del ejemplo anterior) y luego poder salvar los cambios efectuados.

Como una imagen vale más que mil palabras, veamos nuestra arquitectura (imagen de la derecha) como siempre simplificada para el ejemplo que nos ocupa.

La pregunta que debemos hacernos es que ocurre con nuestra entidad Cliente cuando el objeto es serializado por el servico WCF para ser enviado a la capa de interfaz de usuario. Lo que sucede es que el objeto es deserializado al ser recibido en la UI para su consumo, lógicamente ya no se encuentra adjuntado al contexto de EF.

Es decir, del servicio WCF para arriba el contexto EF es totalmente ciego. Por lo tanto, aunque nosotros estemos trabajando con una entidad de tipo Cliente en la UI, el contexto EF no tiene conocimiento de esta entidad y por lo tanto no se entera si realizamos cambios sobre ella.

Nota: Que nadie se lleve a engaño, aunque no viajáramos a través de un servicio WCF, el problema seguiría existiendo. Es decir, cuando recibamos los campos modificados por el usuario en la UI naturalmente trataremos de instanciar un nuevo objeto Cliente y volcar estos cambios, pero antes deberemos adjuntar nuestra entidad Cliente al contexto de EF.

 

Volviendo a nuestra arquitectura donde si teníamos un servicio WCF, vamos acompañar a nuestra entidad Cliente en su viaje de ida desde la base de datos, pasando por todas las capas hasta la UI, y vuelta a la base de datos para guardar los cambios efectuados.

 

Paso 1. Accedemos a la base de datos

Desde nuestra capa de negocio, y obviando repositorios y otras capas intermedias, accedemos a la base de datos para obtener la entidad Cliente que se quiere modificar.

        public static Cliente ObtenerClientePorId(int id)
        {
            var dataContex 
= new BooksDBModelContainer();

            
var cliente dataContex.Clientes.FirstOrDefault(c => c.ClienteId == id);

            return 
cliente;
        
}

 Si incluimos un punto de interrupción al ejecutar el código podemos comprobar la propiedad EntityState de nuestra entidad Cliente.

 

 

El estado de la entidad está establecido a Unchanged lo que significa que nuestra entedidad Cliente está en el contexto de EF y no se han realizado modificaciones sobre ella desde que se adjunto al contexto.

 

Paso 2. El servicio WCF recibe la entidad

En el código del nuestro servicio WCF realizamos la llamada a la capa de negocio para obtener la entidad Cliente.

        public Cliente ObtenerCliente(int id)
        {
            var cliente 
ServicioClientes.ObtenerClientePorId(id);

            return 
cliente;
        
}

Veamos que valor tiene la propiedad StateEntity de la entidad Cliente en este momento.

Como podemos comprobar su estado no ha sido modificado y sigue adjuntada al contexto de EF. Esto es porque aún el objeto no ha sido serializado para enviarse a la capa de interfaz.

 

Paso 3. En la UI el objeto es deserializado

Cuando el Cliente llega a nuestra capa UI es deserializado para su consumo.

            using (var wcf = new ServicioWcf.ServicioWcfClient())
            {
                var cliente 
wcf.ObtenerCliente(id);

                return 
View(cliente);
            
}

Como hemos comentado anteriormente, la entidad devuelta por el servicio WCF ya no pertenece a nuestro contexto EF. Veamos.

Como se puede apreciar la propiedad EntityState de ha establecido a Detached. Por lo tanto a partir de aquí, nuestro contexto EF, desconoce que hacemos con esta entidad Cliente.

 

Paso 4. En la UI se recibe el Cliente modificado

Después de que la interfaz de usuario se ha renderizado y el usuario a modificado, por ejemplo la dirección de nuestro Cliente, el objeto es "armado" con los nuevos valores para enviarlo de nuevo a la base de datos.

            using (var wcf = new ServicioWcf.ServicioWcfClient())
            {
                var clienteModificado 
wcf.ModificarCliente(cliente);

                return 
View("Detalles", clienteModificado);
            
}

Como de momento no hemos adjuntado ni actualizado las modificaciones del Cliente es de suponer, y creerme que así es, que su estado sigue siendo Detached cuando es enviado al servicio WCF.

 

Paso 5. El servicio WCF recibe las modificaciones

En realidad el servicio WCF recibe una entidad Cliente con la modificaciones realizadas pero sigue sin estar adjuntado al contexto de EF.

        public Cliente ModificarCliente(Cliente cliente)
        {
            var clienteModificado 
ServicioClientes.ActualizarCliente(cliente);

            return 
clienteModificado;
        
}

Comprobemos con que estado llega el Cliente al servicio WCF antes de ser serializado para enviarlo a la capa de negocio.

 

Paso 6. La lógica de negocio recibe las modificaciones

Lógicamente si desde el servicio WCF enviamos una entidad Cliente con la modificaciones y no pertenecía al contexto de EF, a nuestra capa de negocio llegará una entidad Cliente con el mismo estado. Es aquí donde debemos hacer nosotros el trabajo de añadirlo al contexto de EF.

        public static Cliente ActualizarCliente(Cliente cliente)
        {
            var dataContex 
= new BooksDBModelContainer();

            
//Creamos un nuevo objeto Cliente que almacenará los cambios efectuados en UI
            
var clienteModificado = new Cliente
                                        {
                                            ClienteId 
cliente.ClienteId  // clienteModificado tiene el estado Detached
                                        
};

            
//Adjuntamos el nuevo Cliente al contexto EF
            
dataContex.Clientes.Attach(clienteModificado);  // clienteModificado tiene el estado Unchanged

            //Le asignamos los cambios de los valores obtenidos desde la UI
            
dataContex.Clientes.ApplyCurrentValues(cliente);

            
//Actualizamos el registro en la base de datos
            
dataContex.SaveChanges();

            return 
clienteModificado;
        
}

Bueno, como he incluido comentarios en el código me voy ahorrar algunas explicaciones, simplemente vamos a comprobar que estado tiene la instancia clienteModificado justo antes de enviar los cambios a la base de datos.

Después de volcar los cambios, de la entidad Cliente que llegaba desde el servico WCF, a la nueva instancia clienteModificado, Entity Framework detecta estos cambios y marca esta entidad como Modified. Todas la entidades marcadas como modificadas serán actualizadas en la base de datos al ejecutar el método SaveChanges().

Pequeños detalles sobre Entity Framework (1ª parte)

23. July 2011 00:37 by Oscar.SS in Desarrollo Empresarial  //  Tags:   //   Comments (0)

En esta serie de artículos vamos a ver algunos compartamientos que hay que tener en cuenta a la hora de trabajar con Entity Framework. En su mayoría son elementos muy sencillos que la mayoría ya conoceréis, pero tampoco está de más recordarlo.

Para los ejemplos vamos a utilizar una pequeña base de datos, creada para la ocasión, que simula de manera muy simplificada la gestión de venta de libros en una tienda o librería. Este sería nuestro modelo Entity Framework con el que vamos a trabjar.

 

 

El contexto trabaja desconectado

Una de las primera cosas que hay que tener en cuenta cuando trabajamos con una instacia de nuestro modelo de Entity Framework, es que principalmente trabaja desconectado de la base de datos. Es decir, en realidad es una representación en memoria del esquema de tablas de nuestra base de datos. En este sentido recuerda un poco a los DataSet.

Por lo tanto cuando escribimos el código siguiente:

            //Instanciamos el contexto de datos
            
var dataContex = new BooksDBModelContainer();

            
//Creamos un cliente
            
var miCliente = new Cliente
                              {
                                  Nombre 
"Antonio Sánchez",
                                  Direccion 
"Santo Dominto 5",
                                  Telefono 
"999999999"
                              
};

            
//Añadimos el cliente al contexto
            
dataContex.Clientes.AddObject(miCliente);

 Hemos creado un Cliente y lo hemos añadido al contexto, o lo que es lo mismo, lo hemos añadido en memoria. Pero aún no se ha abierto una conexión con la base de datos y tampoco se ha insertado un registro en nuestra tabla Clientes. Sin embargo cuando ejecutamos la siguiente instrucción:


            //Conexión a base de datos e insercción en la tabla Clientes
            
dataContex.SaveChanges();

Esto es importante tenerlo en cuenta si por ejemplo queremos añadir varias entidades seguidas, como en un bucle. Solo al salir de este debemos ejecutar la linea enterior. De lo contrario estaríamos abriendo y cerrando conexiones con la base de datos en cada paso del bucle, con la consecuente penalización de rendimiento.

 

Sincronización automática de identificadores

Al ejecutar el método SaveChanges(), como acabamos de ver, se abre una conexión con base de datos y se realiza la inserción de nuestro Cliente. Pero también ocurren otras cosas, digamos, en el otro sentido.

Si inmediatamente después de ejecutar la linea anterior intentamos en nuestro código acceder al ID de nuestro Cliente nos daremos cuenta que este tiene el valor que se ha generado la base de datos.

 

 

Por lo tanto cuando se ejecuta el método SaveChanges() no solo se produce un volcado hacia la base de datos, también se produce una actualización o sincronización hacia nuestras entidades en memoria de todos aquellos campos auto generados en la base de datos.

 

Detección automática de cambios en el contexto

Para explicar este comportamiento vamos ahora a insertar un Ejemplar en nuestra base de datos. Pero como un ejemplar tiene que estar ligado a un Cliente (que lo compra) y a un Libro en concreto, antes debemos insertar estas entidades.

            var dataContex = new BooksDBModelContainer();

            
//Añadimos un cliente
            
var maria = new Cliente
                              {
                                  Nombre 
"María Fernadez",
                                  Direccion 
"Castellana 223",
                                  Telefono 
"444444444"
                              
};

            
dataContex.Clientes.AddObject(maria);
            
            
//Añadimos un libro
            
var arteDefensa = new Libro
                                  {
                                      Titulo 
"El arte de la defensa en Ajedrez",
                                      Isbn 
"84-8019-878-8",
                                      Editorial 
"Paidrotibo"
                                  
};

            
dataContex.Libros.AddObject(arteDefensa);

            
//Insertamos el cliente y el libro en la base de datos
            
dataContex.SaveChanges();

            
//Añadimos un ejemplar
            
var unEjemplar = new Ejemplar
                                 {
                                     Precio 
24,
                                     Encuadernacion 
"Blanda",
                                     Edicion 
"Bolsillo",
                                     ClienteId 
maria.ClienteId,
                                     LibroId 
arteDefensa.LibroId
                                 }
;

            
dataContex.Ejemplares.AddObject(unEjemplar);
            
dataContex.SaveChanges();

Como se puede apreciar en el código para insertar el Ejemplar y ligarlo a las entidades Cliente y Libro hemos establecido sus propiedades ClienteId y LibroId haciendo uso de la sincronización que comentabamos en el punto anterior. Pero también podemos codificarlo de otra forma.

            //Añadimos un ejemplar
            
var unEjemplar = new Ejemplar
                                 {
                                     Precio 
24,
                                     Encuadernacion 
"Blanda",
                                     Edicion 
"Bolsillo",
                                     Cliente 
maria,
                                     Libro 
arteDefensa
                                 }
;

            
dataContex.SaveChanges();

Fijaros en la diferencia. En lugar de utilizar las propiedades ClienteId y LibroId del Ejemplar, hemos establecido las propiedades de navegación Cliente y Libro con las respectivas referencias a los objetos "maria" y "arteDefensa".

Pero hay una diferencia más importante. Al codificarlo de esta forma no hemos tenido que añadir al contexto la entidad Ejemplar antes de ejecutar el método SaveChanges(). Entity Framework detecta que se ha producido un cambio en el contexto actual y lo añade por nosotros. O lo que es lo mismo, detecta que las entidades Cliente y Libro se han ligado al Ejemplar, lo que representa un cambio en el contexto y actualiza este por nosotros.

 

En el siguiente artículo veremos como se comporta el contexto EF cuando viaje entre las diferentes capas de una aplicación.

Aportando un poco de coherencia en los controladores

8. July 2011 14:39 by Oscar.SS in Desarrollo .NET, Desarrollo Web  //  Tags: ,   //   Comments (2)

Cuando formamos parte de un proyecto en el que participan varios desarrolladores, puede suceder si no se tiene una fuerte política de nombrado (desde luego algo poco usual) en las acciones y los métodos, que cada desarrollador elija el nombre que mejor le parezca en ese momento.

De esta forma en un mismo proyecto o solución, podemos encontrarnos para especificar acciones que conceptual y funcionalmente representan lo mismo sobre diferentes entidades, nombres como GetCustomerById,  LoadProductById, DeleteProduct, UserDelete. De esta forma tendríamos URLs de navegación de esta forma:

/Customer/GetCustomerById

/Product/LoadProductById

/Product/DeleteProduct  o   /User/UserDelete

 

En mi opinión creo que este modo de proceder tiene algunas desventajas.

  • Como desventaja para nosotros, los que tenemos que picar código o leer el de otros compañeros, está el hecho de que sería más cómodo si todas las acciones de eliminar una entidad se nombraran simplemente Delete por ejemplo. No importa si es un producto, un cliente, o lo que sea. 

Por ejemplo, cuando estemos escribiendo URLs a mano en el explorador, algo habitual durante un desarrollo en el que no se han implementado todas la funcionalidades de navegación de la aplicación, nos será muy fácil acordarnos que todos los listados o informes se devuelven con Report. Así tendríamos:

/Products/Report

/Orders/Report

/Customer/Report


En lugar de que cada desarrollador elija el que mejor le cuadre en ese momento...

/Product/GetReport

/Orders/GetAllReport

/Customer/GetReportCustomer

  •  Desde un punto de vista puro de la programación orientada a objetos, no hay que olvidar que los controladores no son más que clases, y por lo tanto, parece lógico pensar que si tienes distintas clases con los mismos métodos habría que implementar una interfaz. Y quién sabe, a lo mejor también una clase abstracta para escribir el código de estos métodos en un solo sitio.
  • Como desventaja para los usuarios, si se le puede llamar así, es que ellos tengan a la vista (si alguno se fija) una mayor coherencia de las URLs por las que navega. Creo yo que da un sentido más ordenado a nuestro site.
  • No sé si también puede tener algún efecto negativo en cuanto a SEO. Habría que estudiarlo pero será en otra ocasión. De todas formas si alguien tiene algo que aportar a este aspecto que lo comente por favor.
 
Una posible solución podría ser utilizar inferfaces genéricas. Supongamos que vamos a establecer el CRUD básico sobre todas las entidades de nuestra aplicación. Entonces podríamos definir una interfaz como la siguiente:
 
    internal interface IControllerBase<in TEntity, in TEntityId>
        where TEntity : 
class 
        
where TEntityId: struct
    
{
        ActionResult Index()
;
        
ActionResult Create();
        
ActionResult Create(TEntity entity);
        
ActionResult Edit(TEntityId id);
        
ActionResult Edit(TEntity entity);
        
ActionResult Details(TEntityId id);
        
ActionResult Delete(TEntityId id);
    
}
 
Como podemos apreciar la interfaz permite trabajar con dos entidades genéricas. Una clase cualquiera que podrá ser una entidad de negocio o un ViewModel, lo que necesitemos mientras sea un tipo por referencia (class). Y también admite una entidad de tipo estructura (struct) que en nuestro caso representará el id de la entidad de negocio.
 
Ahora implementemos esta inferfaz en un controlador.
 
    public class ProductsController : Controller, IControllerBase<Product, int>
    {
        [HttpGet]
        
public ActionResult Index()
        {
            
return View(ProductsServices.GetAll());
        
}

        [HttpGet]
        
public ActionResult Create()
        {
            
return View();
        
}

        [HttpPost]
        
public ActionResult Create(Product entity)
        {
            
if (!ProductsServices.NewProduct(entity))
                
return View("Error");

            return 
RedirectToAction("Details"new { id entity.ProductID });
        
}

        [HttpGet]
        
public ActionResult Edit(int id)
        {
            
return View(ProductsServices.GetById(id));
        
}

        [HttpPost]
        
public ActionResult Edit(Product entity)
        {
            
if (!ProductsServices.UpdateProduct(entity))
                
return View("Error");

            return 
RedirectToAction("Details"new { id entity.ProductID });
        
}

        [HttpGet]
        
public ActionResult Details(int id)
        {
            var product 
ProductsServices.GetById(id);
            return 
View(product);
        
}

        [HttpGet]
        
public ActionResult Delete(int id)
        {
            
if (!ProductsServices.RemoveProduct(id))
                
return View("Error");

            return 
RedirectToAction("Index");
        
}
    }
 
 
En otros controladores, que naturalmente contengan las mismas acciones base, implementaríamos la interfaz y escribiríamos el código con la lógica de navegación específica para cada acción.
 
Si ahora necesitáramos añadir una acción o método que solo se va a utilizar en algunos controladores, podríamos definir otra interfaz con este método solamente e implementar la interfaz solo en los controladores que lo usen.
 
Supongo que alguno se estará preguntando si merece la pena el esfuerzo. Yo también me lo pregunto pero sinceramente creo que aporta más ventajas que desventajas. No cuesta tanto ser ordenados y dar un poco de coherencia a nuestro código aunque sea en algo tan simple como esto.
 
¡Se admiten opiniones!  :-)

Pack de ejemplos ASP.NET MVC 3

1. July 2011 18:47 by Oscar.SS in Desarrollo Web, Formación, Información  //  Tags: , , ,   //   Comments (14)

He tardado más de lo que hubiera querido en un principio pero por fin os puedo dar esta pequeña noticia. En CampusMVP han publicado, como un recurso más del excelente curso Desarrollo Web con ASP.NET MVC 3 tutorado por José María Aguilar, un pack de ejemplos en el que he tenido algo que ver.

Como podéis apreciar en la imagén, el pack consta de una solución con 31 pequeños proyectos de ejemplos sobre distintas caractéristicas del Framework ASP.NET MVC 3. Podríamos decir que la solución está dividida en tres partes principales.

1- Acceso a Datos. En el proyecto BlogDataAccess se encuentra el modelo Entity Framework que consume una sencilla base de datos que simula la gestión de un blog, la cual se utiliza en muchos de los ejemplos del pack para mostrar información. El script para crear esta base de datos está incluido también en este proyecto.

2- Demos. Para los que conozcan los cursos de CampusMVP, estos proyectos son los mismos que encontramos en la zona de descarga del curso, pero ahora han sido agrupados en una sola solución. Aquí se encuentran algunas demostraciones de funcionalidades realmente interesantes que pueden ayudar a profundizar en determinados conocimientos.

3- Ejemplos. Muy acertadamente se me ocurrió seguir la metodología de aprendizaje que aconsejan en todos los cursos de CampusMVP. Así que a medida que avanzaba por el temario del curso iba "cacharreando" con mí código. Y de ahí surje esta serie de ejemplos, de hecho, el sufijo en el nombre de cada ejemplo tiene que ver con el índice del temario. Como podéis imaginar son ejemplos muy simples y que muchas veces se han omitido comprobaciones para no distraer sobre la esencia de la caracteristica que se pretende mostrar. Vamos...¡que van al grano!

Sinceramente opino que aunque la mayoria sean ejemplos muy sencillos, el pack puede servir de ayuda para los que están empezando en el curso, sobre todo si tratan de extender el código o cambiarlo por completo. Y por supuesto también para los que ya lo terminaron. Es una forma de tener un repositorio de ejemplos siempre a mano para cuando vienen los...¡y como se hacía esto!

El pack se distribuye bajo una licencia de CampusMVP, así que para obtenerlo naturalmente tenéis que apuntaros al curso.

Recent Comments

Comment RSS

Month List