What Is an ORM?
By the end of this lesson
Explain what an ORM does for you, and what it deliberately hides.
Your application thinks in objects: an Employee with a name and a salary. Your database thinks in rows and columns. An object-relational mapper sits between them and translates.
Without one, you write SQL by hand, read each column out of a result, and construct objects yourself. It works, and it is a great deal of repetitive code that has to change every time a column does.
List<Employee> employees = new();
using SqlConnection connection = new(connectionString);
await connection.OpenAsync();
using SqlCommand command = new(
"SELECT Id, Name, Role, AnnualSalary FROM Employees WHERE Role = @role",
connection);
command.Parameters.AddWithValue("@role", "Engineer");
using SqlDataReader reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
employees.Add(new Employee
{
Id = reader.GetInt32(0),
Name = reader.GetString(1),
Role = reader.GetString(2),
AnnualSalary = reader.GetDecimal(3)
});
}- Correct, and entirely mechanical. Note that the column positions 0 to 3 are tied to the order in the SELECT — reorder the query and this breaks silently.
- Every table needs its own version of this, and every schema change means revisiting it.
List<Employee> employees = await context.Employees
.Where(e => e.Role == "Engineer")
.ToListAsync();- EF Core generates the SQL, runs it, and builds the objects.
- The query is written in C#, so renaming the Role property updates this automatically and misspelling it fails to compile rather than at run time.
What you get:
- Less repetitive code
- Reading and writing rows stops being something you hand-write per table.
- Type-checked queries
- Query mistakes surface at compile time instead of as a run-time SQL error.
- Schema under version control
- Migrations record every schema change alongside the code that needs it.
- Change tracking
- Modify a loaded object, call save, and EF Core works out the UPDATE for you.
The diagram shows why the abstraction leaks. Your LINQ query is translated into SQL, sent over a network connection, executed by the database, and the results are materialised back into objects. Each of those stages has a cost, and the round trip is usually the expensive one.
This is why a query returning ten thousand rows to count them is slower than asking the database for the count — and why knowing what SQL you generated is not optional detail.
Summary
- An ORM translates between objects in your code and rows in your database
- It removes repetitive mapping code and makes queries type-checked
- It hides the SQL, which is both the point and the main risk
- Use it for ordinary data access; drop to SQL deliberately for reports and bulk work
Practice
Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.
Think about it
Think about it
In the hand-written example, the column indexes 0 to 3 correspond to the order of columns in the SELECT. What happens if someone adds a column to the middle of that SELECT list, and why is this class of bug particularly unpleasant?
Show solution
Every index after the insertion point now refers to the wrong column. If the types happen to line up, it compiles and runs, and silently populates fields with the wrong data.
It is unpleasant precisely because it does not throw. A role might end up in a name field and nothing reports an error. An ORM removes this entire category of mistake by mapping on names rather than positions.
Knowledge check
Nothing is recorded and there is no score. The explanation appears either way.
Saved in this browser only.