Visita nuestros cursos en KODOTI. Click para unirte
Estudia con KODOTI. Únete

Ejemplo de Unit of Work con C#

Vamos a entender en que consiste este patrón y que nos resuelve

Rodríguez Patiño, Eduardo
1,798 lecturas
Rodríguez Patiño, Eduardo
Hemos migrado nuestras publicaciones del blog antiguo. Si crees que esta se encuentra incompleta o sin coherencia deja un comentario para darle una pronta solución.

Luego de buen tiempo sin escribir (casí 2 meses o más), he decidido regresar con fuerza y retomarlo con una entrada sobre patrones de diseño, especialmente con este patrón que lo he venido usando en un proyecto y me parece una muy buena propuesta junto a otros patrones adicionales.

¿Unit of Work?

En primera instancia UoW soluciona un problema sobre las conexiones a la base de datos y permite encapsular dichas consultas a la DB en una sola pudiendo así hacer varias tareas y al final realizar los cambios.

Por ejemplo, si necesitamos realizar una app para retirar dinero del cajero automático (ATM), los pasos serían los siguientes:

  1. Ingresar el monto a retirar
  2. Validar si tiene el saldo necesario
  3. Registrar la salida (log)
  4. Actualizar el nuevo saldo

La solución práctica sería meter todo el código en un solo método y problema resuelto. Pero mejor es tener cada acción por métodos separados de esta manera el código queda más limpio y fácil de mantener, y hacemos que sea reutilizable en otros componentes de nuestro proyecto.

public class BankOperation
{
    public decimal decimal Verify(int userId)
    {
        using (var context = DbHelper.Open())
        {
            // Logic
            return context.UserAmount.Single(x => x.UserId == userId).Amount;
        }
    }

    public decimal InputOutput(int userId, decimal amount)
    {
        using (var context = DbHelper.Open())
        {
            // Logic
            context.SaveChanges();
        }
    }

    public decimal Update(int userId, decimal amount)
    {
        using (var context = DbHelper.Open())
        {
            // Logic
            context.SaveChanges();
        }
    }
}

¿Cuál es el problema?

Cada método abre una instancia a la base de datos por lo cual podemos conllevar a problemas de performance. Más allá de esto, cada método gestiona su propia escritura de los cambios (problema real), es decir que al llamar a un método la acción para confirmar el INSERT/UPDATE/DELETE es ejecutado independientemente y esto no es muy bueno. ¿Supongamos que quisieramos tener todo en un bloque de transaction por si algo falla?, pues no, no vamos a poder hacerlo.

¿Cómo debería ser implementando UoW?

En pocas palabras UoW te dice, hey yo voy a gestionar la instancia a la BD y hacerme cargo de las escrituras, tu encargate de la lógica del negocio.

Creamos la clase de UnitOfWork. En la siguiente publicación haremos algo más robusto, por ahora es una introducción.

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;

    public DbContext Open() {
        DbContext = new DbContext();
        return DbContext();
    }

    public void SaveChanges()
    {
    _context.SaveChanges();
    }
}

Refactorizando nuestra clase BankOperation quedaría de esta manera

public class BankOperation : IDisposable
{
    public BankOperation(DbContext _context)
    {
        _context = context;
    }

    public decimal Verify(int userId)
    {
        return _context.UserAmount.Single(x => x.UserId == userId).Amount;
    }

    public decimal InputOutput(int userId, decimal amount)
    {
        // Logic
    }

    public decimal Update(int userId, decimal amount)
    {
        // Logic
    }
}

Si se dieron cuenta cada método ya no hace una instancia a la Db y se removieron los SaveChange(). Es decir, el UnitOfWork inyecta la instancia a la BD y el confirmar los cambios de escritura se encargará UoW, ya no la clase BankOperation.

Ejemplo en marcha

var uow = new UnitOfWork();
using(var context = uow.Open())
{
    // Registramos la dependencia de la base de datos, ahora es solo una compartida por la memoria
    var bankOperation = new BankOperation(context);

    // Parámetros de prueba
    var userId = 1;
    var amount = 2000;

    // Si tiene el saldo necesario a retirar
    var currentAmount = bankOperation.Verify(userId);
    if(currentAmount >= amount)
    {
        // Registra la entrada y salida
        bankOperation.InputOutPut(userId);

        // Actualiza el nuevo saldo
        bankOperation.Update(userId, currentAmount - amount);

        // Ejecuta cambios
        uow.SaveChanges();
    }

    // Libera la conexión
    uow.Dispose();
}

En la siguiente publicación sobre UnitOfWork vamos agregar Repository Pattern, Services Pattern para complementar el ejemplo con un CRUD y algo más robusto que nos pueda servir en nuestros proyectos. Asimismo, mencinaré las ventajas/desventajas porque no todo es perfecto en la vida.

¿Te gustó nuestra publicación?
Suscríbete a nuestro boletín