Guillermo Valencia - Blog 
Este sitio web utiliza JavaScript. Por favor, habilítalo en tu navegador para que todo funcione correctamente.


El Patrón Unit of Work, manejando el contexto de Entity Framework

Guillermo Valencia

Publicado el 05 de febrero de 2018

Tags: C#, Patrones

Para la persistencia de datos siempre es recomendable utilizar un ORM (mapeo objeto-relacional) como Entity Framework (EF). A la hora de utilizar EF, hay que tener especial cuidado con el manejo del contexto de la BD , que implementa el patrón Unit of Work.

El patrón Unit of Work se explica aquí: https://coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/

En el siguiente artículo se proponen una serie de recomendaciones sobre el manejo del contexto de EF: http://mehdi.me/ambient-dbcontext-in-ef6/

A continuación, incluyo un ejemplo de implementación del patrón Unit o Work basado en una variable “Thread Static”, ideal en entornos web, puesto que su tiempo de vida es por petición, y permite mantener cacheado el contexto de EF para una transacción completa entre las diferentes peticiones de usuario.


using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

namespace MiEmpresa.MiAplicacion.Infrastructure.Services
{
    /// <summary>
    /// Ambito de vida "per request" del patrón unit of work.
    /// </summary>
    /// <typeparam name="TDbContext">The type of the DbContext</typeparam>
    public static class UnitOfWorkScope<TDbContext>  where TDbContext : DbContext, new()
    {
        /// <summary>
        /// Clase que encapsula el contexto real de la BD
        /// </summary>
        private class ScopedDbContext<T> : IDisposable where T : TDbContext, new()
        {
            /// <summary>
            /// Constructor
            /// </summary>
            public ScopedDbContext()
            {
                DbContext = new T();
                DbContext.Database.CommandTimeout = 100;
                ((IObjectContextAdapter)DbContext).ObjectContext.SavingChanges
                    += GuardAgainstDirectSaves;
            }
            
            /// <summary>
            /// Destructor
            /// </summary>
            ~ScopedDbContext()
            {
                Dispose(false);
            }


            /// <summary>
            /// El contexto real DbContext.
            /// </summary>
            public TDbContext DbContext { get; private set; }

            /// <summary>
            /// Evitamos llamadas directas a SaveChanges.
            /// </summary>
            public bool AllowSaving = false;

            /// <summary>
            /// Manejador para verificar las llamadas directas a SaveChanges.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void GuardAgainstDirectSaves(object sender, EventArgs e)
            {
                if (!AllowSaving)
                {
                    throw new InvalidOperationException("No realices llamadas directas a SaveChanges en un contexto perteneciente a UnitOfWorkScope. Utiliza UnitOfWorkScope.SaveChanges en su lugar.");
                }
            }

            /// <summary>
            /// Liberación explicita
            /// </summary>
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            /// <summary>
            /// Liberación implícita
            /// </summary>
            /// <param name="disposing"></param>
            protected virtual void Dispose(bool disposing)
            {
                if (disposing && DbContext != null)
                {
                    ((IObjectContextAdapter)DbContext).ObjectContext.SavingChanges
                        -= GuardAgainstDirectSaves;

                    DbContext.Dispose();
                    DbContext = null;
                }                
            }
        }

        /// <summary>
        /// Variable para almacenar el contexto de BD
        /// Se trata de una variable ThreadStatic, ideal para ser utilizada en web (una instancia por Request) y en servicios Windows.
        /// Para Windows Forms puede generar datos sucios puesto que la variable se quedaría a nivel de formulario.
        /// </summary>
        [ThreadStatic]
        private static ScopedDbContext<TDbContext> scopedDbContext;        

        /// <summary>
        /// Retorna el DbContext que utiliza este unit of work.
        /// </summary>
        public static TDbContext Context
        {
            get
            {
                if (scopedDbContext == null)
                {
                    scopedDbContext = new ScopedDbContext<TDbContext>();                    
                }
                return scopedDbContext.DbContext;
            }
        }

        /// <summary>
        /// Salva los cambios en el contexto
        /// </summary>
        public static void SaveChanges()
        {
            scopedDbContext.AllowSaving = true;
            scopedDbContext.DbContext.SaveChanges();
            scopedDbContext.AllowSaving = false;
        }

        /// <summary>
        /// Libera el contexto
        /// </summary>
        public static void Release()
        {
            if (scopedDbContext != null)
            {
                scopedDbContext.Dispose();
                scopedDbContext = null;
            }
        }
    }
}

 

También incluyo un ejemplo de “repositorio base” que puede ser utilizado para la implementación de los diferentes repositorios de entidades contra EF. Dicho “repositorio base” hace uso de “generics” para evitar el acoplamiento a un modelo o entidad concreta.


using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace MiEmpresa.MiAplicacion.Infrastructure.Data.Repositories
{
    /// <summary>
    /// Repositorio base de las operaciones contra BD
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal interface IBaseRepository<T> where T : class
    {
        /// <summary>
        /// Agrega una entidad
        /// </summary>
        /// <param name="entity"></param>
        void Add(T entity);

        /// <summary>
        /// Elimina una entidad por su identificador
        /// </summary>
        /// <param name="id"></param>
        void Delete(int id);

        /// <summary>
        /// Elimina una entidad
        /// </summary>
        /// <param name="entity"></param>
        void Delete(T entity);

        /// <summary>
        /// Obtiene todas las entidades
        /// </summary>
        /// <returns></returns>
        List<T> GetAll();

        /// <summary>
        /// Proporciona una entidad por su identificador
        /// </summary>
        T GetById(int id);

        /// <summary>
        //Libera el contexto
        /// </summary>
        void Release();

        /// <summary>
        /// Salva los cambios
        /// </summary>
        void Save();

        /// <summary>
        /// Realiza una búsqueda
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        List<T> SearchFor(Expression<Func<T, bool>> predicate);
    }


    /// <summary>
    /// Repositorio base de las operaciones contra BD
    /// </summary>
    /// <typeparam name="C"></typeparam>
    /// <typeparam name="T"></typeparam>
    internal class BaseRepository<C,T> : IBaseRepository<T> where T : class where C : DbContext, new()
    {
        /// <summary>
        /// Constructor sin parámetros
        /// </summary>
        public BaseRepository() { }

        /// <summary>
        /// Acceso al contexto de EF
        /// </summary>
        protected DbContext Entities
        {
            get
            {
                return UnitOfWorkScope<C>.Context;
            }
        }

        /// <summary>
        /// Proporciona una entidad por su identificador
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public T GetById(int id)
        {
            return Entities.Set<T>().Find(id);
        }

        /// <summary>
        /// Agrega una entidad
        /// </summary>
        /// <param name="entity"></param>
        public void Add(T entity)
        {
            Entities.Set<T>().Add(entity);
        }

        /// <summary>
        /// Elimina una entidad
        /// </summary>
        /// <param name="entity"></param>
        public void Delete(T entity)
        {
            Entities.Set<T>().Remove(entity);
        }

        /// <summary>
        /// Elimina una entidad por su identificador
        /// </summary>
        /// <param name="id"></param>
        public void Delete(int id)
        {
            T entity = GetById(id);
            Delete(entity);
        }

        /// <summary>
        /// Obtiene todas las entidades
        /// </summary>
        /// <returns></returns>
        public List<T> GetAll()
        {
            return Entities.Set<T>().ToList();
        }

        /// <summary>
        /// Realiza una búsqueda
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public List<T> SearchFor(Expression<Func<T, bool>> predicate)
        {
            return Entities.Set<T>().Where(predicate).ToList();
        }

        /// <summary>
        /// Salva los cambios
        /// </summary>
        public void Save()
        {            
            UnitOfWorkScope<PoolEntities>.SaveChanges();
        }

        /// <summary>
        /// Libera el contexto
        /// </summary>
        public void Release()
        {
            UnitOfWorkScope<PoolEntities>.Release();
        }

    }
}


Manipulación de objetos no manejados

Guillermo Valencia

Publicado el 08 de enero de 2018

Tags: C#, Patrones

En .NET Framework existe un mecanismo llamado “Garbage Collector”, GC o recolector de basura que se encarga de liberar aquellos objetos que ya no son necesarios o cuya referencia se ha perdido.
Esto supone una gran ventaja para los desarrolladores, ya que no se tienen que ocupar de dicha tarea, encargándose el propio GC de evitar la fragmentación de la memoria o su corrupción, evitando así las posibilidades de introducir errores adicionales en situaciones que no son triviales como sí ocurre con otros lenguajes de programación menos avanzados (C, C++).

El GC de .NET es un colector de basura generacional. Eso significa que clasifica los objetos en distintas generaciones, lo cual le permite realizar colecciones de basura parciales (de una o varias generaciones) y así evitar hacer siempre colecciones de basura completas de todo el heap de memoria del CLR de .NET. Esta característica es una de las más importantes en cuanto al rendimiento del GC, y permiten que el GC de .NET sea escalable para aplicaciones de alta concurrencia como por ejemplo aplicaciones ASP.NET.

El comportamiento del GC no exime por completo al desarrollador del uso de la memoria de la aplicación, ya que existen recursos que no son manejados por el CLR de .NET como son los recursos nativos que acceden por ejemplo a sistemas externos (BD, Servicios Web), acceso a ficheros o a APIS que interaccionan con el sistema operativo o con el hardware del sistema (tarjeta de video).
A continuación, se exponen los objetos más típicos con los que es necesario tener especial cuidado, pero hay muchos más, en general cualquiera que implemente la interfaz IDisposable:

  • Objetos de memoria no manejados: Stream, MemoryStream.
  • Objetos de conexión a BD: SqlConnection, DbConnection, SqlCommand, DbCommand, DataSet, SqlDataReader.
  • Clientes de WCF: ClientBase.
  • Modelo de Entity Framework: DbContext.
  • Acceso a recursos externos: WebClient, WebRequest, TcpClient.
  • Acceso a ficheros: FileStream, FileReader.

Un uso incorrecto de este tipo de objetos lastra sustancialmente el rendimiento general de la aplicación, ocasionando un gran uso de memoria en el lado del servidor.

Cuando trabajemos con objetos no manejados, es importante que seamos nosotros los responsables y encargados de liberar dichos objetos. Para ellos nos podemos ayudar del patrón “Dispose”: https://docs.microsoft.com/es-es/dotnet/standard/design-guidelines/dispose-pattern#basic-dispose-pattern

A continuación, muestro un ejemplo de implementación en C#:


/// <summary>
/// Ejemplo de clase que contiene objetos no manejados y que implementa la interfaz IDisposable
/// </summary>
private class MyCustomUnSafeObject : IDisposable
{
   /// <summary>
   /// Instancia de un objeto no manejado, que habrá que liberar adecuadamente
   /// </summary>
   private UnsafeObject instance;

   /// <summary>
   /// Constructor
   /// </summary>
   public MyCustomUnSafeObject()
   {
      instance= new UnsafeObject();      
   }
            
   /// <summary>
   /// Destructor
   /// </summary>
   ~MyCustomUnSafeObject()
   {
      Dispose(false);
   }

   /// <summary>
   /// Liberación explícita
   /// </summary>
   public void Dispose()
   {
      Dispose(true);
      GC.SuppressFinalize(this);
   }

   /// <summary>
   /// Liberación implícita
   /// </summary>
   protected virtual void Dispose(bool disposing)
   {
      if (disposing && instance!= null)
      {
         instance.Dispose();
         instance = null;
      }                
   }
}

Es muy importante implementar el destructor en este caso, ya que es el mecanismo que nos proporciona .NET para ejecutar código de finalización de un objeto de forma automática, antes de que el GC libere su memoria.

Este destructor debe ser muy rápido y óptimo, y nunca puede lanzar una excepción ni tampoco bloquear la ejecución debido a que todos los destructores de los objetos se ejecutan en un thread conocido como Finalizer.

Cuando el GC determina que los objetos están listos para ser finalizados los mete en una cola y dicho thread ejecuta los destructores de cada objeto secuencialmente. Por lo tanto, si se produce un bloqueo durante la finalización de alguno de los objetos, todo lo que venga detrás nunca será finalizado con todas las consecuencias que ello conlleva.
Para maximizar el rendimiento de nuestras aplicaciones .NET, debemos procurar que la menor cantidad posible de objetos sean finalizados por el GC, es decir, liberándolos nosotros manualmente.