As MSDN confirms , in EF 5 onwards, the DbContext class is a combination of Unit-Of-Work and Repository patterns. "In the web applications that I create, I try to implement repository templates and units of measure on top of the existing DbContext class. Recently, like many others, I have found that this is too much in my script. Iโm not worried about the main storage mechanism, that has ever changed with SQL Server, and although I appreciate the benefits that unit testing will bring, I still have a lot to learn before implementing it in a real application.
Thus, my solution is to use the DbContext class directly as a repository and Unit-Of-Work, and then use StructureMap to enter one instance per request into separate service classes, which allows them to do work in context. Then in my controllers I add each service I need and accordingly process the methods needed for each action. In addition, each request ends with a transaction created with DbContext at the beginning of the request, and either rolls back if there is any type of exception (be it an EF error or an application error), or committed if all is well. The following is an example code script.
This example uses the Territory and Shipper tables from the Northwind Sample Database. In this control controller, the territory and the shipper are added at the same time.
controller
public class AdminController : Controller { private readonly TerritoryService _territoryService; private readonly ShipperService _shipperService; public AdminController(TerritoryService territoryService, ShipperService shipperService) { _territoryService = territoryService; _shipperService = shipperService; }
Territory Service
public class TerritoryService { private readonly NorthwindDbContext _dbContext; public TerritoryService(NorthwindDbContext dbContext) { _dbContext = dbContext; } public void Insert(Territory territory) { _dbContext.Territories.Add(territory); } }
Sender Service
public class ShipperService { private readonly NorthwindDbContext _dbContext; public ShipperService(NorthwindDbContext dbContext) { _dbContext = dbContext; } public void Insert(Shipper shipper) { _dbContext.Shippers.Add(shipper); } }
Creating a transaction on Application_BeginRequest ()
Rollback or commit transaction on Application_EndRequest
var transaction = (DbContextTransaction)HttpContext.Items["_Transaction"]; if (HttpContext.Items["_Error"] != null) // populated on Application_Error() in global { transaction.Rollback(); } else { transaction.Commit(); }
Now all this works well, but the only question I have now is where is the best place to call the SaveChanges() function in DbContext? Should I call it in every service level method?
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? } }
Or should I leave the methods of the Insert () service class as they are and just call SaveChanges () before the transaction?
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(); }
Everything is good? Is it safe to call SaveChanges () more than once since it is wrapped in a transaction? Are there any problems that I encounter while doing this? Or is it best to call SaveChanges () only once before a transaction? I personally would rather just call him at the end right before the transaction, but I want to be sure that I will not miss any errors with the transactions or something is wrong? If you read this, thanks for taking the time to help. I know this was a long question.