Ivy's Answer to the ORM Debate: Just Query Your Database

Raw SQL vs EF Core vs Dapper — the ORM debate assumes layered architecture you probably don't need for internal tools. Ivy takes a different position: skip the layers, query your database directly from the UI, and ship faster.

"My thinking was: if I first use raw SQL, I might build a better mental model of what's actually happening, instead of relying on abstractions I don't understand."

This Reddit comment sparked another round of the eternal ORM debate: raw SQL vs Entity Framework vs Dapper vs stored procedures.

For internal tools, Ivy sidesteps this debate entirely. You get direct EF Core access in your UI layer. No repository patterns. No unit of work abstractions. Just queries.

public class EmployeeDashboard : ViewBase
{
    public override object? Build()
    {
        var db = UseService<AppDbContext>();

        return db.Employees
            .Where(e => e.Department == "Engineering")
            .OrderBy(e => e.HireDate)
            .ToDataTable(e => e.Id)
            .Header(e => e.Name, "Name")
            .Header(e => e.Email, "Email");
    }
}

This is the entire data layer for this view. EF Core query, rendered as a table.

Why the ORM Debate Assumes Layers You Don't Need

The traditional ORM debate assumes layers:

  1. UI layer
  2. Service layer
  3. Repository layer
  4. Data layer

Each layer adds abstraction. Each abstraction has opinions. The debate is about which abstraction to use and where.

Ivy's Position: Skip the Layers for Internal Tools

For internal tools, those layers solve problems you don't have:

Problem Solution Do You Need It?
Multiple UI clients API layer No — one internal app
Domain logic reuse Service layer Maybe — often overkill
Database swapping Repository pattern No — you have one database
Unit testing isolation Interfaces everywhere Debatable for internal tools

Ivy assumes you're building one internal tool that talks to one database. The simplest architecture is: UI queries database directly.

What About Business Logic?

Put it in services, not repositories:

public class ExpenseApproval : ViewBase
{
    public override object? Build()
    {
        var db = UseService<AppDbContext>();
        var approver = UseService<ExpenseApprovalService>();

        return db.PendingExpenses
            .ToDataTable(e => e.Id)
            .RowActions(new MenuItem("Approve"))
            .HandleRowAction(async (evt) => {
                var expense = await db.PendingExpenses.FindAsync(evt.Args.Id);
                await approver.ApproveAsync(expense);
            });
    }
}

The ExpenseApprovalService can have whatever logic you need. But it's a service that does things, not a repository that just fetches data.

Building a Better Mental Model with Direct EF Core Access

The Reddit poster wanted a "better mental model" by starting with raw SQL. Valid point — and Ivy's model is similarly transparent:

  1. You have a DbContext with your tables
  2. You write LINQ queries
  3. LINQ translates to SQL
  4. Results render directly in your UI

No N+1 queries to debug (server-side rendering handles pagination). No lazy loading surprises (you control what loads). No repository interfaces to navigate.

When Direct Database Access Isn't the Right Fit

Ivy's direct database access works best when:

  • You're building internal tools, not public APIs
  • You have one primary database
  • Your team knows EF Core basics
  • Sub-millisecond query performance isn't the priority

If those conditions don't hold, the ORM debate matters. For internal dashboards, reporting tools, and admin panels? Just query your data.

Want to try Ivy? Check out the GitHub repo or join our Discord community.

⭐ If you found this useful, a star on GitHub goes a long way — it helps more .NET developers discover Ivy.

Renco Smeding

Written by

Renco Smeding