Как я могу использовать DbContext в репозитории для реализации принципа инверсии зависимостей в ASP.NET Core 8.0 MVC

Я создал приложение ASP.NET Core 8.0 MVC.

Вот мой AppDbContext класс:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    public DbSet<User> Users { get; set; }
}

DbRepo класс:

public class DbRepo
{
    internal readonly AppDbContext db;
    internal DbRepo(AppDbContext context)
    {
        this.db = context;
    }
}

Вот пользовательский репозиторий, где мне нужно использовать DbContext, репозиториев может быть несколько:

public class UserRepository : DbRepo, IUser<User, User, string>
{
    public async Task<bool> Create(User user)
    {
        try
        {
            if (!await IsExist(user))
            {
                await db.AddAsync(user);
            }
            return db.SaveChanges() > 0;
        }
        catch (Exception)
        {
            return false;
        }
    }
}

Это класс DataAccessFactory для слабосвязанных зависимостей:

public class DataAccessFactory
{
    public static IUser<User, User, string> UserData()
    {
        return new UserRepository();
    }
}

Теперь сервис вызовет методы DataAccessFactory to репозитория:

public class UserService
{
    public static Task<bool> CreateUser(UserDTO newUser)
    {
        User user = UserMapping(newUser);
        return DataAccessFactory.UserData().Create(user);
    }
}

И тогда эта служба позвонит с контроллера. Вот в чем проблема: для каждого репозитория должен быть указан аргумент для наследования класса.

Как я мог это решить?

Я добавил это в DbRepo:

builder.Services.AddScoped<DbRepo>();

Интерфейс IUser непонятен. Что это такое? Почему репо реализует это? UserRepository — это то, что возвращает пользователя. Как это еще и пользователь?

Scott Hannen 05.07.2024 20:30
Стоит ли изучать 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
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Есть одна запутанная деталь: UserRepository реализует интерфейс под названием IUser. Имя класса описывает то, что хранит или извлекает пользователей. Это был бы не пользователь. Но я собираюсь обойти это, потому что на этот вопрос в любом случае можно ответить.

Это будет мешать инверсии управления:

public class UserService
{
    public static Task<bool> CreateUser(UserDTO newUser)
    {
        User user = UserMapping(newUser);
        return DataAccessFactory.UserData().Create(user);
    }
}

UserService зависит от UserRepository, но контролирует то, как оно создается. Оно позвонит DataAccessFactory.UserData().Create(user);, которое зовёт new UserRepository().

Как написано, изменить эту зависимость невозможно. Вы можете избавиться от DataAccessFactory и просто вызвать new UserRepository(), и результат будет тот же. Существует фиксированная зависимость от UserRepository.

Вместо того, чтобы UserService принимать это решение, вы можете изменить его следующим образом:

public class UserService
{
    private readonly UserRepository _userRepository;

    public UserService(UserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    // no longer static
    public Task<bool> CreateUser(UserDTO newUser)
    {
        User user = UserMapping(newUser);
        return _userRepository.CreateUser
    }
}

Теперь UserService не контролирует создание UserRepository. Что-то еще должно создать этот репозиторий и передать его конструктору UserService.

В результате теперь аргумент не обязательно должен быть UserRepository. Это также может быть любой тип, наследуемый от UserRepository. Это означает, что класс больше не связан с этим конкретным типом.

Это улучшение, но именно здесь мы часто добавляем интерфейс типа IUserRepository. Это не является строго необходимым, но для этого есть веские причины.

public interface IUserRepository
{
    Task<bool> Create(User user);
}

UserRepository реализовал бы этот интерфейс.

Тогда мы бы изменили UserService, чтобы он зависел от интерфейса, а не от конкретного класса.

public class UserService
{
    private readonly IUserRepository _userRepository;

    public UserService(IUserRepository userRepository)
    {
        _userRepository = userRepository;
    }

    // no longer static
    public Task<bool> CreateUser(UserDTO newUser)
    {
        User user = UserMapping(newUser);
        return _userRepository.CreateUser
    }
}

Теперь UserService знает только об интерфейсе. Мы можем передать ему любой класс, который его реализует. Это полезно для тестирования. Мы можем передать поддельную или имитируемую реализацию IUserRepository, которая всегда возвращает некоторое ожидаемое значение, чтобы мы могли убедиться, что UserService делает то, что мы ожидаем, в ответ на различные полученные результаты.

Во время выполнения (когда приложение фактически запускается) вы, вероятно, захотите, чтобы реализация IUserRepository представляла собой конкретный конкретный класс, например UserRepository. Предполагая, что вы используете контейнер внедрения зависимостей Microsoft, у вас будет код в Startup или Program, который регистрирует зависимости с помощью IServiceCollection. (Я не могу быть более конкретным, не зная, как настроено ваше приложение.)

В этом методе вы настроите приложение для создания различных зависимостей. Например, если вы изменили UserService, чтобы он зависел от IUserRepository, этот код может выглядеть так:

services.AddScoped<IUserRepository, UserRepository>();

Это значит

Если вам нужно передать IUserRepository конструктору какого-либо класса, создайте экземпляр UserRepository.

Я немного умолчал о внедрении зависимостей. Подробнее можно прочитать здесь.

Но поскольку это напрямую относится к вашему вопросу, это изменение реализует инверсию управления. Вместо UserService говорить

Мне нужен репозиторий, поэтому я собираюсь его создать (тем самым контролируя процесс его создания)

там написано

Пожалуйста, добавьте IUserRepository в мой конструктор, когда будете меня создавать. Я не контролирую, как он создается. Приложение контролирует это.

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