rya*_*lit 7 c# asp.net-mvc transactions dbcontext entity-framework-5
正如MSDN所证实的那样,在EF 5及其中,DbContext类是"工作单元和存储库模式的组合".在我构建的Web应用程序中,我倾向于在现有的DbContext类之上实现Repository和Unit-Of-Work模式.最近,和其他许多人一样,我发现这在我的场景中有点过分.我并不担心从SQL Server中改变的底层存储机制,虽然我很欣赏单元测试带来的好处,但在实际应用程序中实现它之前,我仍然需要了解很多东西.
因此,我的解决方案是直接使用DbContext类作为Repository和Unit-Of-Work,然后使用StructureMap将每个请求的一个实例注入到各个服务类中,从而允许它们对上下文进行操作.然后在我的控制器中,我注入了我需要的每个服务,并相应地调用每个动作所需的方法.此外,每个请求都包含在请求开始时从DbContext创建的事务中,如果发生任何类型的异常(无论是EF错误还是应用程序错误),还是回滚,如果一切正常则返回.下面是一个示例代码方案.
此示例使用Northwind示例数据库中的Territory和Shipper表.在此示例管理控制器中,同时添加了区域和出货单.
调节器
public class AdminController : Controller
{
private readonly TerritoryService _territoryService;
private readonly ShipperService _shipperService;
public AdminController(TerritoryService territoryService, ShipperService shipperService)
{
_territoryService = territoryService;
_shipperService = shipperService;
}
// all other actions omitted...
[HttpPost]
public ActionResult Insert(AdminInsertViewModel viewModel)
{
if (!ModelState.IsValid)
return View(viewModel);
var newTerritory = // omitted code to map from viewModel
var newShipper = // omitted code to map from viewModel
_territoryService.Insert(newTerritory);
_shipperService.Insert(newShipper);
return RedirectToAction("SomeAction");
}
}
Run Code Online (Sandbox Code Playgroud)
领土服务
public class TerritoryService
{
private readonly NorthwindDbContext _dbContext;
public TerritoryService(NorthwindDbContext dbContext)
{
_dbContext = dbContext;
}
public void Insert(Territory territory)
{
_dbContext.Territories.Add(territory);
}
}
Run Code Online (Sandbox Code Playgroud)
托运人服务
public class ShipperService
{
private readonly NorthwindDbContext _dbContext;
public ShipperService(NorthwindDbContext dbContext)
{
_dbContext = dbContext;
}
public void Insert(Shipper shipper)
{
_dbContext.Shippers.Add(shipper);
}
}
Run Code Online (Sandbox Code Playgroud)
在Application_BeginRequest()上创建事务
// _dbContext is an injected instance per request just like in services
HttpContext.Items["_Transaction"] = _dbContext.Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted);
Run Code Online (Sandbox Code Playgroud)
在Application_EndRequest上回滚或提交事务
var transaction = (DbContextTransaction)HttpContext.Items["_Transaction"];
if (HttpContext.Items["_Error"] != null) // populated on Application_Error() in global
{
transaction.Rollback();
}
else
{
transaction.Commit();
}
Run Code Online (Sandbox Code Playgroud)
现在这一切似乎运作良好,但我现在唯一的问题是最好SaveChanges()在DbContext上调用函数?我应该在每个服务层方法中调用它吗?
public class TerritoryService
{
// omitted code minus changes to Insert() method below
public void Insert(Territory territory)
{
_dbContext.Territories.Add(territory);
_dbContext.SaveChanges(); // <== Call it here?
}
}
public class ShipperService
{
// omitted code minus changes to Insert() method below
public void Insert(Shipper shipper)
{
_dbContext.Shippers.Add(shipper);
_dbContext.SaveChanges(); // <== Call it here?
}
}
Run Code Online (Sandbox Code Playgroud)
或者我应该保留服务类Insert()方法,并在提交事务之前调用SaveChanges()?
var transaction = (DbContextTransaction)HttpContext.Items["_Transaction"];
// HttpContext.Items["_Error"] populated on Application_Error() in global
if (HttpContext.Items["_Error"] != null)
{
transaction.Rollback();
}
else
{
// _dbContext is an injected instance per request just like in services
_dbContext.SaveChanges(); // <== Call it here?
transaction.Commit();
}
Run Code Online (Sandbox Code Playgroud)
两种方式都可以吗?因为它被包装在一个事务中,所以多次调用SaveChanges()是否安全?这样做可能会遇到任何问题吗?或者最好在事务实际提交之前立即调用SaveChanges()一次?我个人宁愿在交易提交之前就在最后调用它,但我想确定我没有错过任何关于交易或做错事的问题?如果您读到这里,感谢您抽出宝贵时间提供帮助.我知道这是一个很长的问题.
您可以SaveChanges()在提交单个原子持久性操作时调用.由于您的服务并不真正了解彼此或彼此依赖,因此内部无法保证其中一个或另一个将提交更改.因此,在这个设置中,我想他们每个人都必须提交他们的更改.
这当然会导致这些操作可能不是单独原子的问题.考虑这种情况:
_territoryService.Insert(newTerritory); // success
_shipperService.Insert(newShipper); // error
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您已部分提交数据,使系统处于某种未知状态.
在这种情况下,哪个对象可以控制操作的原子性?在Web应用程序中,我认为通常是控制器.毕竟,操作是用户提出的请求.在大多数情况下(当然有例外),我想有人会期望整个请求成功或失败.
如果是这种情况并且您的原子性属于请求级别,那么我建议DbContext从控制器级别的IoC容器获取并将其传递给服务.(他们已经在构造函数上需要它,所以那里没有大的改变.)这些服务可以在上下文中运行,但从不提交上下文.一旦所有服务完成其操作,消费代码(控制器)就可以提交它(或者将其回滚,或者放弃它等).
虽然不同的业务对象,服务等应该在内部维护自己的逻辑,但我发现通常拥有操作原子性的对象在应用程序级别,由用户调用的业务流程控制.
| 归档时间: |
|
| 查看次数: |
9407 次 |
| 最近记录: |