Объем транзакций для разных классов хранилищ - PullRequest
0 голосов
/ 15 ноября 2018

Я пытаюсь обернуть транзакцию вокруг 2 или более операций базы данных, которые происходят в разных классах репозитория.Каждый класс репозитория использует экземпляр DbContext, используя Dependency Injection.Я использую Entity Framework Core 2.1.

public PizzaService(IPizzaRepo pizzaRepo, IPizzaIngredientsRepo ingredientRepo)
{
    _pizzaRepo = pizzaRepo;
    _ingredientRepo = ingredientRepo;
}

public async Task SavePizza(PizzaViewModel pizza)
{
    using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
    {
        int pizzaRows = await _pizzaRepo.AddEntityAsync(pizza.Pizza);
        int ingredientRows = await _ingredientRepo.PutIngredientsOnPizza(
            pizza.Pizza.PizzaId,
            pizza.Ingredients.Select(x => x.IngredientId).ToArray());

        scope.Complete();
    }
}

}

Очевидно, что в случае сбоя одной из операций я хочу откатить все.Будет ли этой области транзакции достаточно для отката или классы репозитория будут иметь транзакции самостоятельно?

Даже если вышеуказанные методы работают, существуют ли более эффективные способы реализации транзакций?

1 Ответ

0 голосов
/ 16 ноября 2018

Шаблоны репозитория отлично подходят для включения тестирования, но не имеют репозитория, нового для 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 */)
     // ...
   }
}

Это дает вам пару преимуществ:

  1. Контроль за единицей работы остается четко в обслуживании. Вы можете позвонить в любое количество репозиториев, и изменения будут зафиксированы или отменены на основании определения службы. (проверка результатов, отлов исключений и т. д.)
  2. Эта модель очень хорошо работает в ограниченном контексте. В более крупных системах вы можете разделить различные задачи на несколько DbContexts. Локатор контекста служит одной зависимостью для хранилища и может обращаться к любому / всем объектам DbContexts. (Подумайте о регистрации, аудите и т. Д.)
  3. Существует также небольшая опция производительности / безопасности для операций на основе чтения с использованием создания области CreateReadOnly() на заводе. Это создает контекстную область, которая не может быть сохранена, поэтому она гарантирует, что никакие операции записи не будут зафиксированы в базе данных.
  4. IDbContextScopeFactory и IDbContextScope легко макетируются, так что тесты вашего сервисного модуля могут проверить, зафиксирована транзакция или нет. (Смоделируйте IDbContextScope, чтобы подтвердить SaveChanges, и смоделируйте IDbContextScopeFactory, чтобы ожидать Create и вернуть макет DbContextScope.) Между этим и шаблоном Repository, нет беспорядочного насмешливого DbContexts.

Одно предостережение, которое я вижу в вашем примере, заключается в том, что кажется, что ваша View Model служит оболочкой для вашей сущности. (PizzaViewModel.Pizza) Я бы посоветовал не передавать сущность клиенту, а позволить модели представления представлять только те данные, которые необходимы для представления. Я обрисую причины этого здесь .

...