Вопрос или проблема
мы можем сделать внедрение зависимостей для открытых обобщенных типов следующим образом:
public interface IRepo<T, TKey> where T: class { ... }
public class RepoBase<T, TKey>: IRepo<T, TKey> where T: class {...}
// Внедрение зависимостей
services.AddScoped(typeof(IRepo<,>), typeof(RepoBase<,>));
но я хочу, чтобы RepoBase
имел два открытых типа и один конкретный тип следующим образом:
public interface IRepo<T, TKey> where T: class { ... }
public class RepoBase<T, TKey, D>: IRepo<T, TKey> where T: class {...} where D : DbContext // D - это DbContext из EF
{
...
}
public class MyDbContext : DbContext { ... }
services.AddScoped(typeof(IRepo<,>), typeof(RepoBase<,,>)); // компилируется, но я хочу, чтобы третий тип был MyDbContext
services.AddScoped(typeof(IRepo<,>), typeof(RepoBase<,,MyDbContext>)); // не компилируется, но это то, что я хочу
как я могу зарегистрировать открытый обобщенный тип, но предоставить частичный конкретный тип?
Как уже упоминалось в комментариях, вы можете определить такой тип:
public class RepoBaseWithMyContext<T1, T2> : RepoBase<T1, T2, MyDbContext>
а затем зарегистрировать его так:
services.AddScoped(typeof(IRepo<,>), typeof(RepoBaseWithMyContext<,>));
Ответ или решение
Для решения задачи регистрации открытых генерических типов с одним конкретным параметром в C#, мы можем использовать подход, который упрощает и структурирует код, тем самым обеспечивая гибкость в архитектуре приложения. В данной ситуации нам нужно зарегистрировать RepoBase
с двумя открытыми типами и одним конкретным типом — MyDbContext
, который является производным от DbContext
.
Шаги для реализации
-
Определение интерфейсов и базовых классов:
Начнем с определения интерфейсаIRepo
, который будет принимать два типа:T
иTKey
.public interface IRepo<T, TKey> where T : class { // Определение методов }
Затем мы создаем базовый класс
RepoBase
, который реализует этот интерфейс и принимает три типа:T
,TKey
иD
, гдеD
должен быть производным отDbContext
.public class RepoBase<T, TKey, D> : IRepo<T, TKey> where T : class where D : DbContext { // Реализация методов }
-
Создание конкретного класса контекста:
Далее мы определим конкретный класс контекста базы данных, который будет использоваться в нашем репозитории:public class MyDbContext : DbContext { // Реализация контекста }
-
Создание обертки для регистрации:
Чтобы зарегистрироватьRepoBase
с частично конкретным типом, мы создадим вспомогательный классRepoBaseWithMyContext
. Этот класс будет производным отRepoBase
и будет передаватьMyDbContext
в качестве третьего параметра.public class RepoBaseWithMyContext<T1, T2> : RepoBase<T1, T2, MyDbContext> { // Здесь могут быть дополнительные методы или свойства }
-
Регистрация в контейнере зависимостей:
Теперь, используяAddScoped
, мы можем зарегистрировать наш класс в контейнере зависимостей:services.AddScoped(typeof(IRepo<,>), typeof(RepoBaseWithMyContext<,>));
Преимущества такого подхода
- Чистота кода: Создание отдельного класса для обработки конкретного контекста позволяет упростить код и обеспечить его расширяемость.
- Гибкость: Вы можете легко изменить контекст или добавить новые зависимости, не изменяя основную логику.
- Поддержка Dependency Injection: Используя механизм DI, вы можете управлять жизненным циклом объектов более эффективно, что приводит к улучшению управляемости приложения.
Заключение
Таким образом, для регистрации открытого генерического типа с частично конкретным типом в C#, мы создаем вспомогательный класс, который наследует базовую логику, тем самым обеспечивая возможность работы с конкретным контекстом базы данных. Этот подход не только предоставляет необходимую гибкость, но и улучшает архитектурное оформление приложения.