Free cookie consent management tool by TermsFeed Generator The Cost of Abstraction: Is Your ORM Worth It? - VexByte Blog

The Cost of Abstraction: Is Your ORM Worth It?

ORMs promise to make database work painless, until they don't. After hitting walls with EF Core's heavy tooling and Dapper's boilerplate, I went looking for a better balance between abstraction and control. Here's what I found.

The Cost of Abstraction: Is Your ORM Worth It?

When I first started using Entity Framework Core on my smaller hobby projects, it honestly felt amazing. I could spin up a new idea, define a few classes, and have a database ready to go without thinking too hard about it. But even back then, while the magic was working, I started noticing some flaws.

The issues mainly came from having difficulties wiring things up the way I wanted them. I knew how to do a simple JOIN in raw SQL, but moving that logic over to EF Core made standard concepts feel surprisingly confusing. Setting up one-to-many or many-to-many relationships with the code first approach, requires a specific way of building out your app that didn't click for me immediately, the first time I used it. Even simple things felt different - like a Select not really being a Select in the way I was used to thinking about it. There was also always the looming question "Did I miss an .Include()?

Still, the real reason I began questioning if EF Core was the best solution to everything, was trying to set it up with a "Database First" project. That is when the cracks really started to show for me. Here is where I stand now after experimenting with a few different ways to handle databases.

1. EF Core: The Cost of Abstraction

EF Core is obviously an incredibly powerful tool. When you pair it with LINQ, it allows you to do some really cool stuff with very little code.

But that abstraction comes at a cost. You lose fine-grained control over your database and the final structure. I know people say you can still manually write and execute SQL, but if I have to do that constantly, it kind of removes the point of using a heavy ORM in the first place.

That being said, one of my least favourite parts about working with EF Core is the migration tooling. Want to add a migration? That'll be a full project build... Oh, and we'll also need to boot up your entire application, spin up the DI container, and run your startup code, just to figure out what your entities look like.

It feels heavy. Other ORMs manage schema diffing with simple source analysis or lightweight metadata inspection. EF Core, by contrast, treats every migrations add like it's preparing for launch. Yes, there are reasons. Dynamic model configuration, runtime logic in OnModelCreating, DI-resolved contexts, etc... But for the 90% of projects with straightforward entity classes and a few Fluent API calls, it's straight up overkill.

And sure, you can work around it with IDesignTimeDbContextFactory, but that's just duct tape over a design that probably shouldn't require your app to run in the first place.

2. Dapper: Good for Specific Jobs

Dapper is usually the first alternative people suggest. It returns that fine-grained control to you, which is generally better for those DB First scenarios I struggled with in EF Core. However, the only real use I have found for it is interacting with existing databases that mainly rely on stored procedures.

For everything else, you have to write an awful lot of boilerplate code just to make it work. You're back to writing raw SQL for everything. Every insert, every update, and every other query. And on top of all that, you manage your relationships manually, which is tedious. It is also prone to errors. If I change a column name in my SQL, Dapper won't know until the app breaks at runtime. In my experience, this hasn't happened much, but the risk is always there.

3. Go & SQLC: My New Favorite

Stepping outside the C# world for a moment, one of my favorite ways to interact with databases recently has been the SQLC package for Go.

It solves the problems I had with both EF Core and Dapper. I get to write raw SQL, which gives me back my fine-grained control over the database. But I also get type safety and checking because it generates a client for me. It also warns me if my queries are wrong, which gives me the confidence that I am not going to have to rebuild the app for every missed ; or ) inside a query. Go's insanely fast compilation helps there, but that's a topic for another time.

The only minus is that some might find the idea of having to write a specific query in a separate file for every single code interaction a bit bothersome. You can't just write a reusable GetById<T>() and call it a day, every query needs to be explicitly defined. But these queries can be separated into different files, so it is quite nice and keeps my project clean.

4. Drizzle ORM: The Promising Newcomer

The latest tool in my toolbox, acting as a TypeScript ORM. Despite having minor experience with it, I've found it rather convenient to use. It feels more like a query builder and less like a massive ORM and it felt quite nice spinning up quick, small applications where I just wanted to get things moving quickly with a single language across the stack..

I am still unsure if I would actually use it in a bigger project, as I'd need to work with it more to get a real feel for how it performs in real-life scenarios. For now I'd say, it is just a fun tool to have in the toolbox.

So, Is Your ORM Worth It?

There's no universal answer here. Every tool I've mentioned solves a real problem and creates new ones. EF Core is fantastic until you need to step outside its happy path. Dapper gives you control but leaves you writing everything yourself. SQLC hits a sweet spot for me, but it means leaving C# behind. Drizzle looks promising, but I haven't pushed it hard enough to know where it breaks.

The real takeaway isn't that one tool is better than another. It's that the "best" choice depends entirely on what you're building and what trade-offs you're willing to live with. If you're spinning up a quick CRUD app, EF Core's abstractions are probably worth it. If you're working with a legacy database full of stored procedures, Dapper makes sense. If you want type-safe SQL without the ORM baggage and don't mind Go, SQLC is worth a look.

The cost of abstraction is real. But so is the cost of not having it. At the end of the day, it is our job as developers to pick the right tool for the job. And if the job changes? Well, that's where we put our expertise to work and pick a new one.

Vexbyte

Martin Yordanov - Software Engineer

© 2026 Martin Yordanov. All rights reserved.