EF Core: LINQ vs. Raw SQL - A Surprising Benchmark

September 11, 2025 · 6 min

I’ve been there. You’re staring at a dashboard in your multi-tenant app, and it’s crawling. The culprit is a query filtering across tens of thousands of records. Your first instinct? “LINQ is too slow. I’ll just write the raw SQL myself.”

It’s a tempting thought. But dropping down to raw SQL isn’t a simple performance switch. Get it wrong, and you could introduce a security hole or, ironically, make performance even worse. This exact scenario burned me once in production, and the fix wasn’t what I expected.

Let’s break down when to stick with LINQ and when (and how) to safely write your own SQL in EF Core.

First, The Big One: Security

If you take one thing away from this post, let it be this: never concatenate user input into a SQL string. SQL injection isn’t a textbook problem; it’s a real threat that I’ve seen take down production systems.

It’s easy to make this mistake, especially with C#’s slick string interpolation. This looks safe, right?

// DANGER: NEVER DO THIS.
var userId = Request.Query["userId"]; // e.g., "123"
var sql = $"SELECT * FROM Users WHERE Id = {userId}";
var users = await context.Users.FromSqlRaw(sql).ToListAsync();

This code is a wide-open door. An attacker can send 123; DROP TABLE Users; -- as the userId, and boom, your user table is gone.

The critical gotcha is that standard C# string interpolation ($"...") does not protect you. It just …

Read more

Fix Slow EF Core Queries with Projections

September 10, 2025 · 6 min

I’ve seen this happen a dozen times. An API endpoint is dog-slow, taking 2-3 seconds to respond. Everyone blames the network or the database. We check the query execution plan, and it’s fine—the database returns data in 50 milliseconds. So what’s the problem?

The problem is the 900KB of data we’re pulling from the database, serializing, and then throwing away for every single request. The culprit is a silent performance killer in Entity Framework Core: fetching full entities when you only need a few properties.

The Default Behavior is a Trap

It’s easy to write code like this. It’s clean, it’s simple, and it works.

var orders = await _dbContext.Orders
    .Where(o => o.Status == "Active")
    .ToListAsync();

But under the hood, EF Core is trying to be helpful by generating a SELECT * style query. If your Orders table has a few large NVARCHAR(MAX) columns for notes, addresses, or serialized metadata, you’re in for a bad time.

Here’s the SQL it probably generates:

SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate], [o].[Status], 
       [o].[Notes], [o].[ShippingAddress], [o].[BillingAddress],
       [o].[InternalComments], [o].[AuditData], [o].[SerializedMetadata]
FROM [Orders] AS [o]
WHERE [o].[Status] = 'Active'

If your API only needs to display the order ID, customer name, and total, you just paid a heavy price in network I/O and memory allocation for nothing. That SerializedMetadata column alone could be megabytes.

The Fix: Project Only What You Need

The …

Read more

Safely Log EF Core SQL Queries in Production

September 6, 2025 · 6 min

Of course. Here is the rewritten blog post, following your style guide and prompt.


Stop Flying Blind: Safely Log EF Core SQL in Production

I remember the incident vividly. P95 latency for our main API shot through the roof overnight. Our logs showed a bunch of slow HTTP requests, but nothing about why they were slow. We were flying blind. After hours of frantic debugging, we found the culprit: a seemingly innocent LINQ query was generating a monster SQL join, causing a full table scan on a massive table.

We had no visibility into what Entity Framework Core was doing. This one burned me, and I promised myself: never again.

Most developers face this. They either accept the black box and pray, or they enable dangerous logging flags that leak sensitive user data. There’s a much better way to see exactly what SQL EF Core is executing in production without compromising security.

TL;DR: Use a custom DbCommandInterceptor to log SQL, execution time, and parameter metadata (but never the values). Correlate logs with a TraceId and use sampling or thresholds to avoid log noise.

The Common Mistakes That’ll Get You Paged at 3 AM

Before we get to the right way, let’s talk about the traps. I’ve seen these “temporary fixes” make their way into production code, and the results are always painful.

  • Never use EnableSensitiveDataLogging() in production. I can’t stress this enough. It logs parameter values. That means passwords, personal user info, and …

Read more

EF Core vs. Dapper: A Pragmatic Guide for Production Systems

September 1, 2025 · 7 min

I’ll never forget the first time I got called at midnight for a performance issue that was my fault. A dashboard in our SaaS app was timing out, and customers were angry. After hours of digging, the culprit was a monster LINQ query generated by Entity Framework Core. It was trying to be clever, but the generated SQL was a performance disaster.

We rewrote that one query using raw SQL with Dapper, and boom: the endpoint went from taking 15 seconds to under 200 milliseconds.

That experience taught me a crucial lesson. The endless debate about EF Core vs. Dapper is asking the wrong question. It’s not about which one is “better.” It’s about knowing which tool to grab for the job at hand. This isn’t about benchmarks; it’s about shipping features that are fast and easy to maintain.

Let’s break down how I decide in the real world.

EF Core: Your Go-To for Business Logic

I start every new project with EF Core. The productivity boost is just too massive to ignore. It shines when your main job is translating complex business rules into code.

Modeling Your Business, Not Just Tables

When your app has real business rules, you want your code to reflect that. EF Core’s mapping for C# objects (POCOs) is fantastic. Features like navigation properties, value objects, and owned types let you build a rich domain model that makes sense.

Think about an Order class. With EF Core, it’s clean and reflects the business.

public class Order
{ …

Read more