我正在尝试将事务存储在不同存储库类中发生的2个或更多数据库操作周围。每个存储库类都使用DbContext实例(通过依赖注入)。我正在使用Entity Framework Core 2.1。
POST https://graph.microsoft.com/v1.0/subscriptions
Content-Type: application/json
{
"changeType": "created,updated",
"notificationUrl": "https://webhook.azurewebsites.net/notificationClient",
"resource": "/me/mailfolders('inbox')/messages",
"expirationDateTime": "2016-03-20T11:00:00.0000000Z",
"clientState": "SecretClientState"
}
}
很显然,如果其中一项操作失败,我想回滚整个过程。 此事务作用域是否足以回滚,或者存储库类是否应具有自己的事务?
即使以上方法可行,还有更好的方法来实现交易吗?
答案 0 :(得分:0)
存储库模式非常适合启用测试,但是没有新的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)的实现是Mehdime的DbContextScope。 (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 */)
// ...
}
}
这给您带来了几个好处:
CreateReadOnly()
范围创建,对于基于读取的操作也有一些性能/安全性选项。这样会创建一个无法保存的上下文范围,因此可以确保不会将任何写操作提交给数据库。SaveChanges
,并模拟一个IDbContextScopeFactory期望一个Create
并返回DbContextScope模拟。)在那和存储库模式之间,不要凌乱地模拟DbContext。在您的示例中看到的一个警告是,您的视图模型似乎充当了实体的包装器。 (PizzaViewModel.Pizza)我建议不要将实体传递给客户端,而应让视图模型仅代表视图所需的数据。我概述了此here的原因。