Implementando el patrón Repositorio y Unidad de Trabajo (Unit of Work & Repository) – ASP.NET MVC 5

En esta entrada veremos un patrón de diseño que utilizo ya en todos los proyectos en los que participo, que consiste básicamente en la creación de una capa intermedia que se encuentra entre la Data Layer (acceso a datos) y la Bussiness Layer (reglas de negocio).

El uso de repositorios llega a ser muy común, ya que permite tener bien divida la aplicación, re utilización de código y ademas hace más sencillo el uso de pruebas unitarias, más si usamos interfaces e inyección de dependencias para poder crear  mockups, fake data y ese stuff.

En una unidad de trabajo su función principal es juntar todos los repositorios que conforman nuestra capa de datos y ordenarlos de tal forma que permiten el trabajar en el mismo contexto de Entity Framework y poder hacer operaciones entre repositorios y todo en las mismas transacciones. La unidad de trabajo es utilizada por la capa de negocios, que por lo regular ahí se incluyen las reglas que nuestra aplicación tendrá. Y finalmente el controlador de MVC que es el que nos comunicará con la vista. Sin mencionar que en el front-end podemos tener arquitecturas MVW (AngularJS) o MVVM (KnockoutJS, KendoUI)… un sin fin de patrones que son tan emocionantes!

Si no sabes muy bien que es un repositorio, puedes leer este articulo (en ingles). Pero básicamente consiste en evitar la duplicación de código, la centralización del acceso a datos ( a veces es necesario tener en caché ciertas cosas), toda la aplicación tendrá el acceso a datos en un solo lugar. Pero después de tener muchos repositorios, que cada uno tiene su contexto, hay que organizar lo que se supone tenemos organizado. Ahí entran las Unit of Work.

Es normal crear un repositorio por cada entidad de datos que tenemos (Ejem. Clientes, Contactos, Ventas, etc) y por lo regular cada uno de estos tiene básicamente las mismas operaciones CRUD (Create, Read, Update & Delete). Por lo tanto no es muy práctico el tener que escribir las mismas operaciones por cada entidad (tabla) que tenemos en la base de datos. Es por eso que en este post veremos como crear un repositorio genérico, que permitirá hacer las operaciones básicas en una entidad utilizando el mismo código para todas.

Creando un proyecto en Visual Studio

Screenshot (5)

Yo tengo el Visual Studio 2015, pero utilizaremos MVC 5.

Screenshot (6)Estoy creando el proyecto MVC sin Authentication, pero la verdad esto viene siendo irrelevante.

Lo primero que voy a hacer, es instalar Entity Framework utilizando Nugget, después de eso crearemos nuestro modelo (muy sencillo) utilizando Code First en lugar de Database First (para hacerlo todo desde visual studio, pero la forma de hacerlo no importa al final).

Crearemos una base de datos de clientes y ventas utilizando Code First (al final solo utilizaremos la tabla Clientes para fines prácticos). Las siguientes clases las agregaremos en la carpeta Models que ya existe, sino, donde tu gustes. Dejándolo de la siguiente forma:

User.cs

1

Aquí agregamos unos Data Annotations para agregar propiedades a la base de datos final que se generará (Auto ingremento, not null, etc)

Sale.cs

2

MyDbContext.cs

3

Aquí estamos creando nuestro contexto que incluye las dos entidades que hemos creado. En el constructor estamos indicando la cadena de conexión de la base de datos, que es la siguiente:

<add connectionString=”Server=(local);Database=MyDbContext;Trusted_Connection=True;MultipleActiveResultSets=true” name=”MyDbContext” providerName=”System.Data.SqlClient”/>

Es importante decir que tenemos que tener instalada una instancia de Sql Server.

En este punto deberíamos de poder compilar sin problemas.

Agregando un repositorio genérico

Regularmente, esto que estamos haciendo lo hacemos en una librería aparte, pero por simplicidad lo hacemos todo en el mismo proyecto.

Lo que vamos a hacer ahora es crear una clase que será un repositorio genérico que pueda ser reutilizado por las dos entidades que acabamos de crear. Para esto crearemos una carpeta llamada Repositories a nivel de nuestro proyecto y agregaremos una clase llamada GenericRepository como se muestra a continuación:

GenericRepository.cs

   public class GenericRepository<TEntity>
        where TEntity: class
    {

        private readonly MyDbContext context;
        private readonly DbSet<TEntity> dbSet;

        public GenericRepository(MyDbContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }
        public void Create(TEntity entity)
        {
            dbSet.Add(entity);
        }
        public void CreateRange(IEnumerable<TEntity> entities)
        {
            foreach (var entity in entities)
            {
                Create(entity);
            }
        }
        public async Task<TEntity> FindAsync(params object[] keyValues)
        {
            return await dbSet.FindAsync(keyValues);
        }
        public virtual IQueryable<TEntity> SelectQuery(string query, params object[] parameters)
        {
            return dbSet.SqlQuery(query, parameters).AsQueryable();
        }
        public void Update(TEntity entity)
        {
            dbSet.Attach(entity);
            context.Entry(entity).State = EntityState.Modified;
        }
        public void Delete(TEntity entity)
        {
            if (context.Entry(entity).State == EntityState.Detached)
            {
                dbSet.Attach(entity);
            }
            dbSet.Remove(entity);
        }
        public async Task Delete(params object[] id)
        {
            TEntity entity = await this.FindAsync(id);
            if (entity != null)
            {
                this.Delete(entity);
            }
        }
        public IQueryable<TEntity> Queryable()
        {
            return dbSet;
        }
    }

Aquí estamos permitiendo hacer las operaciones básicas que se podrán hacer con una entidad. La combinación de estas te permitirán hacer cualquier cosa.

Los repositorios se pueden implementar de muchas formas, pero siento yo que entre menos escribas y más reutilices es mejor, por eso me gusta irme por hacer un repositorio genérico.

Si se dieron cuenta, existen métodos CRUD y algunos más, pero en ningún lado podemos ver el método “SaveChanges” o guardar nuestro contexto. Esto es porque el que se encargará de hacer las operaciones en la base de datos será la unidad de trabajo, para tener centralizado el contexto y cuando se guarden los cambios se haga todo en una sola transacción sin importar cuantos repositorios modificamos.

UnitOfWork.cs

    public class UnitOfWork
    {
        public UnitOfWork()
        {
            this.context = new MyDbContext();
        }
        private readonly MyDbContext context;

        private GenericRepository<Client> clientsRepository;
        public GenericRepository<Client> ClientsRepository
        {
            get
            {
                if (this.clientsRepository == null)
                {
                    this.clientsRepository = new GenericRepository<Client>(this.context);
                }
                return this.clientsRepository;
            }
        }

        private GenericRepository<Sale> salesRepository;
        public GenericRepository<Sale> SalesRepository
        {
            get
            {
                if (this.salesRepository == null)
                {
                    this.salesRepository = new GenericRepository<Sale>(this.context);
                }
                return this.salesRepository;
            }
        }

        public async Task SaveChangesAsync()
        {
            await this.context.SaveChangesAsync();
        }
    }

De esta forma estamos centralizando nuestros repositorios y todos trabajando en un mismo contexto.

De aquí en adelante ya podemos usar nuestra unidad de trabajo para hacer cualquier operación. Se puede usar directamente en el controlador MVC o desde una clase Servicio (business layer) que haga todo. Nosotros lo haremos desde el controlador, luego podremos ver la implementación de servicios e inyección de dependencias para facilitar las pruebas unitarias.

Controlador ClientsController.cs

Crearemos un controlador llamado Clients, simplemente para llenar de información sencilla y poder mostrarla.  Utilizaremos el scaffolding de Visual Studio para no batallar. Agregamos un nuevo controlador y seleccionamos la opción mostrada:

4

5

Este scaffolding nos generará el acceso a datos de forma directa con el contexto, por lo tanto tendremos que modificar el controlador a la siguiente forma (realmente lo que nos importa de este scaffolding son las vistas) y haremos uso de la unidad de trabajo que acabamos de crear:

    public class ClientsController : Controller
    {
        private readonly UnitOfWork unit = new UnitOfWork();

        // GET: Clients
        public ActionResult Index()
        {
            return View(unit.ClientsRepository.Queryable().ToList());
        }

        // GET: Clients/Details/5
        public async Task<ActionResult> Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Client client = await unit.ClientsRepository.FindAsync(id);
            if (client == null)
            {
                return HttpNotFound();
            }
            return View(client);
        }

        // GET: Clients/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Clients/Create        
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Create([Bind(Include = "ClientId,FullName,Email,PhoneNumber")] Client client)
        {
            if (ModelState.IsValid)
            {
                unit.ClientsRepository.Create(client);
                await unit.SaveChangesAsync();
                return RedirectToAction("Index");
            }

            return View(client);
        }

        // GET: Clients/Edit/5
        public async Task<ActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Client client = await unit.ClientsRepository.FindAsync(id);
            if (client == null)
            {
                return HttpNotFound();
            }
            return View(client);
        }

        // POST: Clients/Edit/5        
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> Edit([Bind(Include = "ClientId,FullName,Email,PhoneNumber")] Client client)
        {
            if (ModelState.IsValid)
            {                
                unit.ClientsRepository.Update(client);
                await unit.SaveChangesAsync();

                return RedirectToAction("Index");
            }
            return View(client);
        }

        // GET: Clients/Delete/5
        public async Task<ActionResult> Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Client client = await unit.ClientsRepository.FindAsync(id);
            if (client == null)
            {
                return HttpNotFound();
            }
            return View(client);
        }

        // POST: Clients/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<ActionResult> DeleteConfirmed(int id)
        {
            Client client = await unit.ClientsRepository.FindAsync(id);
            unit.ClientsRepository.Delete(client);
            await unit.SaveChangesAsync();
            return RedirectToAction("Index");
        }        
    }

Si tuviéramos que hacer operaciones con ventas o cualquier otra cosa que se encontrara en nuestra unidad de trabajo, al guardar los cambios a nivel UnitOfWork se guardarían los cambios de un solo contexto, por lo tanto todo los cambios hechos en todos los repositorios se harían en una sola transacción. Y si nos ponemos a pensar, esto mismo lo podría hacer MyDbContext, porque básicamente es una unidad de trabajo también, pero hacerlo de esta forma hace que esté totalmente ligado a Entity Framework y no se podrá crear una abstracción para delegar responsabilidades (Capas).

Manejando un solo contexto permite que todo sea persistente si algo falla, reutilizar código y dar la posibilidad de manejar TDD (Test Driven Development) implementando técnicas de mockups o fake data. Claro, para lograr TDD tendríamos que hacer uso de interfaces y algunas modificaciones para que las implementaciones no estén totalmente ligadas.

Si corremos la aplicación y nos vamos al url http://localhost:xxxx/Clients podemos ver ya un CRUD de clientes utilizando la unidad de trabajo.

Espero que les sea de utilidad, porque ya tengo utilizando este patrón bastante tiempo y siempre que se viene un proyecto nuevo, esto ya es un estandard en la oficina.

Saludos!

Code4Fun!

Anuncios

10 comentarios sobre “Implementando el patrón Repositorio y Unidad de Trabajo (Unit of Work & Repository) – ASP.NET MVC 5

  1. Interesante.
    Desde mi ignorancia, lo veo claro para Entidades Simples.
    Lo que aún no veo con UnitOfWork cuando tienes que obtener datos de un Cliente y los Pedidos de un Cliente. Igual con ADO.NET a pelo se podría incluso en la misma consulta.Ahora con UnitOfWork serían dos consultas ?

    Lo mismo pasa al “guardar” datos, si guardas datos de un Pedido y Items del Pedido, tendrías dos UnitOfWork y además debería ser transaccional.

    Me gustaría indagar enlas buenas prácticas en este sentido.

    1. Hola Preguntón! (jaja)

      Yo siempre reviso con un inspector las consultas que EntityFramework me genera, y si lo haces de una forma errónea, se generarán dos consultas (o más) para consultar clientes y sus pedidos. Pero puedes hacer lo mismo que se hace con ADO usando las propiedades de navegación para poder acceder a las cosas relacionadas de un Entity en un solo Query. Esto ya es independiente si usamos repositorios y Units, pero es mágico por parte de EntityFramework ☺.

      Todos los repositorios pertenecen a un UnitOfWork y siempre deberías de trabajar con un Unit, esto para que todos los Entities estén en un mismo contexto, así que hagas lo que hagas (Create,Read,Update,Delete) todo estará vinculado en el mismo contexto y al momento de guardar cambios a nivel UnitOfWork se guardará en forma transaccional todos los cambios que se hayan hecho.

      Saludos 😀

  2. Me parece interesante pero estoy todavía un poco lejos de entender lo que explicas, podrías recomendarme algunos temas base para llegar a entender lo que explicas?

    1. Hola Roger,

      Te recomiendo este articulo http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

      El objetivo principal de esto es tener desacoplado la capa de datos, poder tener Unit Testing y más fácil implementar TDD. Y se hace uso de interfaces también se podría emplear en las pruebas fake data y mockups, cosa en la cual aun soy nuevo.

      Saludos!

  3. Muy interesante. Esta forma de desarrollo es la que estoy intentando implementar. ¿Cuándo podremos ver la implementación de servicios e inyección de dependencias?

  4. Muy interesante poder aplicar el manejo por capas en MVC. Me aclaraste muchas dudas gracias.
    Tengo un par de dudas, como funcionaria cuando tengo que consultar llaves combinadas (en todos los ejemplos que he visto usan llaves únicas) y la otra como implementas la capa de negocios-

    1. Que tal Carlos,

      Si te fijas, en el método FindAsync se declara así:

      FindAsync(params object[] keyValues)

      Entonces, si estamos hablando de una llave primaria compuesta, solo tendríamos que mandar un array de objectos indicando la llave compuesta, ejemplo:

      FindAsync(Llave1, Llave 2);

      Saludos!

  5. hola un saludo, estoy implementando el ejercicio que proporcionas, pero tengo una duda, que ya se a planteado en preguntas anteriores.
    la duda es como hacer la consulta por ejemplo: tengo la entidad “roles” a su ves la entidad “menús” entre las dos hay una relación de muchos a muchos, y se ocupa que al hacer un select, se obtenga los elementos relacionados, o como se puede hacer como en regularmente se ase con entity “un include(table)” para traer las relaciones.????? o si puedes proporcionar un ejemplo de como funciona el método “SelectQuery”

  6. muy bueno e interesante, pero tengo una duda, por lo que dices entonces ya no debería usar BeginTransaction y Rollback en caso de que algo salga mal para deshacer todo lo realizado??

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s