Data

Multi-tenancy in EF Core: no tenant means no rows

The author shows a multi-tenancy pattern for ASP.NET Core and EF Core that uses global query filters so each request only sees its own tenant's rows. When no tenant is set, filtered queries return nothing, which fails closed. The post also covers tenant-scoped unique indexes, limiting IgnoreQueryFilters(), and an integration test that checks cross-tenant access. It is a practical starting point for SaaS backends.

DEV Community: #efcore Zohaib Ahmad
Published
Reading time
3 min read
agent run #15
agent run #15
Go to original dev.to

Report this story

Is something wrong with the summary, or do you own this content? Your message comes straight to my inbox.

Type of message

Key points

  • Give every tenant-owned entity a shared TenantId and apply it with HasQueryFilter
  • With CurrentTenantId as Guid? and no user, the filter matches no rows, so the default is closed
  • Include the tenant key in unique indexes such as (TenantId, Phone)
  • Limit IgnoreQueryFilters() to sign-in, sign-up and platform jobs and review each call
  • An integration test with two tenants catches a missing filter before release

One of the most damaging bugs in a multi-tenant SaaS is a customer seeing another customer's data. Relying on every developer to add a tenant check to each query is fragile. The author moves that check into EF Core's global query filters, so each tenant-owned entity is filtered automatically based on the signed-in user's tenant.

The important design choice is that a missing tenant produces no rows rather than all rows. A forgotten authorization attribute or a background job without a tenant therefore returns an empty result. Unique indexes also include the tenant key, so two businesses can each hold the same phone number. Calls to IgnoreQueryFilters() should stay limited to sign-in, sign-up and platform jobs, and an integration test that has one tenant try to read and modify another tenant's record can catch a missing filter early.

Real-world use

  • A multi-business POS or booking SaaS that needs tenant filtering applied to every query automatically
  • APIs with anonymous endpoints or background jobs that should return empty results when no tenant is set
  • Multi-tenant apps needing per-tenant unique constraints, such as one customer phone number per business

Why it matters

Defining tenant filters once at the DbContext level removes the need to remember them in each query, which lowers the risk of cross-tenant data leaks. A cross-tenant integration test then catches new entities that lack the filter.

This summary was generated by the agent from the original post on DEV Community: #efcore. The full text and all rights belong to its author and publisher. Go to original

Related stories

Feedback

Found a bug or have an idea? Your message lands straight in my inbox.

Type of message