Шаблоны репозитория отлично подходят для включения тестирования, но не имеют репозитория, нового для DbContext, обмениваются контекстом между репозиториями.
В качестве простого примера (при условии, что вы используете DI / IoC)
DbContext зарегистрирован в вашем контейнере IoC со сроком действия на запрос. Итак, в начале службы звоните:
public PizzaService(PizzaDbContext context, IPizzaRepo pizzaRepo, IPizzaIngredientsRepo ingredientRepo)
{
_context = pizzaContext;
_pizzaRepo = pizzaRepo;
_ingredientRepo = ingredientRepo;
}
public async Task SavePizza(PizzaViewModel pizza)
{
int pizzaRows = await _pizzaRepo.AddEntityAsync(pizza.Pizza);
int ingredientRows = await _ingredientRepo.PutIngredientsOnPizza(
pizza.Pizza.PizzaId,
pizza.Ingredients.Select(x => x.IngredientId).ToArray());
_context.SaveChanges();
}
Тогда в репозиториях:
public class PizzaRepository : IPizzaRepository
{
private readonly PizzaDbContext _pizzaDbContext = null;
public PizzaRepository(PizzaDbContext pizzaDbContext)
{
_pizzaDbContext = pizzaDbContext;
}
public async Task<int> AddEntityAsync( /* params */ )
{
PizzaContext.Pizzas.Add( /* pizza */)
// ...
}
}
Проблема, с которой я столкнулся, заключается в том, что он ограничивает единицу работы запросом и только запросом. Вы должны знать, когда и где происходят изменения сохранения контекста. Например, вы не хотите, чтобы репозитории вызывали SaveChanges, поскольку это может иметь побочные эффекты в зависимости от того, что было изменено в зависимости от контекста, предшествующего этому вызову.
В результате я использую шаблон «Единица работы» для управления областью действия DbContext (ов), где репозитории больше не внедряются с DbContext, вместо этого они получают локатор, а службы получают фабрику контекста контекста. (Единица работы) Реализация, которую я использую для EF (6) - это DhContextScope от Mehdime. (https://github.com/mehdime/DbContextScope) Для EFCore доступны вилки. (https://www.nuget.org/packages/DbContextScope.EfCore/) С DBContextScope сервисный вызов выглядит больше как:
public PizzaService(IDbContextScopeFactory contextScopeFactory, IPizzaRepo pizzaRepo, IPizzaIngredientsRepo ingredientRepo)
{
_contextScopeFactory = contextScopeFactory;
_pizzaRepo = pizzaRepo;
_ingredientRepo = ingredientRepo;
}
public async Task SavePizza(PizzaViewModel pizza)
{
using (var contextScope = _contextScopeFactory.Create())
{
int pizzaRows = await _pizzaRepo.AddEntityAsync(pizza.Pizza);
int ingredientRows = await _ingredientRepo.PutIngredientsOnPizza(
pizza.Pizza.PizzaId,
pizza.Ingredients.Select(x => x.IngredientId).ToArray());
contextScope.SaveChanges();
}
}
Тогда в репозиториях:
public class PizzaRepository : IPizzaRepository
{
private readonly IAmbientDbContextLocator _contextLocator = null;
private PizzaContext PizzaContext
{
get { return _contextLocator.Get<PizzaContext>(); }
}
public PizzaRepository(IDbContextScopeLocator contextLocator)
{
_contextLocator = contextLocator;
}
public async Task<int> AddEntityAsync( /* params */ )
{
PizzaContext.Pizzas.Add( /* pizza */)
// ...
}
}
Это дает вам пару преимуществ:
- Контроль за единицей работы остается четко в обслуживании. Вы можете позвонить в любое количество репозиториев, и изменения будут зафиксированы или отменены на основании определения службы. (проверка результатов, отлов исключений и т. д.)
- Эта модель очень хорошо работает в ограниченном контексте. В более крупных системах вы можете разделить различные задачи на несколько DbContexts. Локатор контекста служит одной зависимостью для хранилища и может обращаться к любому / всем объектам DbContexts. (Подумайте о регистрации, аудите и т. Д.)
- Существует также небольшая опция производительности / безопасности для операций на основе чтения с использованием создания области
CreateReadOnly()
на заводе. Это создает контекстную область, которая не может быть сохранена, поэтому она гарантирует, что никакие операции записи не будут зафиксированы в базе данных.
- IDbContextScopeFactory и IDbContextScope легко макетируются, так что тесты вашего сервисного модуля могут проверить, зафиксирована транзакция или нет. (Смоделируйте IDbContextScope, чтобы подтвердить
SaveChanges
, и смоделируйте IDbContextScopeFactory, чтобы ожидать Create
и вернуть макет DbContextScope.) Между этим и шаблоном Repository, нет беспорядочного насмешливого DbContexts.
Одно предостережение, которое я вижу в вашем примере, заключается в том, что кажется, что ваша View Model служит оболочкой для вашей сущности. (PizzaViewModel.Pizza) Я бы посоветовал не передавать сущность клиенту, а позволить модели представления представлять только те данные, которые необходимы для представления. Я обрисую причины этого здесь .