Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
This question posted on Twitter (and the subsequent thread) caught my attention. I had to pipe in with my own opinion on having queries without repositories.
To summarize, the question is about using Aggregate Roots with an ORM like Entity Framework. Should you eager load all navigation properties or alternatively I guess, use lazy loading?
My answer is if you’re only using an aggregate root for commands (to change state) and you generally have a higher read to write ratio, then eager load the navigation properties. As a general rule of thumb.
I recently heard about eShipOnWeb from Steve Smith. It’s a reference application using ASP.NET Core 3.1 using a layered monolithic architecture.
I decided to take a look around and try and refactor a spot that was using a repository for a query.
There is a query to return all of the orders for a signed-in user. It’s pretty simple but is a good example of it using the OrderRepository along with a specification to fetch out the relevant orders. From there it creates a ViewModel that is used in a Razor view.
Here’s the razor view for reference
Looking at the Razor view, you can see that it doesn’t use some of the properties on OrderViewModel. The OrderItems or ShippingAddress properties are never used.
The Repository with the specification is retrieving the entire Order Aggregate Root and eager loading the OrderItems. We’re fetching in a lot of data that we simply aren’t using.
Refactoring Queries without Repositories
The refactor is rather simple. Just remove the repository and instead inject the Entity Framework DbContext (CatalogContext). The second part is creating a separate ViewModel (MyOrdersViewModel ) that only contains the data we need for our Razor view. Then use this new ViewModel as a projection in our EF Query.
In many situations, I prefer to create a SQL Database View that I will create a new class to represent and use that as a DbSet in my DbContext. And most often I have a completely different DbContext for writes vs reads. My Reads DbContext is what will have a model that represents the SQL view. This allows you to do that projection as an SQL View rather than doing the projection in your code.
If you were using a SQL Database View and created an OrderSummary class that was on your DbContext, that now might look like this:
Live Coding Session
I did a live coding session on my YouTube and Twitch channels. You can check out the recording here of doing this exact refactor.
If you want to take a look a the full sample, you can also find my refactor changes on my fork of eShipOnWeb on GitHub.
- Avoiding the Repository Pattern with an ORM
- Is CQRS Complicated?
- Loosely Coupled Show: Software Architecture and Design