En nuestros
primeros dos post creamos un servicio WCF y un cliente que lo consume. Este
servicio es en realidad muy simple: un único método que devuelve una lista de
string con los id de los productos de nuestra base de datos. En este post
añadiremos unas cuantas operaciones más.
Estas
operaciones serán las siguientes DetalleProducto (devolverá la descripción y el
precio de un producto dado un id), StockProducto (dado un id de producto devolverá
el stock conjunto de todas las bodegas que tengan ese producto), AnadirStock
(añadirá la cantidad del producto a la bodega según se indiquen en los parámetros).
Nuestro
estilo es un desarrollo “contract-first”, el contrato primero. Así, primero
diseñamos las operaciones en la clase interfaz del contrato y una vez definidos
procedemos a su implementación. Comencemos entonces con la clase
IProductoServicio, El código nos quedaría más o menos así:
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; namespace TestWCF { //Esto define la clase que devuelve el detalle de un producto [DataContract] public class ProductoDetalle { [DataMember] public string ProductoId; [DataMember] public string Descripcion; [DataMember] public decimal? Precio; } [ServiceContract] public interface IProductoServicio { // devuelve el id de todos los productos [OperationContract] List<string> ListaProductos(); //Devuelve el detalle de un producto [OperationContract] ProductoDetalle DetalleProducto(string ProductoID); //Devuelve el stock completo de un producto [OperationContract] int StockProducto(string ProductoID); //Añade la cantidad del producto indicados a la bodega indicada [OperationContract] bool AnadirStock(string ProductoID, int Bodega, int cantidad); } }
Analicemos el código, lo primero que notamos es la clase ProductoDetalle, esta una clase etiquetada como contrato de datos (DataContract) y cada una de sus variables como DataMember, es similar a definir a un tipo compuesto. Esta clase la usaremos para devolver un objeto con el detalle del producto en la operación correspondiente, como es publica quedará disponible en el cliente también, por medio del proxy.
Por si no
se percatan, la variable Precio es de tipo decimal? El signo de pregunta
significa que es de tipo nullable, o sea, que es un tipo decimal que puede
contener el valor null (un decimal normal no puede contener el valor null), en
este escenario es necesario por cuanto en la base de datos el valor Precio esta
como “Allow Null” (hubiese sido mejor no permitir nulos y colocar un valor cero
por defecto, digamos que fue un desliz de diseño).
Luego se
añadieron las tres operaciones propuestas, notar que DetalleProducto devuelve
un “tipo” ProductoDetalle, la clase que definimos primero como DataContract. StockProducto
devuelve un entero con la cantidad total de inventario de un producto. Así
mismo AnadirStock devuelve un booleano, esto es verdadero si se pudo añadir la
cantidad, falso si se produjo algún error.
Ya con
nuestro contrato definido procedemos a la implementación de cada una de las
operaciones en el servicio propiamente dicho. El archivo ProductoServicio.cs
nos queda más o menos así:
Examinemos las operaciones que implementamos para discernir mejor el uso de LINQ. En la operación DetalleProducto utilizamos la función First de Linq para traernos el primer registro que corresponda con la comparación del id del producto. Además como esta comparación es entre strings lo realizamos utilizando el String.Compare, esta es una práctica habitual en la consultas vía Linq, ya que es la forma segura de comparar cadenas de caracteres. Una vez obtenido el registro vaciamos sus propiedades en nuestra clase de contrato de datos para enviarla al cliente.
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; using System.ServiceModel; using System.ServiceModel.Web; using System.Text; using ModeloProductos; namespace TestWCF { public class ProductoServicio : IProductoServicio { // Devuelve el id de todos los productos public List<string> ListaProductos() { // Se crea la lista para los id de Productos List<string> lstProductos = new List<string>(); try { // Conectando a la base de datos usando Entity Framework using (TestDBEntities db= new TestDBEntities ()) { // LINQ para obtener la lista de id de los productos var productos = from producto in db.Productos select producto.ProductoID; lstProductos = productos.ToList(); } } catch { // De momento no se implementa el manejo de excepciones } // devuelve la lista de productos return lstProductos; } // Devuelve el detalle de un producto public ProductoDetalle DetalleProducto(string ProductoID) { // Creamos la instancia de la clase Producto definida en el contrato ProductoDetalle DatosProducto = null; try { // conectamos con la base de datos por medio del Entity Framework using (TestDBEntities db = new TestDBEntities ()) { // LINQ para encuentrar el primer registro cuyo id corresponda con el parametro Producto productoEncontrado = db.Productos.First( p => String.Compare(p.ProductoID , ProductoID) == 0); DatosProducto = new ProductoDetalle () { ProductoId = productoEncontrado.ProductoID, Descripcion = productoEncontrado.Descripcion, Precio = productoEncontrado.Precio }; } } catch { // De momento no se implementa el manejo de excepciones } // Retorna el detalle de producto return DatosProducto; } // Devuelve el stock completo de un producto public int StockProducto(string ProductoID) { // Obtiene el nivel total de stock del producto incluyendo todas bodegas int stock = 0; try { // conectamos con la base de datos por medio del Entity Framework using (TestDBEntities db = new TestDBEntities()) { // LINQ para sumar todo el stock de todas los bodegas stock = (from i in db.Inventarios join p in db.Productos on i.ProductoID equals p.ProductoID where String.Compare(p.ProductoID, ProductoID) == 0 select (int)i.Stock).Sum(); } } catch { // De momento no se implementa el manejo de excepciones } // Devuelve el stock total del producto return stock; } // Añade la cantidad del producto indicados a la bodega indicada public bool AnadirStock(string ProductoID, int Bodega, int cantidad) { //Añade la cantidad indicada para el producto y en la bodega indicados try { // conectamos con la base de datos por medio del Entity Framework using (TestDBEntities db = new TestDBEntities()) { // LINQ para aseguramos que el id del producto exista string producto = (from p in db.Productos where String.Compare(p.ProductoID, ProductoID) == 0 select p.ProductoID).First(); // LINQ para extraer de la tabla el registro del inventario // correspondiente al producto y la bodega Inventario inventario = db.Inventarios.First( i => String.Compare(i.ProductoID , ProductoID) == 0 && i.BodegaID == Bodega ); // realizamos el update sobre el registro encontrado inventario.Stock += cantidad; // salvamos los cambios en la base de datos db.SaveChanges(); } } catch { // retorna false si se produjo algun error return false; } // Retorna true si la operacion fue exitosa return true; } } }
Examinemos las operaciones que implementamos para discernir mejor el uso de LINQ. En la operación DetalleProducto utilizamos la función First de Linq para traernos el primer registro que corresponda con la comparación del id del producto. Además como esta comparación es entre strings lo realizamos utilizando el String.Compare, esta es una práctica habitual en la consultas vía Linq, ya que es la forma segura de comparar cadenas de caracteres. Una vez obtenido el registro vaciamos sus propiedades en nuestra clase de contrato de datos para enviarla al cliente.
En la
operación StockProducto usamos una sentencia Linq un poco más complicada:
realizamos un join entre las tablas Producto e Inventario, enlazadas por medio
de la columna ProductoId, además buscamos por el parámetro haciendo la
comparación con el String.Compare tal y como señalamos anteriormente.
Finalmente utilizamos la operación de agregación “sum” para sumar los stocks de
todas las bodegas. El resultado se lo asignamos a la variable stock la cual
devolvemos al final de la operación.
Finalmente
en la operación AnadirStock, primero volvemos a usar la sentencia First de Linq
para determinar si el id de producto existe en la base de datos. Seguidamente
también usamos First para devolver el primer registro de la tabla inventario
que cumpla con los criterios de producto id y bodega. Luego, al registro
devuelto, en la columna stock, le sumamos la cantidad pasada por parámetro y
finalmente salvamos los cambios en la base de datos por medio de SaveChanges del Entity Framework.
Ahora nos
queda actualizar nuestra aplicación cliente para hacer una prueba de cada una
de las operaciones. Primero necesitamos actualizar nuestra referencia para que
el cliente conozca las nuevas operaciones y contrato de datos si los hay. Para
esto nos vamos a nuestro proyecto consola
ProductoCliente y hacemos clic derecho sobre la referencia que teníamos
previamente, en el menú emergente y elegimos “Update Service Reference”
Ya con la referencia actualizada podemos trabajar en el código del cliente. El archivo Program.cs debería quedar más o menos asi:
Como podemos ver la clase ProductoDetalle esta disponible y de hecho la utilizamos para mostrar el detalle de producto, también usamos StockProducto para verificar que AnadirStock funciona correctamente.
Antes de ejecutar el cliente la información en mi base de datos se ve más o menos así:
Ya con la referencia actualizada podemos trabajar en el código del cliente. El archivo Program.cs debería quedar más o menos asi:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using ProductoCliente.TestWCF; namespace ProductoCliente { class Program { static void Main(string[] args) { // Se crea el proxy para conectar con el servicio ProductoServicioClient proxy = new ProductoServicioClient(); // Probamos el metodo que obtiene los id de productos Console.WriteLine("Probando ListaProductos: Listado de Id de Productos"); List<string> ListaProductos = proxy.ListaProductos(); foreach (string idProdcto in ListaProductos) { Console.WriteLine("Id Producto: {0}", idProdcto); } Console.WriteLine(); Console.WriteLine("Probando DetalleProducto: Detalle de producto B-0011"); ProductoDetalle detalle = proxy.DetalleProducto("B-0011"); Console.WriteLine("Producto ID: {0}", detalle.ProductoId); Console.WriteLine("Descripción: {0}", detalle.Descripcion); Console.WriteLine("Precio: {0}", detalle.Precio.ToString()); Console.WriteLine(); Console.WriteLine("Probando StockProducto: Stock completo del producto B-0011"); int stock = proxy.StockProducto("B-0011"); Console.WriteLine("Stock Completo: {0}", stock.ToString()); Console.WriteLine(); Console.WriteLine("Probando AnadirStock: Colocando en la bodega 3 la cantidad de 30 más"); if (proxy.AnadirStock ("B-0011", 3, 30)) { stock = proxy.StockProducto("B-0011"); Console.WriteLine("Stock Modificado. Nuevo Stock: {0}", stock); } else { Console.WriteLine("Actualización de stock fallido"); } Console.WriteLine(); // Desconectado del servicio proxy.Close(); Console.WriteLine("Presione ENTER para terminar."); Console.ReadLine(); } } }
Como podemos ver la clase ProductoDetalle esta disponible y de hecho la utilizamos para mostrar el detalle de producto, también usamos StockProducto para verificar que AnadirStock funciona correctamente.
Antes de ejecutar el cliente la información en mi base de datos se ve más o menos así:
Ejecutamos nuestra aplicación cliente, la cual nos arroja el siguiente resultado:
Todo según el plan.
En el
siguiente post pasaremos nuestro servicio al IIS, tal y como sería una vez
finalizadas las pruebas de un desarrollo de este tipo.
0 comments:
Publicar un comentario