sábado, 7 de noviembre de 2015

Mi implementación de Repository y Unit of Work


1. Repository


He definido una interfaz genérica que implementarán todos los repositories y además alguno de ellos implementará la suya propia (y cumplir con la I de SOLID )

La interfaz genérica es esta:

 public interface IRepository<T> : IDisposable where T : class
    {
        IQueryable<T> GetQueryable();
        IEnumerable<T> GetAll();
        IEnumerable<T> Find(Expression<Func<T, Boolean>> where);
        T Single(Expression<Func<T, Boolean>> where);
        T First(Expression<Func<T, Boolean>> where);
        void Delete(T entity);
        void Add(T entity);
        void Attach(T entity);
        void Attach(T entity, EntityStatus status);     

    }

Como vemos tiene definidos unos métodos genéricos para cualquier Repository. Podremos buscar todos los elementos, buscar uno en concreto o realizar búsquedas más complejas (método Find)

Recordar, que un Repository es una colección de objetos en memoria, por lo que los métodos Delete ó Add son para acciones sobre objetos en memoria; hasta que no usemos UoW para realizar la persistencia, no tendrán efecto los cambios en nuestra base de datos.

Bien, pero resulta que para los movimientos tendremos métodos específicos; por ejemplo, para conseguir un listado de movimientos paginados utilizaremos un procedimiento almacenado, igual que a la hora de recalcular el saldo de un usuario.
Podría haber optado por utilizar extension methods, pero me ha parecido más sencillo hacer una interfaz para Movimientos; queda algo así

  public interface IMovimientosRepository : IRepository<Movimientos>
    {
        /// <summary>
        /// Actualiza en base de datos el saldo de usuario.
        /// </summary>
        /// <param name="UserName">Nombre de usuario para actualizar saldo de movimientos</param>
        void UpdateSaldo(string UserName);
       /// <summary>
        /// Devuelve los movimientos de un usuario, realizando la paginacion indicada
       /// </summary>
       /// <param name="UserName">Usuario del que buscar movimientos</param>
       /// <param name="PageIndex">Numero de pagina a devolver</param>
       /// <param name="PageSize">Numero de movimientos por pagina</param>
       /// <returns></returns>
        IEnumerable<vwMovimientos> GetMovimientos(string UserName, string Filtro, int PageIndex, int PageSize = 10);
    }
Vemos que la interfaz implementa IRepository con lo que tendremos los métodos básicos de buscar y modificar, además de los propios para los movimientos.
  • Implementación de Repository
 public class Repository<T> :IRepository<T> where T : class
    {
        protected readonly IObjectContextAdapter _ObjectContextAdapter;
        public ObjectSet<T> _ObjectSet;

        public Repository(IObjectContextAdapter context)
        {
            _ObjectContextAdapter = context;
            _ObjectSet = context.ObjectContext.CreateObjectSet<T>();
        }

        public IQueryable<T> GetQueryable()
        {
            return _ObjectSet;
        }

        public IEnumerable<T> GetAll()
        {
            return _ObjectSet.ToList();
        }

        public IEnumerable<T> Find(Expression<Func<T, Boolean>> where)
        {
            return _ObjectSet.Where(where);
        }

        public T Single(Expression<Func<T, Boolean>> where)
        {
            return _ObjectSet.Single(where);
        }

        public T First(Expression<Func<T, Boolean>> where)
        {
            return _ObjectSet.First(where);
        }

        public void Delete(T entity)
        {
            _ObjectSet.DeleteObject(entity);
        }

        public void Add(T entity)
        {
            _ObjectSet.AddObject(entity);
        }

        public void Attach(T entity)
        {
            Attach(entity, EntityStatus.Unchanged);
        }

        public void Attach(T entity, EntityStatus status)
        {
            _ObjectSet.Attach(entity);
            _ObjectContextAdapter.ObjectContext.ObjectStateManager.ChangeObjectState(entity, GetEntityState(status));
        }
        
        public void Dispose()
        {
            if (_ObjectContextAdapter != null)
                _ObjectContextAdapter.ObjectContext.Dispose();

            GC.SuppressFinalize(this);
        }

        private EntityState GetEntityState(EntityStatus status)
        {
            switch (status)
            {
                case EntityStatus.Added:
                    return EntityState.Added;
                case EntityStatus.Deleted:
                    return EntityState.Deleted;
                case EntityStatus.Detached:
                    return EntityState.Detached;
                case EntityStatus.Modified:
                    return EntityState.Modified;
                default:
                    return EntityState.Unchanged;
            }
        }
    }
  • Implementación de Movimientos Repository
 public class MovimientosRepository : Repository<Movimientos>, IMovimientosRepository

    {
        public MovimientosRepository(IObjectContextAdapter context)
            : base(context)
        {

        }
        public void UpdateSaldo(string UserName)
        {
            SqlParameter parameter = new SqlParameter("@UserName", SqlDbType.NVarChar);
            parameter.Value = UserName;
            _ObjectContextAdapter.ObjectContext.ExecuteStoreCommand("Exec RecalculaSaldo @UserName", parameter);
        }

        public IEnumerable<vwMovimientos> GetMovimientos(string UserName, string Filtro, int PageIndex, int PageSize = 10)
        {
            var userNameParameter = new SqlParameter("@UserName", UserName);
            var PageIndexParameter = new SqlParameter("@PageIndex", PageIndex);
            var PageSizeParameter = new SqlParameter("@PageSize", PageSize);
            var filtroParameter = new SqlParameter("@Filtro", Filtro);
            var result = _ObjectContextAdapter.ObjectContext.ExecuteStoreQuery<vwMovimientos>("exec GetMovimientos @UserName, @PageIndex, @PageSize, @Filtro", userNameParameter, PageIndexParameter, PageSizeParameter, filtroParameter).ToList();

            return result;
        }
    }

Vemos que MovimientosRepository hereda de Repository e implementa IMovimientosRepository.
Queda claro que, como veiamos en la entrada anterior, con el uso de Repository estamos evitando repetir el código que obtiene los movimientos de un usuario.
Además, todavía no hemos hablado de EF por ninguna parte ;-)

2. Unit of Work

Y ahora Unit of Work. Su interfaz

 public interface IUnitOfWork : IDisposable
    {
        IRepository<T> Repository<T>() where T : class;
        IMovimientosRepository MovimientosRepo { get; }
        void SaveChanges();
    }
Bien sencilla; sólo tener en cuenta que debe implementar IDisposable y que debe tener un método para realizar la persistencia (SaveChanges en este caso)
Tendremos una lista de Repository genéricos y acceso al Repository de Movimientos.

Y aquí la implementación:

public class UnitOfWork : IUnitOfWork
    {
        private readonly IObjectContextAdapter _ObjectContextAdapter;        
        public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
        
        public UnitOfWork(IObjectContextAdapter objectContextAdapter)
        {
            _ObjectContextAdapter = objectContextAdapter;
            MovimientosRepo = new MovimientosRepository(_ObjectContextAdapter);
        }
       
        public IRepository<T> Repository<T>() where T : class
        {
            if (repositories.Keys.Contains(typeof(T)) == true)
            {
                return repositories[typeof(T)] as IRepository<T>;
            }
            IRepository<T> repo = new Repository<T>(_ObjectContextAdapter);
            repositories.Add(typeof(T), repo);
            return repo;
        }
        public IMovimientosRepository MovimientosRepo { get; private set; }
        public void SaveChanges()
        {
            try
            {
                _ObjectContextAdapter.ObjectContext.SaveChanges();
            }
            catch (OptimisticConcurrencyException oce)
            {
                //TODO:Elevar la excepcion                
                //throw new ConcurrencyException(oce.Message, oce.InnerException);
            }
            catch (DataException)
            {
                throw;
            }
        }
       

        public void Dispose()
        {
            if (_ObjectContextAdapter != null)
                _ObjectContextAdapter.ObjectContext.Dispose();

            GC.SuppressFinalize(this);
        }
    }
Si nos fijamos en el constructor de las clase UnitOfWork, vemos que recibe como parámetro la interfaz IObjectContextAdapter; no recibe ningún contexto concreto, solo una interfaz; seguimos trabajando con abstracciones y no con implementaciones concretas.


Bien, pues en la siguiente entrada, veremos cómo usamos todo esto desde nuestra capa de Services

jueves, 5 de noviembre de 2015

Consideraciones previas sobre Repositoy y Unit of Work

Antes de ver cómo he realizado la implementación de Repository y Unit of Work, me gustaría comentar alguna cosa.


  1. Repository

Definición
Es el intermediario entre la capa de dominio y el framework de persistencia; es una colección de objetos del dominio en memoria sobre los que podemos hacer acciones CRUD (siempre en memoria) o consultas


¿Qué nos aporta utilizar Repositories?
Bajo mi modesta opinión, tenemos tres ventajas:

  • Reducción de duplicidad de consultas.
  • Nos desacopla de los frameworks de persistencia
  • Nos facilita los test


  1. Unit of Work
Definición
Se encarga de realizar la persistencia de los objetos modificados en memoria.


Hay gente que considera que no es necesario reimplementar este patrón utilizando Entity Framework porque éste ya lo es. Es cierto que lo implementa, pero por si solo, creo que tiene algunos puntos que no me convencen

Con EF tenemos DbSet que actuan como Repository; en DbSet tenemos los mismos métodos que tenemos en un Repository (Add, Remove, ....)
También es cierto que tenemos DbContext que implementa Unit of Work y que coordina las actualizaciones de los objetos modificados en memoria (DbSet)
Pero con dbSet:

  • No evitamos repetir consultas; si queremos saber los movimientos de un usuario entre un rango de fechas, tenemos que repetir la consulta tantas veces como la necesitemos.... a no ser que lo encapsulemos en una clase (Repository?) o implementemos cualquier otra solución
  • No nos desacoplamos del framework de persistencia. Bueno, esta parece clara.

y con DbContext, la dependencia con EF es evidente.

Por eso creo que EF por si solo no implementa este patrón y por eso he decidido hacer mi implementación que veremos en la próxima entrada.