DEV Community

Cover image for Why would you ever use an ORM?
Kerman
Kerman

Posted on • Originally published at ormfactory.com

Why would you ever use an ORM?

Software development is always a search for balance between multiple aspects: development speed (how fast you ship new features), application performance, memory consumption, UI quality, and the cleanliness of your business logic.

In most cases, development speed is more important than performance or memory consumption. Performance is often "good enough" memory usage is often "acceptable" but development speed directly affects business profitability. There is never "too much" development speed. It determines how much money the business will spend on building the product and how long it will stay under the pressure of risk. If you are targeting a competitive market and your rivals can launch earlier, then nothing is more critical.

Of course, an application that barely moves or consumes absurd amounts of memory won’t survive competition. But if you compare speeding up an app by 20% with saving two or three months of development time, the choice is obvious. Users may not notice the extra 20% speed, but the budget hole will definitely be noticed.

How an ORM speeds up development

ORM is a tool designed to save development time. But how exactly?

First, an ORM makes your code shorter. It hides the tons of boilerplate required to move values between table columns and class fields. This also ensures type safety: the type of a class field always matches the database column type.

This applies primarily to statically typed languages, but IDE completions work everywhere. Autocomplete improves both developer experience and correctness, which also means faster work.

Shorter code is quicker to write and quicker to read. The latter matters even more in large projects, where the limiting factor is the cognitive load of the human brain. Proper layering and clean, well-scoped functions are an art, but hiding technical details behind abstractions helps keep code understandable.

ORMs let you work at a higher level of abstraction. They handle fields and expose objects. You can take a common base query and modify it for multiple specific cases using polymorphism. You can add conditions dynamically depending on user input. Doing the same with raw SQL is significantly harder.

ORMs are less error-prone. When writing SQL by hand, it’s easy to make a mistake in one of thirty fields. An ORM won’t make that mistake. Moreover, it will automatically update all queries when you add or rename fields. It won’t forget to update a reference. Over long development cycles, this eliminates an entire class of human-factor errors and saves huge amounts of debugging time.

ORMs also greatly simplify data refactoring. This is a very underrated feature of mappers. Cleaning up and reorganizing the schema is dramatically easier with an ORM than without one. Hard metrics are difficult to define, but schema refactoring can easily accelerate long-term development by an order of magnitude.

Yes, a new abstraction layer adds some entry complexity. If your project has ten tables that rarely change, you probably don’t need an ORM at all. ORMs exist to manage high complexity, and small projects simply don’t have that complexity.

Impact on performance

Earlier, I said that development is a search for compromise. Does using an ORM mean losing performance or increasing memory usage? Not necessarily. Let’s look at it closer.

Yes, an ORM introduces some overhead for building queries and mapping data. But compared to database I/O, this overhead is negligible. What matters more is that shorter code and easier algorithmic optimization often make ORM-based logic faster than raw SQL in long-term development. Sure, raw SQL can be optimized. But it’s harder to see the full picture, and everyone is afraid to touch something that works — it might break. So developers avoid refactoring raw SQL.

This leads to a paradox: raw SQL may run slightly faster at the moment it is written, but after many iterations of development, the ORM version often ends up significantly faster due to incremental cleanups and safer refactoring.

Sometimes people argue that ORMs make it too easy to generate excessive queries, especially when used by junior developers who don’t understand SQL. But the truth is simple: to use an ORM properly, you must know SQL. You must understand what SQL is generated by your language-level expressions. An ORM is not a replacement for SQL. It is an abstraction layer on top of SQL, not a substitute for it.

Memory consumption

Memory consumption isn’t straightforward either.

I’ve seen developers load entire tables into DataTable objects and then work with them as if they were Excel sheets. In such structures, an Int32 may occupy 8 bytes on the stack plus 24 bytes on the heap (CLR, x64). ORMs store data in typed fields, which reduces memory usage drastically.

Database query results are inherently flat tables. Imagine a table with 100+ columns (e.g., orders) joined one-to-many with a table of 5 fields (positions). If you store the result “as is,” every position record duplicates all 100+ fields from the left table. Memory waste becomes ten-fold or even hundred-fold. A good ORM groups child rows under a single parent instance instead of duplicating all the data.

Schema Synchronization

The first task of any ORM is to keep the database schema and application objects in sync. This is the essence of mapping: loading table columns into class fields and writing them back. To do this correctly, the ORM must take the “original” schema definition at compile time. That definition can come from the database (db-first), from code (code-first), or from a separate model (model-first).

Code-first projects the schema from code into the database. It’s convenient when you have multiple environments, but it breaks down when multiple independent services try to reshape the database to suit themselves. Synchronization becomes complex because it requires loading the existing schema, comparing it correctly, and generating intricate DDL. This approach is beyond the scope of this article, so we won’t go into it here.

DB-first and model-first rely on code generation of entity classes. This is much simpler: load the schema from the database (or from the model file) and regenerate the entity files.

In practice, schema synchronization always has project-specific requirements. Often you need to extend generation for your own rules, but built-in ORM generators are usually closed and not easily customizable. Maybe you want to import database comments into entity files to keep unified documentation. Maybe you use relationships with incompatible field types, or many-to-many links through text columns that can’t be expressed as foreign keys. Maybe your enums require custom mapping logic.

This rigidity is a common and justified criticism of ORMs. I solved this problem by using open-source generators. You can take an MIT-licensed generator and modify it for your project’s needs. This is far easier than writing a generator from scratch, because OrmFactory takes care of most of the heavy lifting. It works directly with the database and can export the schema into your project. Its philosophy is simple: it handles schema design and synchronization, leaving the ORM to do what it does best — mapping.

Top comments (0)