Я создал приложение 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>();
Есть одна запутанная деталь: 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
в мой конструктор, когда будете меня создавать. Я не контролирую, как он создается. Приложение контролирует это.
Интерфейс
IUser
непонятен. Что это такое? Почему репо реализует это?UserRepository
— это то, что возвращает пользователя. Как это еще и пользователь?