2

I am developing an app which uses Entity Framework for data access. The architecture of the app somewhat like below:

enter image description here

As depicted in the drawing, the business service can be consumed from either web app, cli app or windows service. What I'm trying to design is, each service request should be performed in a single transaction. I'm using dependency injection to inject services to web api controllers. If I use request scoped dbcontext using DI container, it'll do the job for web api, but won't work for service requests coming directly from CLI app or windows services.

What are best practices used to handle service level transactions with Entity Framework?

e.g.

// Services
public class UserService
{
    private TaskService _taskService;
    private UserRepository _userRepository;

    public UserService(TaskService taskService, UserRepository userRepository)
    {
        this._taskService = taskService;
        this._userRepository = userRepository;
    }

    public void MarkInactive(int userId)
    {
        this._taskService.CloseAllPendingTasks(userId);
        this._userRepository.MarkInactive(userId);
    }
}

public class TaskService
{
    public void CloseAllPendingTasks(int userId)
    {
        ...
    }
}

// Consumer
// Scenario 1:
this._taskService.CloseAllPendingTasks(1);

// Scenario 2:
this._userService.MarkInactive(2);

In above example, In case of Scenario 1, task service should create new transaction for the operation. While in Scenario 2, user service should create a transaction and task service should join the already open transaction.

  • If I use request scoped dbcontext using DI container, it'll do the job for web api, but won't work for service requests coming directly from CLI app or windows services. -- Why? – Robert Harvey Jan 31 '18 at 5:54
  • While in Scenario 2, user service should create a transaction and task service should join the already open transaction. -- Why? – Robert Harvey Jan 31 '18 at 5:55
  • @RobertHarvey, 1. There wouldn't be a RequestContext when I consume the service from CLI app or Win service. 2. In scenario 2, I would want both those operations (changing user's status and closing their pending tasks) to be done in a single atomic transaction. – Rumit Parakhiya Jan 31 '18 at 5:55
  • 1
    EF doesn't need a RequestContext to work properly. – Robert Harvey Jan 31 '18 at 5:56
  • In scenario 2, I would want both those operations (changing user's status and closing their pending tasks) to be done in a single atomic transaction. -- Then provide a single service method that performs both tasks. – Robert Harvey Jan 31 '18 at 5:57
1

Short answer

This is a problem I've encountered and solved. In short, the issue was indeed that a context was being injected (via an injected repository) which was kept alive in a Windows service consumer, which was not the intention.

The short and simple answer on how I fixed this is to inject a context factory instead of a context.

For my use case, I relied on NInject to handle my dependency injection. I chose NInject because it has an extention which automatically generates factory methods when you inject Func<IFoo> instead of IFoo.


Practical example

To show some code, this is the old way which works for a web context but not a Windows service context:

public class PersonService
{
    private readonly IUnitOfWork _uow;

    public PersonService(IUnitOfWork uow)
    {
        _uow = uow;
    }

    public Person GetById(Guid id)
    {
        return _uow.PersonRepository.Get(p => p.Id == id);
    }
}

The issue here is that the unit of work (and its db context) stays alive for as long as the PersonService object stays alive. As you've found out, this creates issues for threads that are kept alive (like a Windows service).

I changed the code so that a call to the service is able to instantiate its own unit of work. In other words, even if the same PersonService object is kept alive, each subsequent call to a (public) method of the service will create its own unit of work (and db context)

public class PersonService
{
    private readonly Func<IUnitOfWork> _createUnitOfWork;

    public PersonService(Func<IUnitOfWork> uowFactory)
    {
        _createUnitOfWork = uowFactory;
    }

    public Person GetById(Guid id)
    {
        using(var uow = _createUnitOfWork())
        {
            return uow.PersonRepository.Get(p => p.Id == id);
        }
    }
}

The NInject extension I used (NInject.Extensions.Factory) automatically generates a simple factory method.

Assuming my dependency is set up to inject a UnitOfWork when asking for an IUnitOfWork, then the automatically generated factory method will be () => new UnitOfWork();

This solves the issue at hand. Even if a Windows service keeps the same PersonService object alive, every time it asks something of the service (= when it calls a public method), you are guaranteed to work in a separate (and fresh) context.

Some remarks

  • My example uses NInject, but this can be done with any DI-framework that is able to handle deferred execution methods such as Func<T>.
  • When your service has private methods which require database access, you should pass your uow object from the public method to the private method. This ensures that you can maintain the "one execution, one context" approach even if you have many methods which are expected to work with the same context.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.