You're missing the singular nature of the unit of work. Effectively, what you want is a context that is used in more than one action:
- The long running process
- Any additional request to view the object status while the long running process is still running
Based on your code, you are expecting for the second web request to effectively look into the existing unit of work and read the state. That's not how a unit of work is supposed to work. A unit of work should be scoped to the current execution (running the processing job) and should not be accessed by additional executions (e.g. fetching status information at some unforeseen time)
- Model as two distinct unit-of-works: 1: update state; commit; 2: do the actual work; commit.
That is exactly what I would do. This way, the additional requests can open their own unit of work (and context) to the database and retrieve the already updated status. You ensure that everyone who wants to view the data will always have a correct (yet independent) outlook.
But where to put this logic? If it's on the controller level, it needs to be repeated. Somehow it feels as part of the responsibility of the service to update the state.
The controller's responsibility is handling web requests. It does not have the responsibility of executing business logic (other than triggering it in another class/layer of course).
This needs to be added on the business logic level (what I assume you mean with "the service"). Your code example doesn't quite show it, but I assume _repo and _unitOfWork presumably share a common link to the same database context object?
This typically is called by a controller that also takes care of the unit-of-work
That's where I disagree. The controller should not be handling the unit of work. Very simply put, there is an order to the layers:
WEB => BUSINESS => DATA
A unit of work is a wrapper around a db context, or at the very least fills the same niche as a db context would, which makes it part of the DATA layer. The controller should not handle objects that are more than one level down from it. As the controller resides in WEB, it can only directly interact with BUSINESS. It's the responsibility of BUSINESS to handle DATA, and thus the unit of work.
Here's an example of how it would look in the way I create my unit of work:
//WEB (controller)
public void Process(int entityId)
{
_service.Process(entityId);
}
//BUSINESS
public void Process(int entityId)
{
using(var uow = createUow())
{
var entity = uow.EntityRepository.GetEntity(entityId);
entity.State = State.Processing;
uow.Commit();
// ... do the heavy lifting that takes some time ....
entity.State = State.Processed;
uow.Commit();
}
}
//DATA
public class UnitOfWork : IDisposable
{
private readonly MyContext _context;
public EntityRepository EntityRepository { get; private set; }
public UnitOfWork()
{
_context = new MyContext();
EntityRepository = new EntityRepository(_context);
}
public void Commit()
{
_context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
You didn't post your unit of work implementation so I simply used my approach here to complete the example. The focus of the answer isn't on how the uow is built, but rather how it is used by the business layer (and not the web layer).
- The service is allowed to "bypass" the unit-of-work by directly storing the change on the DB. This probably would work but it feels hackish.
I'm not quite sure what benefit this would bring to the table. Why would this be better than simply doing an early commit of your unit of work?