Entity Framework Core (version 7 and higher) supports different ways to map entity-types to relational database tables.
These mappings are:
- TPH ... Table-Per-Hierarchy
- TPT ... Table-Per-Type
- TPC ... Table-Per-Concrete
For detailed information visit:
In this article we will use the following sample entity-type hierarchy.
You can browse the entity-type and DbContext classes here Blog
, RssBlog
and RootDbContextBase
.
public class Blog
{
public Guid Id { get; set; }
public string Url { get; set; }
protected Blog(Guid id, string url)
{
Id = id;
Url = url;
}
public static Blog Create(string url)
{
return new Blog(Guid.NewGuid(), url);
}
}
public class RssBlog : Blog
{
public string RssUrl { get; set; }
private RssBlog(Guid id, string url, string rssUrl) : base(id, url)
{
RssUrl = rssUrl;
}
public static RssBlog Create(string url, string rssUrl)
{
return new RssBlog(Guid.NewGuid(), url, rssUrl);
}
}
public abstract class RootDbContextBase : DbContext, IRootDbContext
{
public DbSet<Blog> Blogs { get; set; } = null!;
public DbSet<RssBlog> RssBlogs { get; set; } = null!;
}
The following entity-type configuration is valid for all table mappings. See class RootDbContextBase
.
public abstract class RootDbContextBase : DbContext, IRootDbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Blog>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id)
.ValueGeneratedNever()
.IsRequired();
entity.Property(e => e.Url)
.HasMaxLength(100)
.IsRequired();
});
modelBuilder.Entity<RssBlog>(entity =>
{
entity.Property(e => e.RssUrl)
.HasMaxLength(200)
.IsRequired();
});
}
}
TPH table mapping:
The following entity-type configuration is valid for TPH table mapping. See class AppDbContextTph
.
public class AppDbContextTph : RootDbContextBase
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Blog>(entity =>
{
entity.UseTphMappingStrategy();
entity.ToTable("Blogs");
entity.HasDiscriminator<string>("BlogType")
.HasValue<Blog>(nameof(Blog))
.HasValue<RssBlog>(nameof(RssBlog));
});
modelBuilder.Entity<RssBlog>(entity =>
{
entity.HasBaseType<Blog>();
});
}
}
TPH sample table data:
TPT table mapping:
The following entity-type configuration is valid for TPT table mapping. See class AppDbContextTpt
.
public class AppDbContextTpt : RootDbContextBase
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Blog>(entity =>
{
entity.UseTptMappingStrategy();
entity.ToTable("Blogs");
});
modelBuilder.Entity<RssBlog>(entity =>
{
entity.HasBaseType<Blog>();
entity.ToTable("RssBlogs");
});
}
}
TPT sample table data:
TPC table mapping:
The following entity-type configuration is valid for TPC table mapping. See class AppDbContextTpc
.
public class AppDbContextTpc : RootDbContextBase
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Blog>(entity =>
{
entity.UseTpcMappingStrategy();
entity.ToTable("Blogs");
});
modelBuilder.Entity<RssBlog>(entity =>
{
entity.HasBaseType<Blog>();
entity.ToTable("RssBlogs");
});
}
}
TPC sample table data:
Run applications:
$ ./app_run.sh tph # table-per-hierarchy
$ ./app_run.sh tpt # table-per-type
$ ./app_run.sh tpc # table-per-concrete
Then open Swagger API explorer:
https://localhost:7196/swagger
http://localhost:5102/swagger
$ dotnet --version
8.0.100
$ dotnet ef --version
Entity Framework Core .NET Command-line Tools
8.0.0
$ sqlite3 --version
3.43.2