Discriminator Column in EF Core: A Quick Guide

Feb 17 2025

Many thanks to the sponsors who make it possible for this newsletter to be free for readers.

 

• With Postman’s AI Agent Builder making waves, I can’t wait to see what’s in store at POST/CON this year. Last year was phenomenal - you remember I attended it in SF. Sign up for updates now! Save the date and join the mailing list here.

 

• I launched my YouTube channel and built TheCodeMan Community - your hub for .NET content, mini-courses, and expert advice! The first 100 members get in for just $4/month! 70 member are already there. 🚀 Join now and grab my first ebook for free.

   

The Background

   

What is a Discriminator in Entity Framework?

 

In Entity Framework (EF) Core, a discriminator is a special column used in Table Per Hierarchy (TPH) inheritance to differentiate between multiple entity types stored in a single table.

 

How Discriminators Work?

 

When using inheritance in EF Core, all derived types are mapped to the same database table. To distinguish between different entity types, EF Core automatically adds a discriminator column in the table.

 

The discriminator column stores a string or an integer value representing the type of entity.

 

Let's see it through example.

   

Discriminator in EF Core – Table Per Hierarchy (TPH)

   

Example: Single Table Inheritance

 

Imagine you have a Base Class Vehicle and two derived classes: Car and Bike.

 

Step 1: Define the Entities

public class Vehicle
{
    public int Id { get; set; }
    public string Model { get; set; } = string.Empty;
}

public class Car : Vehicle
{
    public int NumberOfDoors { get; set; }
}

public class Bike : Vehicle
{
    public bool HasGear { get; set; }
}

 

Step 2: Configure EF Core in DbContext

 

EF Core automatically adds a Discriminator column when you use TPH inheritance.

public class AppDbContext : DbContext
{
    public DbSet<Vehicle> Vehicles { get; set; }
    public DbSet<Car> Cars { get; set; }
    public DbSet<Bike> Bikes { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("your_connection_string");
    }
}

 

Step 3: Migration and Database Table

 

Run the following commands:

dotnet ef migrations add InitialCreate
dotnet ef database update

 

Generated Table (Single Table for All Entities)

Generated Table  

Discriminator: Determines if the row is for Car or Bike.
NULL values exist for columns that are not applicable to that entity.

   

Customizing the Discriminator in EF Core

   

By default, EF Core names the column Discriminator.
However, you can customize its name and values.

 

Example: Custom Discriminator Name and Values

 

Modify OnModelCreating in AppDbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Vehicle>()
        .HasDiscriminator<string>("VehicleType")
        .HasValue<Car>("CarType")
        .HasValue<Bike>("BikeType");
}

 

Effect on Database Table

Generated Table with types

   

Querying Entities with Discriminator

   

EF Core automatically filters data based on the discriminator.

 

Query All Vehicles

var vehicles = context.Vehicles.ToList();
Returns all records (both Cars and Bikes).

 

Query Only Cars

var cars = context.Cars.ToList();

 

EF Core translates this into:

SELECT * FROM Vehicles WHERE VehicleType = 'CarType';

 

Query Only Bikes

var bikes = context.Bikes.ToList();

 

EF Core translates this into:

SELECT * FROM Vehicles WHERE VehicleType = 'BikeType';

   

Changing the Discriminator at Runtime

   

Sometimes, you may want to change the discriminator dynamically.

 

Example: Convert a Bike to a Car

var bike = context.Bikes.FirstOrDefault(b => b.Id == 2);
if (bike != null)
{
    var car = new Car
    {
        Id = bike.Id, // Keep the same ID
        Model = bike.Model,
        NumberOfDoors = 4
    };

    context.Bikes.Remove(bike);
    context.Cars.Add(car);
    context.SaveChanges();
}

 

The discriminator column value changes from 'BikeType' to 'CarType'.

   

Using Enum as Discriminator

   

EF Core 8 and 9 support enums as discriminator values.

 

Step 1: Define an Enum

public enum VehicleType
{
    Unknown = 0,
    Car = 1,
    Bike = 2
}

 

Step 2: Apply Discriminator with Enum
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Vehicle>()
        .HasDiscriminator<VehicleType>("VehicleType")
        .HasValue<Car>(VehicleType.Car)
        .HasValue<Bike>(VehicleType.Bike);
}

 

Effect on Database Table:

Generated Table enum

   

Performance Considerations of Discriminators

   

Advantages of TPH (Discriminator-Based) Inheritance:
1. Faster queries (single table, no joins).
2. Easier schema management (no multiple tables).

 

Disadvantages of TPH:
1. Sparse columns (many NULL values in the table).
2. Potential table bloat (storing different types of data in a single table).

 

Alternative: Table Per Type (TPT)
Instead of using a discriminator, you can use TPT where each entity has its own table.

modelBuilder.Entity<Car>().ToTable("Cars");
modelBuilder.Entity<Bike>().ToTable("Bikes");

   

Wrapping Up

   

EF Core Discriminators enable Table Per Hierarchy (TPH) inheritance.

 

The discriminator column is used to differentiate entity types within a single table.

 

You can customize the discriminator name and values.
EF Core 8 & 9 allow enum-based discriminators.

 

Performance considerations should guide whether to use TPH (with discriminators) or TPT..

 

That's all from me today.

 

P.S. Follow me on YouTube.

dream BIG!

There are 3 ways I can help you:

My Design Patterns Ebooks

1. Design Patterns that Deliver

This isn’t just another design patterns book. Dive into real-world examples and practical solutions to real problems in real applications.Check out it here.


1. Design Patterns Simplified

Go-to resource for understanding the core concepts of design patterns without the overwhelming complexity. In this concise and affordable ebook, I've distilled the essence of design patterns into an easy-to-digest format. It is a Beginner level. Check out it here.


Join TheCodeMan.net Newsletter

Every Monday morning, I share 1 actionable tip on C#, .NET & Arcitecture topic, that you can use right away.


Sponsorship

Promote yourself to 15,250+ subscribers by sponsoring this newsletter.



Join 15,250+ subscribers to improve your .NET Knowledge.

Subscribe to
TheCodeMan.net

Subscribe to the TheCodeMan.net and be among the 15,250+ subscribers gaining practical tips and resources to enhance your .NET expertise.