Entity Framework Core 7 – Table Per Concrete Type (TPC) Davranışı
Merhaba,
Bu içeriğimizde EF Core 7 ile gelecek olan Table Per Concrete Type(TPC) davranış modellemesini inceliyor olacağız.
Table Per Concrete Type (TPC) Nedir?
Table Per Concrete Type modellemesinin ne olduğunu ortaya koyabilmek için öncelikle Table Per Type (TPT) ve Table Per Hierarchy (TPH) davranış modellemelerini hatırlayarak başlayalım.
TPT, merkezi bir tabloyu temsil edecek olan bir sınıf ile kalıtımsal olarak ilişkide olan entity sınıflarının her birine karşılık bir tablonun generate edilmesini sağlayan ve bunlar arasında birebir ilişki kuran kalıtımsal davranış modellemesidir. Yani anlayacağınız, özünde hiyerarşideki her sınıfa karşılık tablo oluşturmakta ve oluşturulan bir üst sınıfla birebir ilişki sağlamaktadır.
abstract public class ApplicationUser
{
public int Id { get; set; }
public string Name { get; set; }
}
abstract public class Student : ApplicationUser
{
public string School { get; set; }
}
public class PostGraduate : Student
{
public string Section { get; set; }
}
public class Teacher : ApplicationUser
{
public string Branch { get; set; }
}
şeklinde kalıtımsal olarak tasarlanmış olan entity’leri ele alırsak eğer bunların context üzerinde aşağıdaki gibi konfigüre edilmeleri neticesinde
class ApplicationDbContext : DbContext
{
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<PostGraduate> PostGraduates { get; set; }
public DbSet<Teacher> Teachers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>().ToTable("StudentsUsers");
modelBuilder.Entity<PostGraduate>().ToTable("PostGraduateUsers");
modelBuilder.Entity<Teacher>().ToTable("TeacherUsers");
}
.
.
.
}
veritabanına yansımaları aşağıdaki gibi olacaktır.
Dikkat ederseniz eğer ‘Student’ ve ‘Teacher’ entity’leri ‘ApplicationUser’ sınıfından, ‘PostGraduate’ entity’si ise ‘Student’ sınıfından kalıtım aldığından dolayı netice itibariyle her bir sınıfa karşılık oluşturulan tablo üst sınıfın tablosuna birebir bir ilişkide oluşturulmuş vaziyettedir.
await context.PostGraduates.AddAsync(new() { Name = "Gençay", School = "Aksaray Üni.", Section = "Yönetim Bilişim Sistemleri" });
await context.PostGraduates.AddAsync(new() { Name = "Okan", School = "Ankara Üni.", Section = "Matematik" });
await context.PostGraduates.AddAsync(new() { Name = "Rakıf", School = "ODTÜ", Section = "Felsefe" });
await context.Teachers.AddAsync(new() { Name = "Bülent", Branch = "Matematik" });
await context.SaveChangesAsync();
Ve bu tasarımda yukarıdaki gibi herhangi bir entity üzerinden kayıt girildiği taktirde tüm veriler kendisine karşılık tabloya gelecek şekilde aşağıdaki görseldeki gibi ilişkisel olarak eklenecektir.
TPH ise hiyerarşideki tüm entity’ler için tek bir tablo oluşturmakta ve bunlar arasındaki ayrım için bir Discriminator kolonu kullanmaktadır.
Yukarıdaki örnekte sunulan aynı entity’leri context üzerinde herhangi bir konfigürasyona tabi tutmadan direkt migrate edersek eğer aşağıdaki gibi beklenen tablonun inşa edildiğini göreceğiz.
Ve aynı değerleri bu tasarımda insert etmeye çalıştığımızda aşağıdaki gibi davranış sergilendiğini gözlemleyeceğiz.

Şimdi TPT ve TPH davranış modellemelerini hatırladığımıza göre artık TPC davranışının ne olduğuna odaklanabiliriz.
TPC‘ye, TPT stratejisine benzeyen lakin genel olarak performanslı davranışını sergileyen versiyonudur diyebiliriz. TPT, kalıtımsal hiyerarşideki tüm türler için tablo oluştururken TPC sade ve sadece concrete yani somut sınıflar için tablo oluşturmaktadır. Burada örneğimize bakarsanız eğer ‘ApplicationUser’ ve ‘Student’ abstract class olduklarından dolayı TPC‘de bunlara karşılık bir tablo üretilmeyecek dolayısıyla migrate neticesinde üretilen tablo yapılanması aşağıdaki gibi olacaktır.
Burada dikkat ederseniz eğer abstract class olan base türler içerisindeki member’lar ilgili tablolara fiziksel olarak eklenmiştir.
Peki, TPC’nin yapılandırmasını nasıl sağlayacağız? diye sorarsanız eğer bunun için aşağıdaki gibi bir konfigürasyon yeterli olacaktır.
class ApplicationDbContext : DbContext
{
.
.
.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>().UseTpcMappingStrategy();
modelBuilder.Entity<Student>().UseTpcMappingStrategy();
}
.
.
.
}
Görüldüğü üzere UseTpcMappingStrategy metodu sayesinde bildirimde bulunulan entity’nin davranışını TPC olarak yapılandırabiliyoruz. Burada bir tek TPC için değil UseTphMappingStrategy ve UseTptMappingStrategy fonksiyonları aracılığıyla diğer davranışlar için de yapılandırmaları gerçekleştirebiliyoruz. (TPH‘ın varsayılan strateji olduğunu unutmayın! Ekstradan bir konfigürasyon gerektirmez.)
TPC’de Primary Key Problemi
Şimdi TPC davranışının söz konusu olduğu durumda yukarıdaki satırlarda örnek olarak vermiş olduğumuz insert işlemini uygularsak eğer aşağıdaki gibi hatayla karşılaşacağız.
Normalde TPH stratejisinde çalışıyor olsaydık hangi entity olursa olsun her kayıt tek bir satır olarak temsil edileceğinden dolayı primary key ile ilgili bir problem söz konusu olmayacaktı. Aynı şekilde TPT stratejisinde de eklenen kayıt türüne göre hiyerarşiye uygun ilişkisel olarak tablolarla eşleşecek şekilde kayıt işlemi gerçekleştirileceği için de primary key açısından bir tutarsızlık meydana gelmeyecekti. Mevzu bahis TPC stratejisi ise verilerin insert süreci daha karmaşık hale gelecektir. Çünkü artık TPT‘den farklı olarak primary key’in ilişkisel olarak beslenebileceği ortak bir tablo bulunmamakta yahut TPH gibi eklenecek veriler tek bir satırda temsil edilmemektedir. O yüzden abstract class olarak tanımlanmış olan base entity’lerde ki primary key(Id) kolonlarına karşılık aşağıdaki gibi sequence yapılandırması oluşturulmalıdır.
class ApplicationDbContext : DbContext
{
.
.
.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasSequence("ApplicationUserIds");
modelBuilder.HasSequence("StudentIds");
modelBuilder.Entity<ApplicationUser>()
.UseTpcMappingStrategy()
.Property(p => p.Id)
.HasDefaultValueSql("NEXT VALUE FOR [ApplicationUserIds]");
modelBuilder.Entity<Student>()
.UseTpcMappingStrategy()
.Property(p => p.Id)
.HasDefaultValueSql("NEXT VALUE FOR [StudentIds]");
}
.
.
.
}
Böylece tekrardan TPC‘nin söz konusu olduğu durumlarda insert işlemine kalkarsak eğer veriler aşağıdaki gibi veritabanına kaydedilecektir.
Ayriyeten burada dikkat ederseniz eğer her iki tabloda da bir bütünlük varmış gibi ardışık bir sıralamada primary key değerleri artış göstermektedir. Bunu EF Core üzerinden bu verilerin üzerine yeni verilerle insert gerçekleştirdiğimizde daha net anlayabileceğiz;
Hangi Durumlarda Hangi Stratejileri Kullanmalıyız?
Entity’ler arası kalıtımsal durumlarda TPH stratejisi varsayılandır. Hiyerarşi ne kadar genişse büyüklük açısından doğru orantıda bir tablo inşa edilir ve genellikle tablonun birçok sütunu boş kalmaktadır. Ama bu durum sorgu performansları açısından bir sorun teşkil etmeyecek, her daim sorgulama tek bir tablo üzerinden gerçekleştirileceği için gayet verimli bir süreç geçirilecektir.
TPT ise nadiren iyi bir seçim olacaktır. Nihayetinde normalizasyona daha uygun bir tasarım sunmaktır. Ama bir entity’e karşılık tüm verileri birden fazla tabloya bölmesi veri sorgulama sürecinde ekstradan join maliyetlerine sebep olacaktır.
TPC stratejisi ise belirli concrete type’dan olan entity’ler için bilgilerin her daim tek bir tabloda tutulmasını sağlanmaktadır. Bu açıdan TPT‘nin gelişmiş versiyonu olarak düşünülebilir. Abstract türlerin kullanıldığı hiyerarşik tasarımlarda tercih edilebilir. TPH‘ye nazaran da hiçbir tabloda boş sütun barındırmayacağı ve Discriminator kolonu olmayacağı için boyut olarak daha az yer kaplayacaktır.
Eğer ki, yapılan çalışmada base türler üzerinden çalışmalar sergilenecekse ve base türün altındaki sınıflar üzerinde sorgulama işlemleri gerçekleştirilecekse TPH‘ı düşünebilirsiniz.
EF Core 7’yi Sağlıklı Kullanabilmek İçin
Son olarak EF Core 7 ile bu makaledeki içeriğe eşlik edebilmek için projenizde bulunması gereken salt kütüphaneleri bildirip içeriğimizi noktayalım.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Selamun aleyküm Hocam,
EF Core 6.0.10 versiyonunda Türkçe karakter desteği kaldırılmış sanırım. Sorunun çözümü hakkında bir malumatınız var mı?
Aleyküm selam,
Türkçe karakter desteği derken biraz daha açar mısın?
cd komutu ile dosya yolunu belirterek dotnet ef migrations add seklinde yazmam gerekiyormuş hocam. Sorun çözüldü.