EF Core'da her entity property'si ilişkisel veritabanında bir tablo kolonuna eşlenir. Kolonun adı, veri tipi, uzunluğu, ondalık hassasiyeti, Unicode olup olmadığı ve boş geçilip geçilemeyeceği konvansiyonla belirlenir; bunları [Column], [MaxLength], [Precision], [Required] gibi attribute'larla ya da HasColumnName, HasColumnType, HasMaxLength, HasPrecision, IsRequired gibi Fluent API çağrılarıyla değiştirirsiniz. Entity Framework Core ayrıca sınıfta hiç bulunmayan shadow property'leri ve property yerine bir alanı okuyup yazan backing field'ları da destekler.
Bu yazıda property düzeyindeki ayarları sırayla ele alıyoruz: dahil etme ve hariç tutma, kolon adı ve tipi, uzunluk, precision ve scale, Unicode, zorunlu ve isteğe bağlı property'ler (nullable reference types ile birlikte), collation, yorum, kolon sırası, shadow ve indexer property'ler, backing field'lar. Varsayılan değer ve hesaplanan kolonlar değer üretimi yazısının, tipin kendisini dönüştürmek value conversion yazısının konusu. Model düzeyindeki genel kurallar için model yapılandırma yazısına, serinin tamamı için EF Core öğrenme rehberi sayfasına bakabilirsiniz.
Hangi property'ler modele girer?
Konvansiyon basittir: getter'ı ve setter'ı olan tüm public property'ler modele dahil edilir. Veritabanında karşılığı olmaması gereken bir property'yi, örneğin uygulamanın çalışma sırasında doldurduğu bir zaman bilgisini, [NotMapped] ile ya da Fluent API'deki Ignore ile dışarıda bırakırsınız. Setter'ı olmayan property'lerin konvansiyonla eşlenmediğini de unutmayın; belgeler bunun bilinçli olduğunu, aksi hâlde hesaplanan property'lerin yanlışlıkla eşleneceğini açıklar.
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
[NotMapped]
public DateTime LoadedFromDatabase { get; set; }
}
// Fluent API karsiligi
modelBuilder.Entity<Blog>()
.Ignore(b => b.LoadedFromDatabase);Kolon adı
İlişkisel veritabanında property, varsayılan olarak kendisiyle aynı adı taşıyan kolona eşlenir. Mevcut bir veritabanına bağlanırken ya da ekip kolonlarda küçük harf ve alt çizgi kullanmayı tercih ettiğinde adı değiştirmek gerekir. [Column("blog_id")] veya HasColumnName("blog_id") bunu yapar. Kod tarafında property adı değişmez; yalnızca üretilen SQL ve migration'lar yeni kolon adını kullanır.
public class Blog
{
[Column("blog_id")]
public int BlogId { get; set; }
public string Url { get; set; }
}
// Fluent API
modelBuilder.Entity<Blog>()
.Property(b => b.BlogId)
.HasColumnName("blog_id");Kolon veri tipi

Kolon tipini veritabanı sağlayıcısı seçer. Seçim property'nin .NET tipine dayanır ama başka meta veriyi de hesaba katar: yapılandırılmış en fazla uzunluk, property'nin bir anahtarın parçası olup olmadığı gibi. Belgelerdeki örneğe göre SQL Server sağlayıcısı DateTime property'lerini datetime2(7), string property'lerini nvarchar(max) kolonlarına eşler; string bir anahtarın parçasıysa tip nvarchar(450) olur. Kolon tipini tam olarak kendiniz belirlemek isterseniz [Column(TypeName = ...)] ya da HasColumnType kullanırsınız. Aşağıdaki örnek Url'yi Unicode olmayan 200 karakterlik bir string, Rating'i ise precision 5, scale 2 olan bir decimal yapar:
public class Blog
{
public int BlogId { get; set; }
[Column(TypeName = "varchar(200)")]
public string Url { get; set; }
[Column(TypeName = "decimal(5, 2)")]
public decimal Rating { get; set; }
}
// Fluent API
modelBuilder.Entity<Blog>(
eb =>
{
eb.Property(b => b.Url).HasColumnType("varchar(200)");
eb.Property(b => b.Rating).HasColumnType("decimal(5, 2)");
});Tip adını elle yazmak güçlüdür ama bir bedeli vardır: yazdığınız metin doğrudan veritabanına gider, yani sağlayıcıya bağlı hâle gelir. varchar(200) SQL Server'da anlamlıdır, başka bir veritabanında farklı bir karşılığı olabilir. Çoğu durumda uzunluk, precision ve Unicode gibi genel ayarları vermek ve tipin adını sağlayıcıya bırakmak daha taşınabilir bir yoldur. Aşağıdaki üç bölüm bu genel ayarları anlatıyor.
En fazla uzunluk
[MaxLength(500)] ya da HasMaxLength(500), sağlayıcıya uygun kolon tipini seçmesi için bir ipucu verir; SQL Server'da bu örnek nvarchar(500) kolonu üretir. Uzunluk yalnızca dizi benzeri tiplerde, yani string ve byte[] için anlamlıdır. Burada sık karıştırılan bir nokta var: EF Core, veriyi sağlayıcıya göndermeden önce uzunluğu doğrulamaz. Doğrulama sağlayıcıya ya da veritabanına kalır. SQL Server hedeflendiğinde sınırı aşan bir değer, kolonun tipi fazlasını saklamaya izin vermediği için bir istisnayla sonuçlanır.
public class Blog
{
public int BlogId { get; set; }
[MaxLength(500)]
public string Url { get; set; }
}
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasMaxLength(500);Precision ve scale
Bazı veri tipleri precision ve scale ayarlarını destekler; bu ayarlar hem hangi değerlerin saklanabileceğini hem de kolonun ne kadar yer kaplayacağını belirler. Hangi tiplerin desteklediği veritabanına bağlıdır, ama belgelere göre çoğu veritabanında decimal ve DateTime destekler. decimal için precision, değeri ifade etmek için gereken toplam basamak sayısının üst sınırıdır; scale ise ondalık basamak sayısının üst sınırıdır. DateTime için precision saniyenin kesirli kısmındaki basamak sayısını belirler, scale kullanılmaz. Scale hiçbir zaman precision olmadan verilmez; bu yüzden attribute [Precision(precision, scale)], Fluent API ise HasPrecision(precision, scale) biçimindedir.
public class Blog
{
public int BlogId { get; set; }
[Precision(14, 2)]
public decimal Score { get; set; }
[Precision(3)]
public DateTime LastUpdated { get; set; }
}
// Fluent API: SQL Server'da decimal(14,2) ve datetime2(3) kolonlari
modelBuilder.Entity<Blog>()
.Property(b => b.Score)
.HasPrecision(14, 2);
modelBuilder.Entity<Blog>()
.Property(b => b.LastUpdated)
.HasPrecision(3);Uzunlukta olduğu gibi EF Core precision ve scale'i de doğrulamaz. Belgelerdeki SQL Server örneği bunun pratikteki etkisini gösterir: datetime tipindeki bir kolona precision verilemez, datetime2 ise 0 ile 7 arasında (ikisi dahil) precision alabilir. Para tutarları gibi kayıpsız saklanması gereken değerlerde precision ve scale'i açıkça vermek, sağlayıcının varsayılanına güvenmekten daha öngörülebilir bir şema üretir.
Unicode ve Unicode olmayan metin
Bazı veritabanlarında Unicode ve Unicode olmayan metin için ayrı tipler bulunur. SQL Server'da nvarchar(x) Unicode veriyi UTF-16 olarak, varchar(x) ise Unicode olmayan veriyi saklar. Bu kavramı desteklemeyen veritabanlarında ayarın bir etkisi yoktur. EF Core'da metin property'leri varsayılan olarak Unicode'dur. ISBN gibi yalnızca ASCII karakter içerdiğini bildiğiniz alanlarda [Unicode(false)] veya IsUnicode(false) ile Unicode'u kapatabilirsiniz. Türkçe karakter içerebilecek ad, adres, açıklama gibi alanlarda varsayılan Unicode davranışını korumak gerekir; aksi hâlde kolon tipi bu karakterleri taşıyamayabilir.
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
[Unicode(false)]
[MaxLength(22)]
public string Isbn { get; set; }
}
modelBuilder.Entity<Book>()
.Property(b => b.Isbn)
.IsUnicode(false);SQL Server sağlayıcısının belgeleri bir ara yol da gösterir: SQL Server 2019 ile gelen UTF-8 desteği sayesinde char ve varchar kolonlarında, adı _UTF8 ile biten bir collation kullanarak UTF-8 veri saklanabilir. EF Core tarafında bunun için kolon tipini varchar yapar, UTF-8 collation'ı verir ve property'yi Unicode olarak işaretlersiniz:
modelBuilder.Entity<Blog>()
.Property(b => b.Name)
.HasColumnType("varchar(max)")
.UseCollation("LATIN1_GENERAL_100_CI_AS_SC_UTF8")
.IsUnicode();Zorunlu ve isteğe bağlı property'ler

Bir property null değer alabiliyorsa isteğe bağlı (optional), alamıyorsa zorunlu (required) sayılır. İlişkisel şemada zorunlu property'ler NOT NULL, isteğe bağlı olanlar NULL kabul eden kolonlar olarak oluşturulur. Konvansiyon .NET tipine bakar: int, decimal, bool gibi değer tipleri zorunlu, int?, decimal?, bool? gibi nullable değer tipleri isteğe bağlıdır.
Referans tiplerinde (en sık string) sonuç, C# nullable reference types (NRT) özelliğinin açık olup olmadığına göre değişir. Belgelere göre NRT yeni proje şablonlarında varsayılan olarak açıktır, mevcut projelerde ise siz açmadıkça kapalı kalır. NRT kapalıyken tüm referans tipli property'ler konvansiyonla isteğe bağlıdır. NRT açıkken C#'taki işaret belirleyicidir: string? isteğe bağlı, string zorunlu olur. Belgeler NRT kullanımını önerir, çünkü aynı bilgiyi hem C# kodunda hem Fluent API veya attribute ile iki kez ifade etmeniz gerekmez.
// NRT acik
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; } // konvansiyonla zorunlu
public string LastName { get; set; } // konvansiyonla zorunlu
public string? MiddleName { get; set; } // konvansiyonla istege bagli
public Customer(string firstName, string lastName, string? middleName = null)
{
FirstName = firstName;
LastName = lastName;
MiddleName = middleName;
}
}
// NRT kapali: zorunluluk icin attribute gerekir
public class CustomerWithoutNullableReferenceTypes
{
public int Id { get; set; }
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public string MiddleName { get; set; } // istege bagli
}NRT açıkken derleyici, başlatılmamış nullable olmayan her property için uyarı verir. Belgeler bunun için üç yol gösterir. C# 11 ve sonrasında public required string Name { get; set; } biçimindeki required members en temiz çözümdür: derleyici, kodunuz nesneyi oluştururken Name'in atanmasını garanti eder, EF'in yüklediği nesnelerde de kolon NOT NULL olduğu için değer hep doludur. Daha eski C# sürümlerinde constructor binding aynı işi görür (yukarıdaki Customer örneği). Navigation property'ler constructor ile başlatılamadığı için orada null-forgiving operatörüyle = null!; yazılabilir. DbContext üzerindeki başlatılmamış DbSet property'leri için de belgeler, EF Core 7.0 ve sonrasının bu uyarıyı bastırdığını, çünkü EF'in bu property'leri yansıma ile kendisinin doldurduğunu belirtir.
Konvansiyonla isteğe bağlı olacak bir property'yi açıkça zorunlu yapmak için [Required] veya IsRequired() kullanırsınız. Bu özellikle NRT kapalı projelerde ve eski kod tabanlarında sık gerekir.
Collation, kolon yorumu ve kolon sırası
Metin kolonlarına bir collation tanımlayarak karşılaştırma ve sıralamanın nasıl yapılacağını belirleyebilirsiniz. Belgelerdeki örnek bir SQL Server kolonunu büyük/küçük harfe duyarsız yapar: .UseCollation("SQL_Latin1_General_CP1_CI_AS"). Tüm kolonlar aynı collation'ı kullanacaksa bunu kolon kolon değil veritabanı düzeyinde tanımlamak önerilir. Collation'ların sorgu sonuçlarına etkisini LINQ sorguları yazısında ele aldık.
[Comment("The URL of the blog")] veya HasComment, kolona veritabanında görünen bir yorum ekler. Kolon sırası ise migration ile tablo oluşturulurken önemlidir: EF Core varsayılan olarak önce birincil anahtar kolonlarını, sonra entity tipinin ve owned tiplerin property'lerini, en son taban tiplerden gelen property'leri dizer. [Column(Order = 1)] veya HasColumnOrder(1) bu sırayı değiştirir; Fluent API, attribute'larla verilen sırayı ezebilir ve aynı numarayı taşıyan çakışmaları çözebilir. Belgelerin önemli uyarısı: çoğu veritabanı kolon sırasını yalnızca tablo oluşturulurken destekler, yani bu ayar mevcut bir tablodaki kolonları yeniden sıralamaz.
modelBuilder.Entity<Customer>().Property(c => c.Name)
.UseCollation("SQL_Latin1_General_CP1_CI_AS");
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasComment("The URL of the blog");
modelBuilder.Entity<Employee>(x =>
{
x.Property(b => b.Id).HasColumnOrder(0);
x.Property(b => b.FirstName).HasColumnOrder(1);
x.Property(b => b.LastName).HasColumnOrder(2);
});Shadow property: sınıfta olmayan kolon
Shadow property, .NET sınıfınızda tanımlı olmayan ama EF Core modelinde o entity tipi için tanımlı olan property'dir. Değeri ve durumu tamamen change tracker'da tutulur. Veritabanında bulunması gereken ama entity sınıfında açığa çıkmasını istemediğiniz veriler için kullanışlıdır; tipik bir örnek, kaydın son güncellenme zamanıdır.
En sık karşılaşılan shadow property'ler foreign key'lerdir. Bir ilişkide konvansiyonla bulunan ya da açıkça yapılandırılan bir FK property'si yoksa EF Core bir shadow FK ekler. Adı, bağımlı taraftaki navigation adı ile principal anahtar adının birleşimidir; principal anahtar adı zaten navigation adıyla başlıyorsa yalnızca anahtar adı kullanılır. Bağımlı tarafta navigation yoksa navigation adı yerine principal tipin adı geçer. Örneğin Post sınıfında yalnızca Blog navigation'ı varsa modele BlogId adlı bir shadow property eklenir. İlişki tarafındaki ayrıntılar ilişkiler yazısında anlatılıyor.
Kendi shadow property'nizi Property<DateTime>("LastUpdated") gibi string alan aşırı yüklemeyle tanımlarsınız; sonra diğer property'lerdeki tüm yapılandırma çağrılarını zincirleyebilirsiniz. Verdiğiniz ad sınıfta veya modelde zaten varsa yeni bir shadow property oluşmaz, var olan property yapılandırılır. Değeri change tracker API'si ile okur ve yazarsınız, LINQ sorgularında ise EF.Property statik metoduyla kullanırsınız:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property<DateTime>("LastUpdated");
}
// Deger atama (change tracker uzerinden)
context.Entry(myBlog).Property("LastUpdated").CurrentValue = DateTime.Now;
// Sorguda kullanma
var blogs = context.Blogs
.OrderBy(b => EF.Property<DateTime>(b, "LastUpdated"));Shadow property'lerin bir sınırı vardır: değerleri yalnızca change tracker'da yaşadığı için no-tracking bir sorgudan sonra bu değerlere erişilemez, çünkü dönen entity'ler takip edilmez. Takipli ve takipsiz sorguların farkını AsNoTracking yazısında anlattık.
Benzer bir kavram da indexer property'dir: sınıftaki bir indexer (this[string key]) üzerinden okunup yazılan property'ler. Fluent API'de IndexerProperty<DateTime>("LastUpdated") ile tanımlanır ve sınıfı değiştirmeden entity tipine yeni property eklemenizi sağlar. Yalnızca indexer property'lerden oluşan tiplere property bag entity tipi denir; belgelere göre şu an yalnızca Dictionary<string, object> bu amaçla desteklenir ve shared-type entity olarak, adıyla birlikte yapılandırılması gerekir.
Backing field: property yerine alan
Backing field, EF Core'un bir property yerine doğrudan bir alanı okuyup yazmasını sağlar. Sınıfınızda kapsülleme kullanıyor, örneğin değeri bir doğrulama metodundan geçirmeden atanmasını istemiyorsanız, ama veritabanından okurken ve veritabanına yazarken bu kısıtların devreye girmesini istemiyorsanız işe yarar. Konvansiyon, modele dahil property'ler için şu adlardaki alanları öncelik sırasıyla backing field olarak bulur: property adının camelCase hâli, başına alt çizgi eklenmiş camelCase hâli, başına alt çizgi eklenmiş asıl adı, başına m_ eklenmiş camelCase hâli ve başına m_ eklenmiş asıl adı. Yani Url property'si için _url alanı kendiliğinden bulunur.
Alan adı bu kalıplara uymuyorsa [BackingField(nameof(_validatedUrl))] veya HasField("_validatedUrl") ile açıkça belirtirsiniz. Belgelere göre doğru yapılandırılmış bir backing field varsa EF varsayılan olarak her zaman alanı okuyup yazar ve property'yi hiç kullanmaz. Bunu UsePropertyAccessMode ile değiştirebilirsiniz; örneğin PropertyAccessMode.PreferFieldDuringConstruction, alanı yalnızca nesne veritabanından oluşturulurken kullanır, diğer tüm durumlarda property'yi kullanır.
public class Blog
{
private string _validatedUrl;
public int BlogId { get; set; }
public string Url
{
get { return _validatedUrl; }
}
public void SetUrl(string url)
{
// dogrulama kodu burada
_validatedUrl = url;
}
}
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasField("_validatedUrl")
.UsePropertyAccessMode(PropertyAccessMode.PreferFieldDuringConstruction);Bir adım ötesi field-only property'dir: sınıfta hiç karşılık gelen property yoktur, veri yalnızca bir alanda tutulur ve modelBuilder.Entity<Blog>().Property("_validatedUrl") ile modele eklenir. EF önce bu adla bir property, bulamazsa bir alan arar; ikisi de yoksa bir shadow property oluşturur. Shadow property'den farkı, verinin change tracker'da değil entity nesnesinin kendisinde durmasıdır. Bu alanlar genellikle private olduğu için LINQ sorgusunda EF.Property<string>(b, "_validatedUrl") ile başvurulur.
Mini örnek: bir ürün tablosunu baştan tasarlamak
Öğrendiklerimizi tek bir entity üzerinde birleştirelim. Ürün adı Türkçe karakter içerebileceği için Unicode kalıyor ve uzunluğu sınırlanıyor; stok kodu yalnızca ASCII olduğu için Unicode kapatılıyor; fiyat için precision ve scale açıkça veriliyor; son güncelleme zamanı sınıfta görünmesin diye shadow property olarak tanımlanıyor. Örnek yalnızca belgelerde geçen API'leri kullanıyor; üretilecek kolon tipleri sağlayıcıya göre değişir.
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public required string Sku { get; set; }
public decimal Price { get; set; }
public string? Description { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(b =>
{
b.Property(p => p.Name).HasMaxLength(200);
b.Property(p => p.Sku).HasMaxLength(32).IsUnicode(false);
b.Property(p => p.Price).HasPrecision(14, 2);
b.Property(p => p.Description).HasComment("Urun aciklamasi");
b.Property<DateTime>("LastUpdated");
});
}NRT açık olduğu için Name ve Sku zorunlu, Description isteğe bağlı olur; ayrıca [Required] yazmaya gerek kalmaz. Id konvansiyonla birincil anahtar olur (primary key yazısı). Sku için tekillik istiyorsanız bunu bir unique index ile sağlarsınız (index yazısı).
Sık yapılan hatalar

- HasMaxLength'in uygulama tarafında doğrulama yaptığını sanmak. EF Core uzunluğu, precision'ı ve scale'i doğrulamaz; sınırı veritabanı uygular.
- Mevcut projede NRT'yi açıp üretilen migration'ı okumadan uygulamak. Referans tipli kolonlar NOT NULL'a dönebilir.
- Türkçe metin tutan bir kolonda IsUnicode(false) kullanmak. Unicode olmayan tip bu karakterleri saklayamayabilir; Unicode'u yalnızca ASCII olduğundan emin olduğunuz alanlarda kapatın.
- Var olan tablonun kolonlarını HasColumnOrder ile yeniden sıralamayı beklemek. Çoğu veritabanı sırayı yalnızca tablo oluşturulurken uygular.
- No-tracking sorgudan sonra shadow property değerine erişmeye çalışmak. Değer change tracker'da tutulduğu için takipsiz sonuçta yoktur.
- HasColumnType ile sağlayıcıya özgü tip adı yazıp sağlayıcı değiştirmek. Tip metni olduğu gibi veritabanına gider.
Sık Sorulan Sorular
SQL Server string kolonları neden nvarchar(max) oluyor?
Uzunluk vermediğiniz sürece SQL Server sağlayıcısı string property'leri nvarchar(max) kolonlarına eşler; property bir anahtarın parçasıysa nvarchar(450) kullanılır. HasMaxLength veya [MaxLength] ile uzunluk verdiğinizde tip buna göre seçilir. Tüm string'ler için tek bir varsayılan istiyorsanız ConfigureConventions ile toplu ayar yapabilirsiniz.
[Required] ile C# required anahtar kelimesi aynı şey mi?
Hayır. [Required] EF Core'a kolonun zorunlu olduğunu söyleyen bir eşleme attribute'udur. C# 11'deki required ise derleyicinin nesne oluşturulurken property'nin atanmasını zorunlu kılmasını sağlar. NRT açıkken nullable olmayan bir string zaten zorunlu sayıldığı için required anahtar kelimesi, derleyici uyarısını gidermek ve nesnenin hep geçerli oluşturulmasını sağlamak için kullanılır.
Shadow property ile backing field arasındaki fark nedir?
Shadow property'nin değeri change tracker'da tutulur ve sınıfta hiçbir alanı yoktur. Backing field ve field-only property'de ise değer entity nesnesinin bir alanında durur; EF yalnızca property yerine bu alanı okuyup yazar.
Kolon adlarını tek tek mi değiştirmem gerekir?
HasColumnName property düzeyinde çalışır. Çok sayıda property'ye aynı kuralı uygulamak istiyorsanız belgelerdeki toplu yapılandırma yöntemlerine, yani meta veri API'sine ya da kendi konvansiyonunuza bakmanız gerekir; bunları model yapılandırma yazısında özetledik.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: EF Core öğrenme rehberi · Model yapılandırma · Default value ve computed column · Value conversion · Primary key · İlişkiler ve foreign key