Connection Per Tenant in EF Core: Design & Pitfalls for SQL Server

September 19, 2025 · 6 min

Multi-tenant SaaS applications face a critical decision: how to isolate tenant data while maintaining performance and security. One wrong move and Tenant A sees Tenant B’s customer records. I’ve seen this exact scenario destroy customer trust overnight.

The connection-per-tenant approach offers strong isolation but introduces complexity that can sink your architecture if not handled correctly. Let’s look at the real-world patterns, pitfalls, and production-ready solutions.

The Multi-Tenant Connection Problem

Most SaaS applications start simple: one database, one connection string, everything shared. This works until you need compliance certifications, enterprise customers demand data isolation, or you face your first security audit.

Common Early Mistakes

Shared Connection String Chaos: Using the same connection string for all tenants with TenantId filtering sounds efficient. Until someone forgets a WHERE clause and exposes everything.

Per-Tenant Schema Confusion: Mapping different schemas in EF Core without proper context switching leads to runtime errors that are painful to debug.

Security Breach Example: I’ve consulted on incidents where a missing global query filter resulted in tenant data leakage. The fix took hours, but the damage to customer relationships lasted months.

Multi-Tenant Database Design Patterns

Here are three proven approaches for handling tenant connections, each with distinct trade-offs:

Database Per Tenant (Full Isolation) …

Read more

TL;DR: My EF Core Entity Modeling Checklist

September 17, 2025 · 7 min

A few years back, I spent a solid week chasing a weird performance bug. Queries that should have taken milliseconds were sometimes taking seconds to run. The culprit? A simple string property on a core entity. Nobody had set a max length, so EF Core defaulted it to nvarchar(max). SQL Server handles those columns differently, and it was wrecking our query plans.

This one burned me in production, and it taught me a hard lesson: your entity model is the foundation of your application’s performance. Get it wrong, and you’ll pay for it with slow queries and painful migrations.

This checklist is the result of modeling dozens of schemas for EF Core on SQL Server. I’m focusing on SQL Server here because its defaults and indexing behaviors are unique.

Getting the Basics Right: The Entity Itself

Let’s start with the entity class. If you mess this up, everything else gets more complicated.

  • Primary Keys: Just use Id or [EntityName]Id. Don’t get clever. I avoid composite keys unless the domain model is impossible without them. They make joins and repository logic a pain to write and maintain.

    // Good: Simple, clean, effective.
    public int Id { get; private set; }
    
    // Avoid: This complicates everything.
    public class OrderItemKey
    {
        public int OrderId { get; set; }
        public int ProductId { get; set; }
    }
    
  • Value Objects: For data that has no identity on its own, use Owned Types. An Address or a Money value doesn’t need its own Id. It belongs to its parent. This …

Read more

EF Core Owned Types: When to Use Them in SQL Server

September 16, 2025 · 6 min

I once inherited a codebase where every value object was an EF Core Owned Type. The C# models looked clean, but the main Orders table in SQL Server had over 150 columns. Queries were timing out, and simple reports were a nightmare to build.

The culprit? A well-intentioned feature used in the wrong way.

Owned Types are a great tool, but if you don’t understand how they translate to SQL Server, you can create a maintenance headache that’s hard to untangle. After using them in production for years, I’ve learned where they shine and where they just cause pain.

This is my practical take for anyone using EF Core 8+ with SQL Server.

What’s actually happening under the hood?

Owned Types let you group related properties in your C# code without creating a separate table in the database. Think of an Address object on a Customer. In your code, it’s a neat, self-contained object. In the database, EF Core flattens it right into the Customers table.

It’s a way to keep your domain model expressive without cluttering your database schema with tiny tables and foreign keys for things that don’t need their own identity.

The simplest example

Here’s the classic Address value object stored with a Customer.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address BillingAddress { get; set; }
}

[Owned]
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public …

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

How to Find and Fix N+1 Queries in EF Core

September 9, 2025 · 6 min

The API was screaming. A single endpoint, perfectly fine on my machine, was bringing our database to its knees in production. After digging through the logs, I found the culprit: one request was generating 241 separate SQL queries.

This one burned me badly early in my career. It’s a classic trap that every developer using an ORM like Entity Framework Core will eventually fall into: the N+1 query problem. It’s silent in development but absolutely lethal at scale.

So, What’s an N+1 Query Anyway?

It’s a sneaky performance bug. You ask EF Core for a list of items, and it happily obliges with one query. That’s the “1”.

Then, you loop through that list, and for each item, you access a related property (like a post’s author). If you haven’t told EF Core to load that related data upfront, it goes back to the database for every single item. That’s the “N”.

So, to get 100 blog posts and their authors, you end up with:

  • 1 query to get the 100 posts.
  • 100 more queries to get each post’s author.
  • Total: 101 queries for what should have been a simple operation.

In that production disaster I mentioned, fixing the N+1 pattern dropped the query count from 241 to just 3. The response time fell by over 85%.

How to Spot an N+1 Ambush

You can’t fix what you can’t see. Here are my go-to methods for hunting down these hidden performance killers.

1. Just Watch the Logs

This is the most direct way to see the problem. Turn on …

Read more

5 EF Core Defaults to Change Before Production

September 8, 2025 · 6 min

I once spent half a day chasing a performance bug in an API endpoint. It was a simple read operation, but it was inexplicably slow and chewing up memory under load. The culprit? A single, innocent-looking Entity Framework Core default that was tracking thousands of objects for changes that would never happen.

EF Core is fantastic, but its defaults are designed for getting-started tutorials, not production applications. They prioritize ease of use over performance and safety. Here are the five settings I change in every single production project before writing a single line of business logic.

1. The Silent Killer: QueryTrackingBehavior.TrackAll

By default, every time you query for data, EF Core’s DbContext assumes you’re going to change it. It loads the entities and sets up change tracking, which consumes memory and CPU cycles to keep an eye on every property.

This is a complete waste for the 90% of queries that are read-only: fetching data for an API response, a report, or a UI display. I’ve seen this add 20-40% overhead on high-traffic endpoints. It’s the number one performance gotcha I find in code reviews.

The Fix: Turn it off globally.

// In your Program.cs or Startup.cs
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString)
           .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

This simple change makes all queries non-tracking by default. For the rare cases where you do need to fetch and then …

Read more