coolboyjules coolboyjules - 1 month ago 16
C# Question

Service pattern entity framework asp.net mvc

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!

Answer

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.