-
Agrega dos ítems más a la lista de ítems,
-
Modifica el importe y/o la cantidad de productos de uno de los ítems y finalmente,
-
Elimina algún que otro ítem.
En este caso, cuando deben persistirse los datos? apenas agrega, modifica o elimina un ítem? o debe tratarse a la factura como una unidad y persistir todo junto? Si algo falla,… debe guardarse el resto? que sucede si otro usuario (además del que estamos hablando) estaba trabajando con el mismo documento al mismo tiempo pero guardó primero? como sabemos que fue lo que modificó el usuario y que, por lo tanto, debemos guardar? guardamos primero la factura y luego sus ítems o al revés?
Todas las respuestas a estas preguntas (que considero que las fuiste contestando) se resuelven mediante la implementación de este patrón.
Voy a explicarlo un poco. El Unit Of Work, como se observa en la figura de arriba, se implementa mediante una única clase la cual tendrá al menos tres listas, una lista para los objetos nuevos que deben guardarse en la db, otra lista para los objetos que han sido modificados y que por lo tanto deben modificarse en la db y, otra lista para los objetos que deben eliminarse. Opcionalmente, o mejor dicho, según la implementación lo requiera, puede contener una cuarta lista para almacenar clones de los objetos que se traen desde la base de datos, de manera que antes de persistir un objeto pueda corroborar, mediante estos clones de los objetos originales, que los registros correspondientes no han sido modificados por otro usuario.
Una razón para leer el libro y no solo conformarse con este artículo es que Fowler hace un análisis exhaustivo de este patrón (y de todos sus patrones), sus formas de implementar, cuando implementarlos, pros y contras y sobre todo, lo que a mi más me gustó fueron los distintos intentos por hacer esta clase lo más transparente posible para el programador. Así, por ejemplo, analiza la posibilidad de que todas las clases persistibles hereden de una clase base que automáticamente las registre como nuevas en el contenedor, que cuando se invoque el setter de una propiedad, esta notifique al UnitOfWork correspondiente para que la registre como dirty, etc.
Acá dejo una implementación sencillísima de UnitOfWork sin control de concurrencias y en colaboración con un DataMapper trivial (actualización: el artículo original tenía un proyecto adjunto el cual he perdido, por esta razón el DataMapper no está presente pero lo que originalmente hacía era retornar la sentencia sql necesaria para insertar, modificar y borrar los objetos en la base de datos).
using System; using System.Collections.Generic; using System.Text; using System.Transactions; using System.Data.SqlClient; using System.Configuration; using System.Threading; using System.Resources; using ConsoleApplication7; namespace Patterns { // Nuestra clase UnitOfWork (Martin Fowler) public class UnitOfWork { // Aquí estan las tres listas de las que hablamos // Ademas de estas puede existir una cuarta que almacene // los objetos limpios (o que se leyeron de la db) List<IBusinessObject> newObjects; List<IBusinessObject> dirtyObjects; List<IBusinessObject> removedObjects; // Creamos las listas en este constructor public UnitOfWork() { newObjects = new List<IBusinessObject>(); dirtyObjects = new List<IBusinessObject>(); removedObjects = new List<IBusinessObject>(); } public void New(IBusinessObject bo) { Guard.NotNull(Resources.parameter_is_null, "bo", bo); Guard.IsTrue(Resources.object_is_dirty, dirtyObjects.Contains(bo)); Guard.IsTrue(Resources.object_is_deleted, removedObjects.Contains(bo)); Guard.IsTrue(Resources.object_is_already_inserted, newObjects.Contains(bo)); newObjects.Add(bo); } public void Remove(IBusinessObject bo) { Guard.NotNull(Resources.parameter_is_null, "bo", bo); if (newObjects.Remove(bo)) return; dirtyObjects.Remove(bo); if (!removedObjects.Contains(bo)) removedObjects.Add(bo); } public void Update(IBusinessObject bo) { Guard.NotNull(Resources.parameter_is_null, "bo", bo); Guard.IsTrue(Resources.object_is_deleted, removedObjects.Contains(bo)); if (!newObjects.Contains(bo) && !dirtyObjects.Contains(bo)) dirtyObjects.Add(bo); } // El método Commit es el encargado de iniciar las transacciones, // y realizar la invocación a la base de datos. public void Commit() { string connectString = string.Empty; using (TransactionScope transactionScope = new TransactionScope()) { connectString = ConfigurationManager.ConnectionStrings["Patterns"].ConnectionString; using (SqlConnection connection = new SqlConnection(connectString)) { // Construimos las sentencias SQL para luego pasárselas a la DB // mediante un command. Para esto, en .Net hay que hacerlo separando // las sentencias con un punto y como (;) StringBuilder stringBuilder = new StringBuilder(); foreach (IBusinessObject bo in newObjects) stringBuilder.Append(Mapper.Instance.Insert(bo) + ";"); foreach (IBusinessObject bo in dirtyObjects) stringBuilder.Append(Mapper.Instance.Update(bo) + ";"); foreach (IBusinessObject bo in removedObjects) stringBuilder.Append(Mapper.Instance.Delete(bo) + ";"); string command = stringBuilder.ToString(); // Abre la conexió, crea el comando con las sentencias SQL // e invoca al RDBMS. connection.Open(); SqlCommand command1 = new SqlCommand(command, connection); command1.ExecuteNonQuery(); // Limpia las listas si todo estuvo bien. ClearAll(); } transactionScope.Complete(); } } private void ClearAll() { newObjects.Clear(); dirtyObjects.Clear(); removedObjects.Clear(); } } }
Introducción En vista de que parece que el artículo que publique ayer sobre UoW , parecía que era demasiado