3

I'm trying to get the Repository/UnitOfWork pattern figured out. We're working on a project with EntityFramework and our DBContext contains 200+ tables.

Right now our approach is to inject whatever repository is required to a service. This way the services can only call a few methods from the passed repository. Services are easy to test because it's easy to fake the injected repositories.

  • When using a UnitOfWork pattern, should the UnitOfWork contain every single Repository from the project?
  • Would it be ok to have multiple, different UnitOfWorks (containing different Repositories) for different parts of an application?
2
  • 2
    The Unit of Work should only reference the repositories that it needs. Commented Oct 24, 2018 at 17:16
  • And it would be ok to have many different UnitsOfWork for every case you need.
    – Fabio
    Commented Oct 25, 2018 at 5:38

2 Answers 2

3

While it is possible to use composition to inject every relevant repository into a unit of work, this becomes quite unwieldy and clumsy for any non-trivial amount of repositories, on top of being an OCP violation.

In .Net Core, I've found a nicer way to approach this. In short, it relies on the DI container to connect the db context to both the repositories and the unit of work, without requiring you to directly connect the repositories and the unit of work.

This works neatly out of the box for any web-based context as you can rely on the DI scope to treat your dependencies as a singleton only within a single web request. A similar thing can be done for non-web contexts but it requires more configuration, and a unit of work factory may be simpler in that case.

This can be achieved by doing the following:

  • Register the db context into the DI container as a scoped entry.
  • Inject the db context in the unit of work constructor.
    • The unit of work only interacts with the committing of the db context.
  • Register the unit of work into the DI container. It doesn't matter if it's scoped or transient as the context itself will already be scoped.
  • Inject the db context in the repository constructor.
    • The repository only interacts with the sets of the db context, it does not commit any changes.
  • Register the repository into the DI container. It doesn't matter if it's scoped or transient as the context itself will already be scoped.

This enables your consumer to request the appropriate dependency based on what it needs to do:

  • If your consumer wants to get/set data, inject whatever repository is relevant for the job at hand.
  • If your consumer wants to commit any made changes, inject the unit of work.

Generally speaking, my domain logic only interacts with repositories, whereas my business service is the only one using the unit of work to commit any changes that have been made to the db context (indirectly via the domain logic).

I find this a much cleaner approach that avoids the clumsy manual composition of all repositories into the unit of work.

2
  • So, basically, your UoW doesn't hold any references to Repositories, only to the same DB context (session/transaction) as any of the repositories used in the BL. If that's right, then your UoW doesn't track all the DB changes that belong to the same business transaction. It' will just trigger the commit/rollback. Doesn't that defeat the purpose of the UoW?
    – Laiv
    Commented Sep 20, 2022 at 7:49
  • @Laiv: The unit of work tracks what it needs to track, which for EF generally means a single db context. Nothing is stopping you from letting the uow handle multiple contexts if you have them, but that's not part of the question that was asked.
    – Flater
    Commented Sep 20, 2022 at 8:56
1

One of the main goal of the unit of work pattern is to save all related changes in one process to fulfill data integrity concerns. It means that the unit of work should contain all repositories depending to each other as much as possible.

As a simple example, you might have UnitOfWorkTypeA and UnitOfWorkTypeB. UnitOfWorkTypeA contains RepositoryA and UnitOfWorkTypeB contains RepositoryB.

Assume that you have an object containing two fields F1 and F2 (each of them is a POCO). Let's assume that the values of F1 and the values of F2 should be added in RepoA and Repo B, respectively. All values are essential for the sake of integrity. When the methodSaveChanges() is executed in UnitOfWorkTypeA and UnitOfWorkTypeB, the first POCO's data will be added in the corresponding table of RepositoryA nicely, but the second one will be not stored due to any reason (Server not responding, losing connection and etc). Consequently, breaking data integrity. In this case, it is essential that both data are transferred to DB using one unit of work.

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