8

I am trying to follow the Onion Architecture to design my application where I have the following layers -

  • Domain Layer: is the inner-most layer and defines repository interfaces
  • Infrastructure Layer: forms the outer-most layer peered with the Web/UI layer and implements the repository interfaces
  • Service Layer: is the middle layer where business logic resides and repository interfaces are injected
  • Web/UI Layer: forms the outer-most layer peered with the Infrastructure layer and handles DI configurations

Things are working. No issues so far.

I've come across several Stack Overflow answers and online articles that recommend not to implement Repository pattern when using Entity Framework and Entity Framework Core, because the DbContext itself is implemented to provide the functionality of the Unit of Work pattern while the each IDbSet<T> acts as a repository. I do understand the point and that’s very good. Because, less code to write. So, I thought let’s see if I can find a way to structure my application avoiding the repositories and using the DbContext only.

Some recommendations I've found suggests use of the DbContext directly in my Controllers, which I didn’t like. I don’t want my Controller code to get all messy with queries. I prefer them slim, and delegate the business processing and database operations to somebody else, like I currently do with the Service classes.

I've read this article Ditch the Repository Pattern Already and in the comment section in response to one comment the writer said he uses Onion Architecture and -

If an Application Layer exists as a discrete layer then it is injected with the DbContext and possibly other dependencies. For a Web application, a UI layer sets up all the DI, translates request to calls to the Application layer, and maps responses onto view models to be sent back to the browser. Other infrastructure layers like the persistence layer I define the DbContext in sit at the outside layer with the UI layer as "peer" layers.

Since according to Onion Architecture the dependency should only go inward what I really don't understand is how can you get a reference of DbContext (defined in Infrastructure Layer) in Application Layer through DI.

For those who prefer not to implement Repository and Unit of Work patterns with EF/EF Core could you suggest how can I do the same and still structure my application using Onion Architecture?

EDIT - Feb 03 2020

Thanks to Flater for his answer. I've gone through each and every link everyone provided. Thanks to all.

But it seems I failed to properly express my problem. I'll try once again. My application can be represented with the following diagram of Onion Architecture -

Onion Architecture with no Application Layer

Each layer is implemented as a separate project for convenience. Since Onion Architecture dictates that dependencies go only inward -

Domain - has no dependency on any other project. Defines models and interfaces.

Service - has dependency only on Domain. Contains the Service classes which implements the Service interfaces. Service classes are injected with Repository interfaces.

Infrastructure - has dependency on Domain. It defines the DbContext. It contains the Repository classes which implements the Repository interfaces. Repository classes are injected with the DbContext.

Web/UI - has dependency on Service and Domain. It contains the Controller classes which are injected with Service interfaces and/or Repository interfaces. (This project also has a dependency on Infrastructure, but that is solely for DI configuration purpose).

Onion Architecture puts persistence operations at the outer most layer as part of Infrastructure and use Dependency Inversion to access them. This results in a loosely coupled design where the Application Core (comprised of Application + Services + Domain Layer) doesn't have any dependency on data access layer/technologies.

When I said -

what I really don't understand is how can you get a reference of DbContext (defined in Infrastructure Layer) in Application Layer through DI.

what I meant is, if I'm to discard the Repositories and use DbContext only, then I must use the DbContext from my Service classes, but DbContext is defined in Infrastructure and Service doesn't have a reference to Infrastructure.

To use the DbContext, the Service Layer it needs a direct reference to Infrastructure. Adding this reference violates the most fundamental concept required by Onion Architecture - dependencies should always go inward. This requirement forces us to use DIP to resolve outward dependency and thus achieve the loose coupling.

So, if Service Layer have a reference to the Infrastructure, the Application Core is directly dependent on the Data Access Layer, and I don't think we can call it an Onion anymore.

That's where my question comes - how can I avoid the Repositories, use DbContext only but still adhere to Onion Architecture?

I'm just trying to implement something that I can use with smaller applications, because clearly Repo/UoW approach is overkill for some scenarios and not to mention more code to write.

atiyar
  • 203
  • 2
  • 8
  • There's a decent overview and some good ideas [here](https://www.c-sharpcorner.com/article/onion-architecture-in-asp-net-core-mvc/). **Fair warning:** It's a whole lot of boilerplate code. – Robert Harvey Jan 09 '20 at 23:07
  • I don't have time to write a full answer, but [this](https://github.com/JasonGT/NorthwindTraders) is an example repository of exactly that. [Here](https://www.youtube.com/watch?v=RQve_bD8X_M) is a presenation by the author of the repository, using the above repository to explain clean architecture. – Flater Jan 09 '20 at 23:15
  • 1
    @RobertHarvey, The example in the link you provided actually do implement the Repository pattern. But, I'm trying to avoid repositories and use only `DbContext` instead. – atiyar Jan 09 '20 at 23:47
  • My counter-argument that EF _is not_ a repository and does not replace the need for one [is here](https://stackoverflow.com/a/13189143/18938), in case you're interested. – Eric King Jan 10 '20 at 13:45
  • @EricKing, I went through the link. Thanks. – atiyar Jan 10 '20 at 18:28

2 Answers2

7

The core argument for using repositories is to prevent leaking EF dependent code into your domain. That argument is not wrong, it just comes with a steep cost, i.e. a high-complexity uow/repo layer, which is now being regarded (by some, at least) as too high a price to pay for what it gives back.

By not using that uow/repo layer, you do actually let your domain depend on EF (the context and db sets). That much is unavoidable.

I once wrote a lengthy answer on repositories with EF. Summarizing my anti-repository conclusion: this is why it is called Entity Framework, not Entity Library. Frameworks are something you cannot reasonably abstract without effectively needing to duplicate the entire framework's structures (which negates the benefits of having said framework).

what I really don't understand is how can you get a reference of DbContext (defined in Infrastructure Layer) in Application Layer through DI

It is, in essence, no different from how you would get a reference to your self-built UOW if you had implemented an UOW/repo layer. You register your db context in your DI framework and then your service layer is able to have this context injected.

Here's a .Net Core example from one of my projects:

services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connString));

It's possible to use an interface rather than the direct db context class, but I haven't really dealt with projects where that became a necessity. Use it where appropriate.

Note that this registration doesn't have to live in the top level project (as that would violate your onion layering). I actually perform this registration in my persistence project. Something along the lines of:

// In Persistence

public static class DependencyRegistration
{
    public static void Register(IServiceCollection services, string connString)
    {
        services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connString));
    }
}

// In top-level Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    Persistence.DependencyRegistration.Register(services, "myConnString");
}

This way, your top-level project actually doesn't need to depend on the EF dll (since it doesn't refer to anything from EF), but it still has the authority to decide the connectionstring (from its own configuration) and still directs the persistence layer to set up the necessary EF-related DI registrations.

Note: for a pure onion architecture, the top layer project should not tell persistence to register its dependencies; but rather the top layer project tells the service/application layer to do their registration, and the service/application layer tells the persistence layer to do their registration.
My example isn't full onion but I kept it for simplicity's sake. Setting up the call chain layer-by-layer isn't complicated to set up but would needlessly lengthen my example.

Other infrastructure layers like the persistence layer I define the DbContext in sit at the outside layer with the UI layer as "peer" layers.

I don't really get what they mean here. This description of persistence and UI being "peer layers" sounds like it's at odds with what an actual onion architecture should be.


For a bigger example, check out the NorthwindTraders github repository. The author of this repo also has presentations on Youtube where he elaborates on the how (and why).

Flater
  • 44,596
  • 8
  • 88
  • 122
  • So, in a nutshell, your example is separating the project that defines the `DbContext` and moving the project inward so that the Service layer project can have a reference to it, right? That "peer layer" is actually used to reference the original Onion Architecture where Web/UI, Infrastructure, and Testing comprise the outer-most layer, and Persistence is made part of Infrastructure. – atiyar Jan 10 '20 at 18:23
1

In Onion architecture, when you are using repositories, you do not store the implementations of those repositories in the inner-most (domain) layer, instead you store abstractions (interfaces) that the rest of the application can use as part of dependency injection.

If you are wanting to inject your DbContext instead of repositories, this rule stays true. Abstract your DbContext and create an interface for it. Include all your DbSets and include any repository methods you want access to (SaveChangesAsync, FindAsync, etc...). This interface lives in your Domain layer, whereas the implementation of it lives in the Infrastructure layer (on one of the outer layers of your onion).

This has advantages when developing, too. You can choose to include only methods that are part of the DbContext that you want developers to use, and exlcude the ones you don't. (For instance, only including SaveChangesAsync() and not SaveChanges() would make it so developers must save changes asynchronously.

To consume the interface, in your Startup.cs, after you define your DbContext via:

services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext...

You also define the interface implementation:

services.AddScoped<IMyContext>(provider => provider.GetService<MyContext>());

  • I thought of that too, but didn't like the idea of my Domain layer having a dependency on Entity Framework. – atiyar Sep 23 '20 at 04:15
  • 1
    It's just about how much work you want to do to avoid that dependency. IMO, avoiding it is not worth the work, and having it does not violate Onion architecture. But if you really don't want it, you will need to avoid using things like DBSet in your interface. You will need to wrap your DBSet declarations in properties and return them as something like IQueryable, or a custom type of your own. Write wrappers for AddAsync, Attach, Update, and Remove, etc... It's more work, but not a lot. And you only have to do it once. – CleverPatrick Sep 25 '20 at 10:55