> cat why-i-avoid-ef-core.md

════════════════════════════════════════════════════════════

Why I Avoid EF Core in High-Scale Systems

Entity Framework is convenient, but convenience has costs. Here's why I reach for raw SQL and stored procedures when performance matters.

Date: January 15, 2024

════════════════════════════════════════════════════════════

When I started GeriGelir, I made a decision early on: no Entity Framework Core for the core transaction system. Here's why.

## The Convenience Trap

EF Core is fantastic for rapid development. You get:

  • - Automatic migrations
  • - LINQ queries
  • - Change tracking
  • - Easy relationships

But every abstraction has a cost, and EF Core's costs become visible at scale.

## What Happens Under Load

At 1,000 users, EF Core works fine. At 50,000+ users processing transactions simultaneously, you start seeing:

// This innocent-looking query
var user = await _context.Users
    .Include(u => u.Transactions)
    .Include(u => u.Rewards)
    .FirstOrDefaultAsync(u => u.Id == userId);

// Generates a JOIN explosion that kills your database

The N+1 problem, change tracking overhead, and unpredictable query generation become real bottlenecks.

## My Approach Instead

For GeriGelir's critical paths (transactions, payouts, referrals), I use:

  1. - Stored procedures for complex operations
  2. - Dapper for simple reads
  3. - Raw SQL when I need full control
// Explicit, predictable, fast
var transaction = await _connection.QuerySingleAsync<Transaction>(
    "CALL sp_process_transaction(@userId, @amount, @type)",
    new { userId, amount, type }
);

## When I Do Use EF Core

EF Core still has its place:

  • - Admin panels with low traffic
  • - Internal tools
  • - Prototyping
  • - CRUD operations that don't need optimization

The key is knowing when convenience is worth it and when it's not.

## The Real Lesson

Know your ORM's generated SQL. If you can't explain what query your code produces, you don't understand your system.

Every line of code that touches your database should be intentional. At scale, "it just works" isn't good enough.

>