I am in the process of refactoring and improving a codebase. One of the major missing features is transactional safety and certain errors arising from each repository having its own DbContext.
The current setup is as follows:
- The BLL (
FooManager
) connects to as many repositories (FooRepository
,BarRepository
) as it wants to. - Each repository has its own
DbContext
. - Ninject is used for constructor injection.
What I want to create is as follows:
- The BLL (
FooManager
) has an injectedUnitOfWork
- The
UnitOfWork
class holds a reference to all repositories (Note: I am aware of the possibility of having the repositories register themselves to the unit of work, more on that later.) - A
UnitOfWork
's repositories should all use the same DbContext. - We must ensure that the
DbContext
is disposed of even if theFooManager
is kept alive after its method was called.
But I'm encountering a few issues here. I initially wanted to post these as separate questions but I suspect that any relevant answer needs to take all these considerations into account. I've found individual answer to each question (which I will mention when relevant) but I haven't found a solution that ticks all the boxes.
How to ensure that the DbContext
is shared between repositories?
Currently, the DbContext
is injected into the repository constructor. I see other options, but I can't fully solve any of them.
- I could set NInject to treat the
DbContext
as a singleton. However, then two units of work will also share the sameDbContext
. - I could inject the
DbContext
into theUnitOfWork
constructor, but I'm unsure how to then pass the context to the underlying repositories.- Setting a public property feels dirty.
- Using a constructor would mean that the UOW constructs the repository instead of NInject, which feels like an antipattern.
- Having the repositories each point to the context in the UOW requires the UOW injecting itself into the repositories. The question remains the same, regardless of whether I choose to pass the context or the UOW itself to the underlying repositories.
How to ensure that contexts get closed?
I want to stay away from using NInject's .InRequestScope()
feature, because the company has in the past shown to reuse its libraries in different forms. In the past, we've encountered issues because a BLL never disposed of its context.
Initially, it was developed for a REST web API, so the context was disposed of at the end of the request. But when the BLL was later reused as part of a Windows service, the contexts were never disposed of because the "request" never finished (since the service was active at all times). It took us many weeks of work to trace bugs that were caused by this (we kept seeing ghost data enter the database).
I want to avoid it in the new project, so the proposed solution needs to work both for a web service and a windows service. I am already relying on factory injection here, provided by a NInject extension:
public class FooManager
{
private Func<UnitOfWork> createUOW;
public FooManager(Func<UnitOfWork> uowFactory)
{
createUOW = uowFactory;
}
public object PublicMethod1()
{
using (var uow = createUOW())
{
// ...
}
}
public object PublicMethod2()
{
using (var uow = createUOW())
{
// ...
}
}
}
The goal is to ensure that each "call" to the business logic gets its own unique UOW (and therefore unique context). This means that in a Windows service, where the FooManager
may be kept alive for an extended time, that the underlying context will still be refreshed every time the FooManager
is called upon.
How to connect the UOW and the repositories?
My first attempt was to simply inject every repository via the UOW constructor. While that is technically possible, I'm apprehensive of having to bloat the constructor to using more than 30 different repositories. It's going to be hell to maintain in the future.
I can't just inject the IKernel
itself into the UOW. The DI and BLL (and DAL) are separate projects, and the DI already depends on the BLL (and DAL) so I can't have the BLL (and DAL) depend on the DI project as well as that would create a circular dependency.
I've come across solutions to have the repository register itself with the unit of work, but I'm unsure if this actually improves things in my case.
- While I do have a
Repository<T>
that all repositories inherit from, almost all repositories heavily rely on an additionalFooRepository
which considerably extends the available method. TheFooManager
can't just useRepository<Foo>
, it should be usingFooRepository
. - If I understand it correctly, this would also still require the BLL to instantiate the repositories it needs. But I'm specifically trying to prevent this behavior. I want the BLL to only work with a UOW directly.
- I'm aware that the BLL will still be aware of which repositories of the UOW it wishes to use, but I don't want the BLL to be responsible for instantiating the repositories.
One last mention I want to make is that complexity matters. I've fought long and hard for the opportunity to refactor the codebase, but management is liable to cancel the whole change if it takes too long or becomes too complex for the current developers to understand/properly work with. This means that I probably won't be able to implement a perfect design.
The main requirements are:
- Transactional behavior
- Ensuring that a UOW uses a single db context in all of its repositories.
- Ensuring that every BLL-method uses its own UOW/context and correctly disposes of it at the end of the method.
- For all other concerns, the needed change from the current setup should be minimized.
While I have found some solutions to the problems listed above, I cannot find one that aligns all needed requirements.
Am I missing something here, or am I trying to go about it the wrong way?