inyección inyeccion dependencias c# asp.net-mvc dependency-injection ef-code-first entity-framework-6

c# - inyeccion - Código EF6 Primero con repositorio genérico e inyección de dependencia y SoC



inyeccion de dependencias youtube (1)

Después de leer y probar muchas cosas con la última versión estable de Entity Framework (6.1.1).

Estoy leyendo muchas contradicciones sobre si usar o no los repositorios con EF6 o EF en general, porque DbContext ya proporciona un repositorio y DbSet el UoW , UoW .

Permítanme primero explicar qué contiene mi solución en términos de proyecto y luego volveré a la contradicción.

Tiene un proyecto de biblioteca de clases y un proyecto asp.net-mvc . El proyecto class lib es el acceso a los datos y donde las Migrations están habilitadas para Code First .

En mi proyecto de clase lib tengo un repositorio genérico:

public interface IRepository<TEntity> where TEntity : class { IEnumerable<TEntity> Get(); TEntity GetByID(object id); void Insert(TEntity entity); void Delete(object id); void Update(TEntity entityToUpdate); }

Y aquí está la implementación de ello:

public class Repository<TEntity> where TEntity : class { internal ApplicationDbContext context; internal DbSet<TEntity> dbSet; public Repository(ApplicationDbContext context) { this.context = context; this.dbSet = context.Set<TEntity>(); } public virtual IEnumerable<TEntity> Get() { IQueryable<TEntity> query = dbSet; return query.ToList(); } public virtual TEntity GetByID(object id) { return dbSet.Find(id); } public virtual void Insert(TEntity entity) { dbSet.Add(entity); } public virtual void Delete(object id) { TEntity entityToDelete = dbSet.Find(id); Delete(entityToDelete); } public virtual void Update(TEntity entityToUpdate) { dbSet.Attach(entityToUpdate); context.Entry(entityToUpdate).State = EntityState.Modified; } }

Y aquí algunas entidades:

public DbSet<User> User{ get; set; } public DbSet<Order> Orders { get; set; } public DbSet<UserOrder> UserOrders { get; set; } public DbSet<Shipment> Shipments { get; set; }

No sé qué repetir, pero con EF6 ya no pasas los repositorios, sino DbContext . Así que para DI he configurado lo siguiente en el proyecto asp-net-mvc Ninject usando Ninject :

private static void RegisterServices(IKernel kernel) { kernel.Bind<ApplicationDbContext>().ToSelf().InRequestScope(); }

Y esto inyectará ApplicationDbContext través de la inyección del constructor a las clases de la capa superior cuando corresponda.

Ahora volviendo a la contradicción.

Si ya no necesitamos un repositorio porque EF ya lo proporciona de forma inmediata, ¿cómo hacemos la Separation of Concern (abreviada como SoC en el título)?

Ahora corríjame si me equivoco, pero me suena como que solo necesito hacer toda la lógica / cálculos de acceso a datos (como agregar, buscar, actualizar, eliminar y algunos cálculos / lógica personalizados aquí y allá (específico de la entidad)) en el proyecto asp.net-mvc , si no agrego un repositorio.

Cualquier luz sobre este asunto es realmente apreciada.


Una pequeña explicación, con suerte, aclarará su confusión. El patrón de repositorio existe para abstraer la conexión de la base de datos y la lógica de consulta. Los ORM (mapeadores objeto-relacionales, como EF) han existido de una forma u otra durante tanto tiempo que muchas personas han olvidado o nunca han tenido la inmensa alegría y el placer de tratar con el código de espagueti lleno de consultas y declaraciones SQL. Era hora de que si quería consultar una base de datos, era realmente responsable de cosas locas como iniciar una conexión y construir sentencias de SQL desde ether. El punto del patrón del repositorio era darte un lugar único para poner toda esta maldad, lejos de tu hermoso código de aplicación prístino.

Avance rápido a 2014, Entity Framework y otros ORM son su repositorio. Toda la lógica SQL está empaquetada perfectamente lejos de sus miradas indiscretas, y en su lugar, tiene una buena API programática para usar en su código. En un aspecto, eso es suficiente abstracción. Lo único que no cubre es la dependencia del propio ORM. Si más tarde decide que desea cambiar Entity Framework por algo como NHibernate o incluso una API web, tiene que hacer una cirugía en su aplicación para hacerlo. Como resultado, agregar otra capa de abstracción sigue siendo una buena idea, pero no es un repositorio, o al menos digamos un repositorio típico.

El repositorio que tienes es un repositorio típico . Simplemente crea proxies para los métodos API de Entity Framework. repo.Add a repo.Add y al repositorio calles context.Add . Francamente, es ridículo, y es por eso que muchos, incluido yo mismo, dicen que no usamos repositorios con Entity Framework .

Entonces, ¿ qué debes hacer? Cree servicios, o tal vez sea mejor decirlo como "clases similares a servicios". Cuando los servicios comienzan a ser discutidos en relación con .NET, de repente estamos hablando de todo tipo de cosas que son completamente irrelevantes para lo que estamos discutiendo aquí. Una clase similar a un servicio es como un servicio en el sentido de que tiene puntos finales que devuelven un conjunto particular de datos o realizan una función muy específica en algún conjunto de datos. Por ejemplo, mientras que con un repositorio típico te encontrarías haciendo cosas como:

articleRepo.Get().Where(m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now).OrderByDescending(o => o.PublishDate)

Su clase de servicio funcionaría como:

service.GetPublishedArticles();

Ver, toda la lógica para lo que califica como un "artículo publicado" está claramente contenida en el método de punto final. Además, con un repositorio, todavía estás exponiendo la API subyacente. Es más fácil cambiar con otra cosa porque el almacén de datos base está abstraído, pero si la API para realizar consultas en ese almacén de datos aún está en la cima.

ACTUALIZAR

La configuración sería muy similar; la diferencia radica principalmente en cómo utiliza un servicio en lugar de un repositorio. Es decir, ni siquiera lo haría entidad dependiente. En otras palabras, esencialmente tendría un servicio por contexto, no por entidad.

Como siempre, comienza con una interfaz:

public interface IService { IEnumerable<Article> GetPublishedArticles(); ... }

Entonces, su implementación:

public class EntityFrameworkService<TContext> : IService where TContext : DbContext { protected readonly TContext context; public EntityFrameworkService(TContext context) { this.context = context; } public IEnumerable<Article> GetPublishedArticles() { ... } }

Entonces, las cosas empiezan a ponerse un poco peludas. En el método de ejemplo, simplemente podría hacer referencia al DbSet directamente, es decir, al contexto. DbSet , pero eso implica conocer los nombres de DbSet en el contexto. Es mejor usar context.Set<TEntity>() , para una mayor flexibilidad. Sin embargo, antes de saltar los trenes, quiero señalar por qué llamé a este EntityFrameworkService . En su código, solo haría referencia a su interfaz IService . Luego, a través de su contenedor de inyección de dependencia, puede sustituir EntityFrameworkService<YourContext> para eso. Esto abre la posibilidad de crear otros proveedores de servicios como quizás WebApiService , etc.

Ahora, me gusta usar un solo método protegido que devuelva un consultable que todos mis métodos de servicio puedan utilizar. Esto elimina una gran parte del crucero como tener que inicializar una instancia DbSet cada vez a través de var dbSet = context.Set<YourEntity>(); . Eso se vería un poco como:

protected virtual IQueryable<TEntity> GetQueryable<TEntity>( Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = null, int? skip = null, int? take = null) where TEntity : class { includeProperties = includeProperties ?? string.Empty; IQueryable<TEntity> query = context.Set<TEntity>(); if (filter != null) { query = query.Where(filter); } foreach (var includeProperty in includeProperties.Split (new char[] { '','' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { query = orderBy(query); } if (skip.HasValue) { query = query.Skip(skip.Value); } if (take.HasValue) { query = query.Take(take.Value); } return query; }

Note que este método es, primero, protegido. Las subclases pueden utilizarlo, pero esto definitivamente no debería ser parte de la API pública. El punto central de este ejercicio es no exponer las consultas. Segundo, es genérico. En otras palabras, puede manejar cualquier tipo que le lances, siempre y cuando haya algo en el contexto para ello.

Luego, en nuestro pequeño método de ejemplo, terminarías haciendo algo como:

public IEnumerable<Article> GetPublishedArticles() { return GetQueryable<Article>( m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now, m => m.OrderByDescending(o => o.PublishDate) ).ToList(); }

Otro buen truco para este enfoque es la capacidad de tener métodos de servicio genéricos utilizando interfaces. Digamos que quería poder tener un método para publicar algo . Podría tener una interfaz como:

public interface IPublishable { PublishStatus Status { get; set; } DateTime PublishDate { get; set; } }

Entonces, cualquier entidad que sea publicable simplemente implementaría esta interfaz. Con eso en su lugar, ahora puedes hacer:

public IEnumerable<TEntity> GetPublished<TEntity>() where TEntity : IPublishable { return GetQueryable<TEntity>( m => m.Status == PublishStatus.Published && m.PublishDate <= DateTime.Now, m => m.OrderByDescending(o => o.PublishDate) ).ToList(); }

Y luego en su código de aplicación:

service.GetPublished<Article>();