viernes, 22 de julio de 2016

Entity Framework Core 1.0. Shadow Properties

Ayer estuve en una charla de Unai Zorrilla sobre las novedades que implementa la nueva versión de EF, denominada EF Core.

Hay muchas (y muy buenas y otras por llegar), pero una que me hizo esbozar una pequeña sonrisilla fue la inclusión de las Shadow Properties.

Qué es una Shadow Propery

Una shadow property es una propiedad que no pertenece a nuestra Entidad. Lo primero que se vino a la cabeza son los campos de auditoría que tenemos en las tablas de nuestra base de datos (quién crea, cuándo se crea, quién modifica, cuándo se modifica).
No parece tener mucho sentido andar con estos atributos arriba y abajo en nuestra aplicación cuando realmente no se usan. De esta forma sólo los usaremos cuándo se produzca una actualización de la entidad.

El valor de estas propiedades es mantenido por el Change Tracker API.

Cómo definir una Shadow property

Imaginemos que tenemos este modelo


  1. public class Orders  
  2. {  
  3.     public int ID  
  4.     {  
  5.         get;  
  6.         set;  
  7.     }  
  8.     public string OrderNumber{  
  9.         get;  
  10.         set;  
  11.     }  
  12. }  
  13.   
  14. // Definition of DbContext class  
  15. public class MyContext: DbContext   
  16. {  
  17.     public DbSet < Orders > Orders {  
  18.         get;  
  19.         set;  
  20.     }  
  21.   
  22.     protected override void OnModelCreating(ModelBuildermodelBuilder)  
  23.     {  
  24.   
  25.     }  
  26. }  

Lo que tenemos que hacer, sobreescribir el evento OnModelCreating de nuestro dbContext y definir la shadow  property

  1. protected override void OnModelCreating(ModelBuildermodelBuilder)  
  2. {  
  3.     modelBuilder.Entity < Orders > ()  
  4.         .Property < DateTime > ("CreatedDate");  

  5. base.OnModelCreating(builder);
  6. }



Lo que tenemos que hacer, sobreescribir el evento OnModelCreating de nuestro dbContext y definir la shadow  property

  1. //Por simplicidad no hago comprobaciones ni miro el estado de la entidad
  2. public override int SaveChanges()
  3. {
  4. MyContext.Entry(Orders  ).Property("CreatedDate").CurrentValue = DateTime.Now; 

  5. return base.SaveChanges();
  6. }



Aquí os dejo un link con la documentación de Microsoft: EF Core 1.0 Shadow Properties





jueves, 23 de junio de 2016

Rejillas fluidas con Bootstrap

Bueno, hemos visto conceptos de Diseño Web Adaptable que nos permitirían ser "autosuficientes" en el desarrollo de nuestras páginas.
Pero digamos que el proceso es un poco tedioso y repetitivo y aquí encontramos frameworks de responsive design que vienen a nuestra ayuda; hay varios, pero yo el que más conozco y más utilizo es Bootstrap.

Bootstrap fue creado por desarrolladores de Twitter para intentar unificar criterios de forma interna. Les gustó la idea y cómo quedó y decidieron publicarlo bajo licencia Open Source.

Yo empecé a usarlo no porque me ofreciera Responsive Design, sino porque me ofrecía una serie de elementos ya definidos (botones, menús, ...) que me facilitaban mucho hacer que mis desarrollos fueran tuvieran los mismos criterios.
Poco a poco me fui dando cuenta de que me facilitaba el hacer mis páginas "responsive".

Bootstrap utiliza la aproximación "mobile first", por defecto tiene una rejilla de doce columnas y utiliza media queries para saber qué estilo aplicar en cada caso.
Los criterios que usa Bootstrap, por defecto, son estos:

- Dispositivos muy pequeños : < 768px
- Dispositivos pequeños: >= 768px
- Dispositivos medianos: >= 992px
- Dispositivos grandes : >=1200px


Hay que tener en cuenta que la estructura básica de una página que utilice Bootstrap es de la siguiente manera:

Contenedor
                 - > Fila
                              - > Columna(s)

Un ejemplo sería:
<div class="container">
   <div class="row">
      <div class="col-xs-12 col-sm-6 col-md-8">.col-xs-12 .col-sm-6 .col-md-8</div>
      <div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
   </div>
 
   <div class="row">
      <div class="col-xs-6 col-sm-4">.col-xs-6 .col-sm-4</div>
      <div class="col-xs-6 col-sm-4">.col-xs-6 .col-sm-4</div>
   </div>
</div>

¿Qué estamos definiendo?
Bueno, vemos que la primera capa, es un contenedor, seguido de una capa que es una fila.
Después definimos el tamaño de las columnas dependiendo del dispositivo utilizando en patrón:
col-YY-XX, donde YY indica para qué dispositivos se aplica el estilo y XX es el número de columnas que utiliza para ese dispositvo.
xs -> (Dispositivos muy pequeños) Xtra Small devices
sm -> (Dispositivos pequeños) Small devices
md -> (Dispositivos medianos) Medium Devices

Por lo tanto, cuando ponemos
<div class="col-xs-12 col-sm-6 col-md-8">.col-xs-12 .col-sm-6 .col-md-8</div>
lo que estamos haciendo es definir:
- Para dispositivos muy pequeños, el tamaño será del 100%
- Para dispositivos pequeños, el tamaño será del 50%
- Para dispositivos medianos, el tamaño será de 2/3.

Os dejo un enlace a la propia página de Bootstrap para que lo veáis vosotros mismos: http://getbootstrap.com/css/

lunes, 16 de mayo de 2016

Diseño responsive. Media Queries.

Siguiendo con el tema del diseño responsive, hay que hablar de uno de sus pilares. Las media queries.
Las media queries, tienen su origen en los media types disponible en CSS2.

Con los media types, se podían definir diferentes estilos dependiendo del medio en el que se esté "visualizando" la página.

Por ejemplo:
<link href="main.css" media="all" rel="stylesheet" type="text/css"></link>
<link href="print.css" media="print" rel="stylesheet" type="text/css"></link>
Aquí estamos indicando que queremos usar main.css en todos los casos ("all") pero en el caso concreto de la impresora ("print") utilizaremos print.css

Los diferentes tipos de media son

  • all: Valor por defecto. Aplica a todos los media
  • print: Aplica en la impresión (y en la vista previa si está disponible)
  • screen: Pantalla de ordenador
  • tv: Televisores.

Las media queries evolucionan de los media type, proporcionando información no solo del dispositivo, sino también de ciertas características del mismo como por ejemplo el ancho.

En el ejemplo de la entrada anterior, ¿cómo hacemos que el contenido se recoloque en función al tamaño del dispositivo?
Pues un ejemplo puede ser este:



<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Ejemplo Media Queries</title>
<style>
@media screen and (min-width: 480px){
#derecha { width: 98%; float:none }
#izquierda { width: 98%; float:none }
}

@media screen and (min-width: 800px){
#derecha { width: 49%; float:left }
#izquierda { width: 49%; float:left }
} </style>
</head>
<body>

<div id="derecha" style="background-color:aqua;">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc,
</div>
<div id="izquierda">
Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. Ma quande lingues coalesce, li grammatica del resultant lingue es plu simplic e regulari quam ti del coalescent lingues. Li nov lingua franca va esser plu simplic e regulari quam li existent Europan lingues. It va esser tam simplic quam Occidental in fact, it va esser Occidental. A un Angleso it va semblar un simplificat Angles, quam un skeptic Cambridge amico dit me que Occidental es.Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. Por scientie, musica, sport etc, litot Europa usa li sam vocabular. Li lingues differe solmen in li grammatica, li pronunciation e li plu commun vocabules. Omnicos directe al desirabilite de un nov lingua franca: On refusa continuar payar custosi traductores. At solmen va esser necessi far uniform grammatica, pronunciation e plu sommun paroles. </div>
</body>
</html>

Como veis, para los dispositivos más pequeños (mínimo 480px de ancho) las capas se verán una encima de otra; cuando el ancho supere los 800px, las capas se verán una al lado de otra. Hemos conseguido que el contenido se recoloque dependiendo de la anchura del dispositivo.

Para más información, podéis visitar este enlane: https://www.w3.org/TR/css3-mediaqueries/
Aquí os dejo un enlace con sitios web que utilizan media queries http://mediaqueri.es/

domingo, 8 de mayo de 2016

Diseño web adaptable

Últimamente me he dado cuenta de que el desarrollo de front-end es más "agradecido" que el desarrollo de back-end.
En el back-end hay sesudas discusiones sobre el uso de IoC, capas, .... que sólo tenemos los desarrolladores o arquitectos de software.
El front-end es más vistoso y el usuario es lo que termina viendo.

Cuántas veces hemos hecho un desarrollo bien organizado, con sus capas bien hechas, bien orgullosos de nuestro trabajo ... y cuando lo ve el usuario pone de cara de besugo diciendo "Esto qué es??!!" (Por favor, decidme que os pasado!)

Hay que decir que el front-end es algo más que poner iconos bonitos y colorines vistosos; hay que hacer una experiencia de usuario agradable, bonito y usable. He de reconocer de que es uno de mis puntos débiles, pero intento controlar la parte técnica, esperando que algún día mis niveles de dopamina lleven mi creatividad a un "estado normal" ;-)

Hoy voy a hablar del diseño web adaptable (RWD -Responsive Web Design- son sus siglas en inglés), que dicho en román paladino y resumiendo mucho, es hacer que nuestra página se vea en cualquier dispositivo (ordenador, tablet, smartphone, ....).
Para ello, haremos que nuestro contenido se redimensione y se coloque según las necesidades. Es decir, el contenido fluye utilizando media queries de CSS3.

Esto implica la necesidad de pararse a pensar un poco (bastante) el diseño antes de empezar a picar código, pero tiene la gran ventaja de que el mismo desarrollo sirve para cualquier dispositivo con la consiguiente ganancia en mantenibilidad de nuestro sitio y visibilidad de nuestros contenidos.

Es importante tener en cuenta eso de "el contenido fluye", que el contenido se coloque.

En el siguiente ejemplo, vemos que el contenido se redimensiona:



@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>pruebasinRWD</title>
</head>
<body>
<div style="width:48%; background-color:aqua; float:left">
Lorem ipsum dolor sit amet, consectetuer adipiscing elit..
</div>
<div style="width:48%; float:left">
Li Europan lingues es membres del sam familie...
</div>
</body>
</html>

El resultado que obtenemos es:



Y si redimensionamos la pantalla




Vemos que el contenido se ha redimensionado correctamente, pero quizás debería haberse recolocado de otra manera para hacer la página más visible en cualquier dispositivo como vemos a continuación











Perdón

Lo primero quería pedir perdón a mis lectores por el tiempo que he estado sin publicar nada. Falta de tiempo y problemas con el ordenador me han puesto difícil escribir. También quería pediros perdón porque voy a cambiar un poco la estructura que tenía pensada inicialmente para el blog. Voy a hacer posts independientes hablando sobre temáticas más variadas. Voy a reemprender mi andadura por estos lares haciendo una reflexión sobre algo que me ha pasado esta semana. Actualmente trabajo para el departamento de desarrollo de una empresa de formación online. En una de las reuniones semanales para ver el estado de los proyectos, el jefe de proyecto lanzó una frase: - "Hay que desarrollar un módulo nuevo. Lo vamos a hacer con .NET Core y Bootstrap" - "¿Y porqué .NET Core?", le pregunté yo - "Porque es lo último", respondió. "Además es muy fácil" me dijo enseñándome link a un tutorial. - "Bueno, ¿pero es lo que necesitamos?. ¿Hay personal con la suficiente formación en el departamento para abordarlo?", le repliqué. - "Víctor eres un miedica (realmente empleó otra expresión que no prefiero no poner aquí). ¿Ves? Abres Visual Studio, clickas en proyecto nuevo, ..... y sigues el tutorial". - "Y con Bootstrap, lo mismo", me dijo abriendo la página principal de Boostrap. "¿Ves? Si tienes los estilos ya definidos". - "Bueno, pero hacer diseño responsive, es algo más que poner un enlace a Boostrap", le dije ya un poco molesto. - "Anda, anda, que eres un miedica...", terminó diciendo de nuevo. Que esta conversación hubiera sido con una persona que no desarrolla software, me hubiera parecido normal; pero con el jefe de proyecto me pareció preocupante. Desarrollar software es más que abrir Visual Studio (o el IDE que se use en cada caso) y empezar a picar código, copiando de aquí y de allí. Primero creo que hay que entender que tipos de arquitectura hay, que ofrece cada tecnología, ... para saber cuándo aplicar una u otra. No siempre DDD es la mejor solución, ni hacer una aplicación Web aplica en todos los casos, ni una base de datos relacional es la mejor solución siempre. Tampoco creo que la solución pase por reuniones interminables filosofando sobre arquitecturas o si TypeScript es mejor que .... o peor que ... y al final se hace una aplicación complicada de desarrollar y mantener. Creo que hay que pensar en hacer las cosas simples y fáciles de entender y mantener. Hay que ser consciente de la gente que tenemos en el equipo, de los tiempos de entrega que tenemos y de las aplicaciones que desarrollamos. ¿O no? ¿Seré de verdad un miedica?

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.