Reputation: 2470
I want to do unit testing in my asp.net mvc entity framework 6 project and it forced me to look at my code. I had my controllers exposed to my context and decided I should probably separate it out. I was initially looking at the repository/UoW pattern but after reading a lot I decided on the simple service pattern with DI (i.e., the controller is injected with a service that has GetProducts()
, FindProduct()
, etc.).
My question is related to the change tracking in this pattern. Before I would just call SaveChanges()
after a lot of things were done in a controller method, but separating everything made methods like these:
public void AddRequest(Request request)
{
using (PricedNotesContext ctxt = new PricedNotesContext())
{
ctxt.Requests.Add(request);
ctxt.SaveChanges();
}
}
public void DeleteRequestData(BaseRequestData reqData)
{
using (PricedNotesContext ctxt = new PricedNotesContext())
{
ctxt.RequestData.Remove(reqData);
ctxt.SaveChanges();
}
}
In the above you see that SaveChanges()
is called twice. But I only want it called once. I only want my changes to persist if both operations completed successfully. Is the recommended solution to just have a method called SaveChanges()
and Dispose()
which obviously calls SaveChanges()
and Dispose()
exposed to the controller to manage the transaction?
Thanks for your help!
Upvotes: 1
Views: 1608
Reputation: 13531
Your service should offer functionality in such a way that the consumer does not have to care about saving the context. Actually, it doesn't know about the context. Why don't you simply do something like:
public class EfNotesService : INotesService
{
public ExecuteSomeBusinessOperation(input parameters here)
{
// Validate input parameters
using (PricedNotesContext ctxt = new PricedNotesContext())
{
ctxt.Requests.Add(...);
ctxt.RequestData.Remove(...);
// other logic
ctxt.SaveChanges();
}
}
}
Or even better, you could also use dependency injection to inject the context at the start of the request and dispose of it at the end of the request. This context is then injected into the constructor of EfNotesService, and you can then just use it without the using statement:
public class EfNotesService : INotesService
{
private readonly PricedNotesContext _ctxt;
public EfNotesService(PricedNotesContext ctxt )
{
_ctxt = ctxt;
}
public ExecuteSomeBusinessOperation(input parameters here)
{
// Validate input parameters
_ctxt .Requests.Add(...);
_ctxt .RequestData.Remove(...);
// other logic
_ctxt .SaveChanges();
}
}
Like this, the same context can span multiple services, and you don't have to worry about creating and disposing the context in these services.
Additionally, in your service operations you can of course use business components or even a domain layer to execute the business logic, instead of doing everything in the service.
And add a global exception handler to gracefully deal with exceptions.
Upvotes: 2
Reputation: 2470
Here's what I came up with.
public class EfNotesService : INotesService
{
private readonly PricedNotesContext _ctxt = new PricedNotesContext();
public IEnumerable<Request> GetAllRequests()
{
return _ctxt.Requests.ToList();
}
public void SaveRequest(Request request)
{
_ctxt.Entry(request).State = EntityState.Modified;
}
public void DeleteRequestData(BaseRequestData reqData)
{
_ctxt.RequestData.Remove(reqData);
}
// ...
// Other methods...
// ...
// Transaction control
public void SaveChanges()
{
_ctxt.SaveChanges();
}
public void Dispose()
{
_ctxt.Dispose();
}
}
It's not UoW or Repository but it's simple and it works well. Plus I get to keep all the 'store' advantages of the DbContext.
Upvotes: 1