Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 2 de abril de 2019
ASP.NET Core Hace unas semanas, el amigo J. Roldán escribía un comentario en el post Inyección de dependencias en ASP.NET Core con una pregunta interesante sobre la forma de registrar determinados componentes en el inyector de dependencias. Básicamente la duda que planteaba era cómo asociar distintas interfaces a una única instancia, algo que, aunque no es complicado de conseguir, tampoco tiene una solución precisamente intuitiva.

El problema

En el escenario concreto que planteaba nuestro querido lector era que quería proporcionar, a los componentes que necesitaban acceder a datos en su aplicación, un acceso limitado al contexto de datos de Entity Framework.

Para ello, por un lado definía una interfaz parecida a la siguiente, que proporcionaba acceso a los repositorios:
public interface IUserRepositories
{
    DbSet<User> Users { get; }
    ...
}
Por otra parte, definía otra interfaz que era la que permitía comprometer cambios (adiciones, supresiones, modificaciones) realizadas en el contexto de datos:
public interface IUnitOfWork
{
    int SaveChanges();
}
La idea de esta separación es que si un componente necesitaba exclusivamente consultar datos relativos a usuarios, sólo recibiría mediante inyección de dependencias la instancia de IUserRepositories, mientras que si necesitaba persistir datos, recibiría adicionalmente un objeto IUnitOfWork, por ejemplo:
public class UserServices
{
    ...
    public UserServices(IMapper mapper, IUserRepositories userRepos, IUnitOfWork uow) 
    { 
        _mapper = mapper;
        _userRepos = userRepos;
        _uow = uow;
    }

    public async Task Update(int id, UserDto user) 
    {
        var user = await _userRepos.Users.FirstOrDefaultAsync(u=>u.Id == id);
        if(user == null)
            throw new Exception();
        
        _mapper.Map(userDto, user);
        await _uof.SaveChangesAsync();
    }
}
Bien, pues el problema lo tenía precisamente a la hora de registrar las dependencias de forma apropiada.

Si, como es lo habitual, registraba ambos componentes como scoped, las instancias del contexto de datos que recibía eran distintas, por lo que los cambios realizados en una no eran visibles para la otra, aparte de la sobrecarga que supone crear dos contextos en cada petición:
// En ConfigureServices():

services.AddScoped<IRepositories, EfContext>();
services.AddScoped<IUnitOfWork, EfContext>();

// En UserServices:
public class UserServices
{
    ...
    public UserServices(IUserRepositories userRepos, IUnitOfWork uow, IMapper mapper) 
    { 
        if(userRepos != uow)                // userRepos contiene una instancia del contexto
            throw new Exception("Ooops!");  // uow contiene otra instancia distinta del contexto
        _mapper = mapper;
        _userRepos = userRepos;
        _uow = uow;
    }
    ...
}

La solución

Obviamente, lo que hay que cambiar es la forma de registrar las interfaces, de forma que ambas estén asociadas a la misma instancia del contexto de datos, única en el ámbito de una petición.

Para ello, podemos hacer lo que se muestra en el siguiente código:
services.AddScoped<IUserRepositories, EfContext>();
services.AddTransient<IUnitOfWork>(provider => (IUnitOfWork)provider.GetService<IUserRepositories>());
Como podemos observar, en primer lugar asociamos la interfaz IUserRepositories a una instancia per request del contexto de datos EfContext. Hasta ahí, lo normal.

La diferencia viene ahora: la interfaz IUnitOfWork la registramos utilizando la sobrecarga que permite especificar la factoría que será utilizada para obtener la instancia, y desde ella retornamos el objeto que ya esté asociado a IUserRepositories.

Sencillo, ¿verdad?

Publicado en: www.variablenotfound.com.

Aún no hay comentarios, ¡sé el primero!