ASP.NET Core, реализация универсального контроллера на основе универсального интерфейса репозитория

У меня проблема с реализацией универсального контроллера для операций CRUD с моими сущностями.

В настоящее время у меня есть следующие классы:

public class BaseItem
    {
        public int? Id { get; set; }
    }

Этот класс является родительским для нескольких сущностей:

public class Customer : BaseItem
    {
        // some fields
    }

    public class Project : BaseItem
    {
        // some fields
    }

Затем есть интерфейс для универсального репозитория:

    public interface IGenericItemRepository<TDomain>
                     where TDomain : BaseItem
    {
        // methods
    }

И на основе этого интерфейса строятся более конкретные интерфейсы:

    public interface ICustomerRepository : IGenericItemRepository<Customer> {}

Чтобы сейчас не решать EF, я использую mock-классы. Делаются они следующим образом:

public abstract class GenericMockRepository<TDomain> : IGenericItemRepository<TDomain> where TDomain : BaseItem
...

и реализует всю логику базового CRUD. Конкретный фиктивный репозиторий делается следующим образом:

    public class MockCustomerRepository : GenericMockRepository<Customer>, ICustomerRepository
    {
        public MockCustomerRepository() : base()
        {
            CreateCustomersList();
        }

        private void CreateCustomersList()
        {
            // method body
        }
    }

Итак, в целом я мог бы использовать мою существующую настройку для реализации контроллера следующим образом:

public class CustomersController : Controller
    {
        private ICustomerRepository _customerRepo;

        public CustomersController(ICustomerRepository customerRepo)
        {
            _customerRepo = customerRepo;
        }
    }

Затем я попытался реализовать универсальный контроллер:

    public interface IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
        where TDomain           :   BaseItem
        where TDetailsViewModel :   BaseViewModel, new()
        where TEditViewModel    :   BaseViewModel, new()
        where TRepo             :   IGenericItemRepository<BaseItem>
    {
        [HttpGet] public IActionResult Edit(int? id);
        [HttpPost] public IActionResult Edit(TDomain entity);
        [HttpGet] public IActionResult Details(int? id);
    }

public class BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> : Controller, IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
        where TDomain : BaseItem
        where TDetailsViewModel : BaseViewModel, new()
        where TEditViewModel : BaseViewModel, new()
        where TRepo : IGenericItemRepository<BaseItem>
    {
        private IGenericItemRepository<BaseItem> _repo;

        public BaseController(IGenericItemRepository<BaseItem> repo)
        {
            _repo = repo;
        }
    }

Но когда я пытаюсь изменить свой CustomerController на этот код ниже:

public class CustomersController : BaseController<Customer, BaseViewModel, BaseViewModel, ICustomerRepository>
    {
        private ICustomerRepository _customerRepo;

        public CustomersController(ICustomerRepository customerRepo)
        {
            _customerRepo = customerRepo;
        }
    }

есть следующая ошибка:

Ошибка CS0311 Тип «DAL.Repositories.ICustomerRepository» не может быть используется как параметр типа «TRepo» в универсальном типе или методе 'BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>'. Нет неявного преобразования ссылок из «DAL.Repositories.ICustomerRepository» в 'DAL.Repositories.IGenericItemRepository<Domain.BaseItem>'.

Но мой ICustomerRepository типа IGenericItemRepository, не так ли?

Как я могу исправить ошибку и правильно реализовать универсальный контроллер, чтобы я мог повторно использовать его в своих конкретных контроллерах? Я проверял похожие темы, но поскольку общий репозиторий реализован иначе, чем у меня, я не мог исправить это на основе примеров... Причина, по которой у меня есть дополнительные интерфейсы для конкретных репозиториев, заключается в том, что для каждого типа сущности будет быть более конкретными методами, поэтому я хотел бы определить их в конкретных интерфейсах (поэтому у меня нет чего-то вроде

public interface IBaseRepository
{
  T Get<T>(int id);
  void Save<T>(T entity);
}

Большое спасибо за любую помощь заранее!

Но мой ICustomerRepository типа IGenericItemRepository, не так ли? нет, посмотрите на Ковариантность и контравариантность в дженериках

Pavel Anikhouski 11.12.2020 15:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
1 085
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Попробуйте IGenericItemRepository<TDomain> вместо IGenericItemRepository<BaseItem> в своих BaseController и IBaseController.

public class BaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> : Controller, IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo> 
    where TDomain : BaseItem
    where TDetailsViewModel : BaseViewModel, new()
    where TEditViewModel : BaseViewModel, new()
    where TRepo : IGenericItemRepository<TDomain>
{
    private IGenericItemRepository<TDomain> _repo;

    public BaseController(IGenericItemRepository<TDomain> repo)
    {
        _repo = repo;
    }
}

 public interface IBaseController<TDomain, TDetailsViewModel, TEditViewModel, TRepo>
    where TDomain           :   BaseItem
    where TDetailsViewModel :   BaseViewModel, new()
    where TEditViewModel    :   BaseViewModel, new()
    where TRepo             :   IGenericItemRepository<TDomain>
{
    [HttpGet] public IActionResult Edit(int? id);
    [HttpPost] public IActionResult Edit(TDomain entity);
    [HttpGet] public IActionResult Details(int? id);
}

Другие вопросы по теме