简体   繁体   中英

Problem with Dependency Injection if the interface is not instantiated

I'm developing an API, using EntityFramework. Everything was going well.

namespace ControlTec.Controllers

{

    [Route("api/[controller]")]
    [ApiController]
    public class ZoneController : Controller, IBaseController<Zone>, IBaseRules<Zone>
    {
        private readonly IBaseRepository<Zone> _zoneRepository;
        public const int ID_INSERT = 0;
        public ZoneController(IBaseRepository<Zone> zoneRepository)
        {
            _zoneRepository = zoneRepository;
        }

        [HttpGet]
        public async Task<ActionResult<List<Zone>>> GetAll()
        {
            return await _zoneRepository.GetAll();
        }
    }
}

namespace ControlTec.Models
{
    public interface IBaseRepository<T> where T : new()
    {
        Task<T> Add(T objModel);
        Task<T> Update(T objModel);
        Task<T> Remove(int id);
        Task<T> GetById(int id);
        Task<List<T>> GetAll();
    }
}

namespace ControlTec.Models
{
    public class ZoneRepository : IBaseRepository<Zone>
    {
        private readonly DataContext _context;

        public ZoneRepository(DataContext context)
        {
            _context = context;
        }

        public async Task<Zone> Add(Zone objModel)
        {
            _context.Zone.Add(objModel);
            await _context.SaveChangesAsync();
            return await GetById(objModel.Id); 
        }

        public async Task<Zone> GetById(int id)
        {
            var zone = await _context.Zone.FirstOrDefaultAsync(t => t.Id == id);
            return zone;
        }

        public async Task<Zone> GetByName(string name)
        {
            var zone = await _context.Zone.FirstOrDefaultAsync(t => t.Name == name);
            return zone;
        }

        public async Task<List<Zone>> GetAll()
        {
            return await _context.Zone.ToListAsync();
        }

        public async Task<Zone> Remove(int id)
        {
            var zone = await GetById(id);
            _context.Remove(zone);
            await _context.SaveChangesAsync();
            return zone;
        }

        public async Task<Zone> Update(Zone objModel)
        {
            var zone = await GetById(objModel.Id);
            zone.Name = objModel.Name;
            await _context.SaveChangesAsync();
            return objModel;
        }

    }
}

ConfigureServices

public void ConfigureServices(IServiceCollection services)
        {
            services.ConfigureProblemDetailsModelState();
           // services.AddGlobalExceptionHandlerMiddleware();


            services.AddControllers();


            //------------------------------------------------------------------------------------------//

            var connection = Configuration["ConexaoSqlite:SqliteConnectionString"];

            services.AddDbContext<DataContext>(options => {
                options.UseSqlite(connection);
            });

            services.AddScoped<IBaseRepository<Zone>, ZoneRepository>();

        }

The problem arose when I needed to create a new method in ZoneRepository and did not want to implement it in the interface.

This way, I can no longer instantiate IBaseRepository.

The code was:

namespace ControlTec.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ZoneController : Controller, IBaseController<Zone>, IBaseRules<Zone>
    {
        private readonly ZoneRepository _zoneRepository; //here
        public const int ID_INSERT = 0;
        public ZoneController(ZoneRepository zoneRepository) //here
        {
            _zoneRepository = zoneRepository;
        }

        [HttpGet]
        public async Task<ActionResult<List<Zone>>> GetAll()
        {
            return await _zoneRepository.GetAll();
        }
}

After a change, you will receive an exception below.

System.InvalidOperationException: Unable to resolve service for type 'ControlTec.Models.ZoneRepository' while attempting to activate 'ControlTec.Controllers.ZoneController'.\\r\\n at object Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)\\r\\n
at object lambda_method(Closure, IServiceProvider, object[])\\r\\n at Func Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.CreateActivator(ControllerActionDescriptor descriptor)+(ControllerContext controllerContext) => { }\\r\\n at Func Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.CreateControllerFactory(ControllerActionDescriptor descriptor)+CreateController(ControllerContext controllerContext)\\r\\n at Task Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)\\r\\n at Task Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\\r\\n at async Task Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()+Awaited(?)\\r\\n at async Task Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeAsync()+Logged(?)\\r\\n at async Task Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)+AwaitR equestTask(?)\\r\\n at async Task Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware.Invoke(HttpContext context)+Awaited(?)

If you want to resolve a ZoneRepository then you'll need to register it as a such as well.

services.AddScoped<ZoneRepository, ZoneRepository>();

Though I would recommend creating a new interface, IZoneRepository , that inherits from IBaseRepository<Zone> to keep this easily testable.

By wanting another method in ZonRepository that isn't in the interface, you are breaking LSP in SOLID principles. IMO generic repositories are an antipatern as they greatly restrict the capabilities offered by EF for the sake of "consistency". My golden rule is "Don't strive to be consistent for consistency's sake."

To add to Karl's answer, you should create a contract interface called IZoneRepository which extends IBaseRepository<Zone> and add your desired methods here.

public interface IZoneRepository : IBaseRepository<Zone>
{
    void SomethingZoneSpecific();
}

Then in your class:

public class ZoneRepository : IZoneRepository
{
    public void SomethingZoneSpecific()
    {
        //...
    }

    // And all of the IBaseRepository<Zone> implementations...
}

Then register ZoneRepository against IZoneRepository instead of IBaseRepository<Zone> , and change your controller's dependency to an IZoneRepository .

You should reconsider using the generic pattern because of methods like your "GetAll()" implementation. With entity Framework, using methods like this is highly inefficient. You can filter, sort, and paginate in your controller using code like:

var data = Repository.GetAll()
    .Where(x => x.ParentId == parentId)
    .OrderByDescending(x => x.CreatedAt)
    .Skip(pageSize * page)
    .Take(pageSize)
    .ToList();

But your GetAll() method is already fetching all rows from the table before filtering client side. That is a lot of data to send from the DB to the server, and a lot of memory needed for the request on the server for potentially a lot less data to send back to the client. Then there is the problem where you might have related entities (navigation properties) that may be accessed. This would result in lazy load hits, so extra queries get run. These limitations often lead developers to have to explore deviating from the generic "catch all" patterns when these performance issues crop up. EF can mitigate these issues extremely well through projection where Select methods, along with OrderBy , Where , and Pagination calls against IQueryable can result in queries that only return exactly what data from the DB is required. For these reasons, the generic pattern should really be avoided as you end up forsaking most of what EF can bring to the table to build efficient, high performance systems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM