DEV Community

loading...

Committing the unit of work

Afshar Mohebi
A work-life balance lover developer who is passionate about startups while trying to keep updated with coding trends.
・2 min read

Most of applications I have worked on as a developer involve a sort of unit of work based on database operations. Traditionally, a unit of work starts when a web request begins, and it ends when the request ends. Normally, database transaction is committed at the end of the web request. This is the point that sometimes needs tuning.

In ASP.NET WebForms there were a place called Global.asax. As far as I remember, some sort of automation could have happened there. For instance, calling commit was put here. But, if I remember correctly, if something bad happened in the database, the client would not be informed at all. For example, when an integrity rule raised an error in database due to a delete action triggered by the user, no one in client side would informed about it. The request would have return 200 family while the transaction was rolled back in the back-end. This was causing lot of problems, because none of changes by the user were saved into the database. I have memories of bugs reported by customers just as a result of this behavior.

I was thinking that a perfect automation can not be reached in ASP.NET almost. However, a SO question encouraged me to try it again. Firstly, I created and ActionFilter in ASP.NET Core 2.x as this:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using TestFilter.Data;

namespace TestFilter
{
    public class TestActionFilter : IActionFilter
    {
        readonly ApplicationDbContext _applicationDbContext;
        public TestActionFilter(IServiceProvider serviceProvider)
        {
            _applicationDbContext = serviceProvider.GetRequiredService<ApplicationDbContext>();
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Exception == null)
            {
                try
                {
                    _applicationDbContext.SaveChanges();
                }
                catch (Exception exception)
                {
                    context.Result = new BadRequestObjectResult($"Error: {exception.Message}");
                    return;
                }
            }
            else
            {
                throw new Exception("Captured in Filter", context.Exception);
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
        }
    }
}

In the catch section, I can receive possible database errors, hence reflecting it to the user. This way, I am not forced to add SaveChanges to every single actions. Not only this solution works with ASP.NET Core MVC, but also it works with ASP.NET Core API too. Thanks to ASP.NET Core because either of MVC and API utilize same base controllers.

To having my curiosity satisfied, I removed the try-catch fences from around SaveChanges() and tied to cause a database error. To my surprise, the error come up right in user side rather than being missed. Seems that something has changed during years, and the database error is not going to be missed again. I used SQLite database in the experience.

Discussion (0)