2

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 injected UnitOfWork
  • 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 the FooManager 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 same DbContext.
  • I could inject the DbContext into the UnitOfWork 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 additional FooRepository which considerably extends the available method. The FooManager can't just use Repository<Foo>, it should be using FooRepository.
  • 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?

Flater
  • 44,596
  • 8
  • 88
  • 122
  • This deserves a better answer than I've got time to write at the moment. However when I did this I had an ISessionManager which provided the current UoW on request and was a dependency of the repositories. The SessionManager implementation could be stored in memory or http context depending on the application. – Liath Aug 24 '18 at 09:56
  • 1
    @Liath: It sounds interesting, but don't think I'm fully getting what you mean. An extended answer would be welcome if and when you could find the time (or if someone else wishes to elaborate) – Flater Aug 24 '18 at 09:57
  • If I was doing this i would have each UOW have it's own instance of each required repository, scoped to the UOW life cycle just as the DbContext is. Then when the repositories are created you just inject the DbContext into the constructor. – user1450877 Aug 24 '18 at 11:27
  • @user1450877: Each UOW can have its own repository objects, I have no issue with that. It's actually a requirement, since all the repositories of a single UOW should share the same context, and the repositories of another UOW should share a different context. That means that two UOWs cannot logically use the same repository (as they need to have different contexts) _"you just inject the DbContext into the constructor"_ That is exactly what the question is about, I'm asking how to correctly achieve this. – Flater Aug 24 '18 at 12:30
  • @Flater scope the DBContext to the lifecycle of your unit of work https://www.planetgeek.ch/2010/12/08/how-to-use-the-additional-ninject-scopes-of-namedscope/ – user1450877 Aug 24 '18 at 15:38
  • @Flater , as @user1450877 referenced, I am fairly certain you can just `Bind` `DbContext` to `Self` with `InTransientScope` (which I think is the default Container/LifecycleManagement actually) and you'll achieve most of your listed create 'wants' (Repositories would have `DbContext` in constructor). I think the more interest dilemma here is the notion on how 'best' to relate the UoW to these non-generic repositories. – Brett Caswell Aug 25 '18 at 05:50
  • I think perhaps the reason you mentioned `IKernel` notions here is because you were thinking along the lines of resolving to Repositories in UoW.. something like `GetRepository()`, you could create a decorative interface that inherits IEnumerable, and in a `Bind.From.ToMethod(syntax => { })`, have it return resolved types from the kernel. the in constuctor of UoW assign it readonly field, and attempt OfType resolving from it in `GetRepository()`. – Brett Caswell Aug 25 '18 at 06:02
  • @BrettCaswell: FYI, the main reason I mentioned `IKernel` was so that I wouldn't need to put 30+ repositories in the constructor method, but rather one `IKernel` and then have that kernel instantiate the needed repositories. While it's not a technical argument (as it would still work), I do have a severe dislike for constructor bloat. But I realize `IKernel` isn't the solution as I can't have a circular dependency between my NInject and DAL projects, so I'll (sadly) have to use the repositories as constructor parameters. – Flater Aug 27 '18 at 08:03
  • @user1450877: The named scope option looks to be exactly what I'm looking for. I'm still testing it out now, but I would suggest you write this as an answer. If it works, then I consider it the answer to this question. – Flater Aug 27 '18 at 08:09
  • @user1450877: It has in fact solved my issue, so if you post the answer you get the tick :) Thank you for your help! – Flater Aug 28 '18 at 11:21

2 Answers2

0

The credit should go to @user1450877 but he's not actually posting an answer. The suggestion to use a named scope indeed solved the issue.

Reference link, I used the first of the three options.

I installed the NInject.Extensions.NamedScope plugin, and then created a string key to act as the name of the scope:

private const string UnitOfWorkScope = "UNITOFWORKSCOPE";

And then changed my bindings to use the correct scope:

kernel.Bind<IUnitOfWork>()
      .To<UnitOfWork>()
      .DefinesNamedScope(UnitOfWorkScope);

kernel.Bind<IFooRepository>()
      .To<FooRepository>();

kernel.Bind<MyDbContext>()
      .ToSelf()
      .InNamedScope(UnitOfWorkScope);

This achieves exactly what I wanted, a DbContext that behaves like a singleton in scope of every IUnitOfWork that is instantiated.

Flater
  • 44,596
  • 8
  • 88
  • 122
-5

Your repository should be at database level not table level.

ie instead of FooRepo.Get() and BarRepo.Get() you should have DbRepo.GetFoo() and DbRepo.GetBar()

This allows you to deal with the most common UoW problems where you are working on a single database with FK connected tables.

The repository can handle the UoW logic and expose an appropriate method

If you have cross database UoW then you have to see whether your language of choice supports cross DB TransactionScopes or if you can implement them yourself. Opening a transaction on each server and checking success of actions on the other before closing your own.

Moving the UoW logic out of the Business Layer and into the Data Layer is the correct approach. "Undo those writes because this thing failed" is a Data Layer concern, not business logic.

So on a practical note:

  1. Transactional behavior
  2. Ensuring that a UOW uses a single db context in all of its repositories.
  3. Ensuring that every BLL-method uses its own UOW/context and correctly disposes of it at the end of the method.
  4. For all other concerns, the needed change from the current setup should be minimized.

Simply moving current Business method to a DBRepo in the Data Layer which hides your existing TableRepos from the app allows you to

  1. use a transaction
  2. instantiate and keep as private variables your TableRepo classes using the same database context
  3. ensures that the Method controls its own UoW/transaction and correctly disposes of it
  4. has involved only copy/paste and public -> private/internal
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • 1
    We are talking about 30+ repositories each with their own custom methods (on average, I'd say 10 custom methods per repository, and 15 methods shared by all repositories). I'm not going to be putting those in a single monolithic class. You're suggesting to effectively use the UOW _as_ the repository. – Flater Aug 24 '18 at 10:39
  • keep the classes, just make them internal and expose through a single interface if you like – Ewan Aug 24 '18 at 10:40
  • 1
    That still requires around 300 copy/pasted "pass-through" methods to be implemented in the uow, which still creates a monolithic class. Any future change to a repository can be doubled (since the UOW must be updated as well), and it provides no additional benefits Whether I call `myUOW.FooRepository.Get()` or `myUOW.GetFoos()` is irrelevant in regards to the question. – Flater Aug 24 '18 at 10:44
  • Furthermore, if I keep the (now internal) classes as per your suggetion, then you haven't actually answered the question on how to pass the db context to these (internal) classes. – Flater Aug 24 '18 at 10:46
  • It has a clear single responsibility. I would question whether all of those per table methods need to be exposed, or whether they are simply generic CRUD operations which would only be called as part of a larger change – Ewan Aug 24 '18 at 10:46
  • (1) The methods wouldn't have been created if they hadn't been necessary. (2) The BLL composes the separate repository methods into "the larger change" (3) Merging everything into one monolithic class is not the answer. – Flater Aug 24 '18 at 10:47
  • because they would be internally instantiated you would easily enforce them all having the same DB context – Ewan Aug 24 '18 at 10:48
  • sure you need to delete a foo, the quesitn is whether your app needs to delete a foo, or is that just a small part of updating a bar that the app doesnt care about – Ewan Aug 24 '18 at 10:49
  • "or is that just a small part of updating a bar that the app doesnt care about" That is why the **business logic** layer exists and why I wish for it to use a unit of work. Specifically so the BLL can orchestrate multi-entity changes. – Flater Aug 24 '18 at 10:50
  • so you already have the code, you just think it belongs in a different layer – Ewan Aug 24 '18 at 10:52
  • The business logic will use the unit of work to create a data transaction (which may contain changes to multiple entity types), where the changes (pre-commit) are made via repository methods which are made available to the BLL through the unit of work. The BLL uses the repositories directly but it does not instantiate them. That is the setup, the question is how to achieve this setup within the given parameters. Your suggestions here are either bad practice or simply redesigning the entire system from the ground up, both of which are out of scope for the posted question. – Flater Aug 24 '18 at 10:56
  • `Moving the UoW logic out of the Business Layer and into the Data Layer is the correct approach.` I'm not sure where you got this from. At no point did I say that the UOW is part of the business layer. It is part of the data layer. – Flater Aug 24 '18 at 10:58
  • as a side note: Why doesnt new TransactionScope() in the BLL solve all your problems? – Ewan Aug 24 '18 at 10:58
  • 1
    (1) Currently, each repository has its own db context. It cannot simply be registered as a singleton as the db context does need to vary between separate BLL methods. (2) As I am using a single db context with `SaveChanges()`, this means there will already be transational safety on the context _provided that the same context is reused_. The Unit Of Work effectively serves as the scope for the uniqueness of the db context. (3) The UOW will further help prevent BLL constructor bloat when many repositories are called upon, as is now the case in e.g. an `ImportManager` that affects many tables. – Flater Aug 24 '18 at 11:03
  • is this an entity framework issue, transactionscope will work across dbs and dbconnections? – Ewan Aug 24 '18 at 11:06
  • https://stackoverflow.com/questions/815586/using-transactions-or-savechangesfalse-and-acceptallchanges – Ewan Aug 24 '18 at 11:12