EF Core'da her entity'nin, onu diğer kayıtlardan ayıran bir anahtarı vardır ve bu anahtar ilişkisel veritabanında primary key (birincil anahtar) olur. Entity Framework Core, adı Id veya <tip adı>Id olan property'yi konvansiyonla birincil anahtar yapar. Başka bir property'yi seçmek için [Key] attribute'u ya da HasKey kullanırsınız; birden çok kolondan oluşan composite key için [PrimaryKey(...)] ya da HasKey(c => new { ... }) yazarsınız. Birincil anahtarın yanında, ilişkilerin hedefi olabilen alternate key'ler de tanımlanabilir.

Bu yazı anahtarın tanımlanmasını, adlandırılmasını, desteklenen tipleri, yeni entity eklerken anahtar değerinin nasıl ele alındığını ve alternate key ile unique index arasındaki farkı anlatıyor. Anahtar değerlerinin veritabanında nasıl üretildiği (IDENTITY, sequence, GUID) değer üretimi yazısının konusu; anahtarların ilişkilerde foreign key ile nasıl buluştuğu ise ilişkiler yazısında anlatılıyor. Serinin tamamı EF Core öğrenme rehberi sayfasında.

Konvansiyon: Id ve TipAdıId

Konvansiyon: Id ve TipAdıId

Anahtar tanımlamanın en kısa yolu hiçbir şey yazmamaktır. EF Core, Id adlı ya da tip adının sonuna Id eklenmiş adı taşıyan property'yi birincil anahtar olarak yapılandırır. Aşağıdaki örnekte Car için Id, Truck için TruckId anahtar olur. Örnekte anahtarların string olduğuna dikkat edin; konvansiyon tipe değil ada bakar.

csharp
public class Car
{
    public string Id { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

public class Truck
{
    public string TruckId { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

Belgeler bir istisnayı ayrıca not eder: owned entity tipleri anahtar için farklı kurallar kullanır. Owned ve complex tipleri complex types ve owned entity yazısında ele alıyoruz. Anahtarı olmayan veriler için ise keyless entity tipleri vardır (model yapılandırma yazısı).

[Key] ve HasKey ile anahtarı seçmek

Anahtarınızın adı konvansiyona uymuyorsa, örneğin bir aracı plaka numarasıyla tanımlıyorsanız, anahtarı açıkça seçersiniz. Data annotations tarafında property'ye [Key] koyarsınız, Fluent API tarafında HasKey çağırırsınız:

csharp
public class Car
{
    [Key]
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

// Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => c.LicensePlate);
}

HasKey'in string alan bir aşırı yüklemesi de vardır. Belgelerdeki constructor örneğinde anahtar hiçbir property'de değil, yalnızca private bir alanda tutulur ve b.HasKey("_id") ile yapılandırılır. Bu tasarımda anahtarı yalnızca EF yazar ve okur; yeni kayıt oluşturan kodun anahtarı yanlışlıkla atamasının önüne geçilmiş olur. Alan, veritabanının ürettiği anahtarların yazılabilmesi için readonly olmamalıdır.

Composite key: birden çok kolondan anahtar

Bazen tek bir kolon kaydı tanımlamaya yetmez. Plaka numaraları yalnızca bir eyalet içinde tekilse, anahtar eyalet ve plakanın birleşimi olmalıdır. Bu tür anahtarlara composite key denir. Konvansiyonlar composite key'i yalnızca belirli durumlarda, örneğin owned tip koleksiyonlarında kendiliğinden kurar; normal entity'lerde composite key'i her zaman açıkça tanımlarsınız. Sınıf düzeyindeki [PrimaryKey] attribute'u property adlarını sırayla alır; Fluent API'de ise anonim tip kullanılır:

csharp
[PrimaryKey(nameof(State), nameof(LicensePlate))]
public class Car
{
    public string State { get; set; }
    public string LicensePlate { get; set; }

    public string Make { get; set; }
    public string Model { get; set; }
}

// Fluent API
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Car>()
        .HasKey(c => new { c.State, c.LicensePlate });
}

Composite key'in ilişkiler üzerinde de etkisi vardır: bu entity'ye işaret eden her foreign key de aynı sayıda kolondan oluşur. Ayrıca belgeler, veritabanının değer ürettiği anahtar konvansiyonunun yalnızca composite olmayan (tek kolonlu) sayısal ve GUID anahtarlar için kurulduğunu belirtir. Yani composite key'in parçaları için değerleri uygulamanızın vermesi beklenir. SQLite sağlayıcısında da AUTOINCREMENT konvansiyonu yalnızca composite key'in parçası olmayan tamsayı anahtarlar için uygulanır.

Primary key constraint adı

İlişkisel veritabanlarında birincil anahtar constraint'i konvansiyonla PK_<tip adı> adını alır. Kurumsal bir adlandırma standardınız varsa ya da var olan bir veritabanıyla uyumlu kalmanız gerekiyorsa bu adı HasName ile değiştirebilirsiniz:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasKey(b => b.BlogId)
        .HasName("PrimaryKey_BlogId");
}

Anahtar tipleri: int, Guid, string, byte[]

Anahtar tipleri: int, Guid, string, byte[]: int, long, short, Guid, string, byte[]

EF Core birincil anahtar olarak string, Guid, byte[] dahil her ilkel tipteki property'yi destekler. Ancak belgelerin uyardığı gibi her veritabanı her tipi anahtar olarak desteklemez. Bazı durumlarda anahtar değeri desteklenen bir tipe kendiliğinden dönüştürülebilir, aksi hâlde dönüşümü value converter ile sizin tanımlamanız gerekir.

Tip seçiminin değer üretimiyle de doğrudan ilişkisi vardır. Belgelere göre composite olmayan short, int, long ve Guid birincil anahtarlar, uygulama değer vermediğinde eklenen kayıtlar için değer üretilecek şekilde kurulur. SQL Server'da sayısal anahtar bir IDENTITY kolonu olur; GUID anahtarlar için ise sağlayıcı sıralı GUID değerlerini istemci tarafında üretir. string anahtarlar için böyle bir konvansiyon belgelenmemiştir; değeri uygulamanın vermesi gerekir. Ayrıntılar ve seçenekler değer üretimi yazısında.

Anahtar tipiKonvansiyonla değer üretimiDikkat edilecek nokta
int, long, shortEvet (tek kolonlu ise); SQL Server IDENTITYAçık değer vermek IDENTITY nedeniyle varsayılan olarak başarısız olur
GuidEvet (tek kolonlu ise); SQL Server'da istemcide sıralı GUIDDeğer veritabanına gitmeden bilinir
stringBelgelenmemiş; değeri uygulama verirSQL Server'da anahtar ise nvarchar(450); büyük/küçük harf karşılaştırması farklı olabilir
byte[]BelgelenmemişAnahtar olduğunda içerik karşılaştırması yapılır
CompositeHayırTüm parçaları uygulama verir

Yeni entity eklerken anahtar değeri

Belgelerin bu konudaki kuralı kısa ama önemlidir: yeni bir entity context'e eklendiğinde anahtar property'si her zaman varsayılan olmayan bir değer taşımalıdır. Değer veritabanında üretilecekse EF, takip amacıyla entity eklendiği anda geçici bir değer üretmeye çalışır. SaveChanges çağrıldıktan sonra bu geçici değer, veritabanının ürettiği gerçek değerle değiştirilir. Yani kaydetmeden önce entity'de gördüğünüz anahtar değeri kalıcı değildir; gerçek değeri kaydetme işleminden sonra okumalısınız.

Anahtar değerinin dolu ya da varsayılan olması, bağlantısız (disconnected) senaryolarda da belirleyicidir: istemciden gelen nesneleri context'e bağlarken EF'in onları yeni mi yoksa var olan kayıt mı saydığı bu değere bakılarak anlaşılır. Attach ve Update kullanımını disconnected entity yazısında ayrıntılı anlattık.

String ve binary anahtarlarda karşılaştırma

String anahtar kullanırken gözden kaçan bir ayrıntı karşılaştırma kuralıdır. Value conversion belgeleri şunu hatırlatır: SQL Server dahil bazı veritabanları string'leri varsayılan olarak büyük/küçük harfe duyarsız karşılaştırır, .NET ise varsayılan olarak duyarlı karşılaştırır. Bu yüzden "DotNet" değerini taşıyan bir foreign key, SQL Server'da "dotnet" birincil anahtarıyla eşleşir ama EF Core'da eşleşmez. Sonuç, uygulamanın ne yaptığına göre değişir; tipik olarak nesne grafikleri doğru bağlanmaz ya da FK değeri yanlış olduğu için güncellemeler başarısız olur.

Belgelerdeki çözüm, anahtar property'lerine büyük/küçük harfe duyarsız bir ValueComparer atamaktır. Belgeler bu kalıbın basit ASCII anahtarlar için çalıştığını, kültüre özgü karakterler içeren anahtarlarda başarısız olabileceğini ayrıca uyarır. ç, ğ, ı, ö, ş, ü gibi Türkçe harfler ASCII dışında kaldığı için bu uyarı Türkçe değer taşıyabilecek string anahtarlar için de geçerlidir; böyle değerleri anahtar yapmadan önce bu riski hesaba katın. Collation konusunun genel çerçevesi LINQ sorguları yazısında.

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
        v => v.ToUpper().GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .Metadata.SetValueComparer(comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
            b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
        });
}

Sabit uzunluklu kolonlar başka bir tuzak kurar. char(20) gibi bir tipte saklanan "dotnet" değeri, veritabanından boşluklarla tamamlanmış olarak geri gelir ve dolgusuz anahtarlarla doğru karşılaştırılmaz. Belgeler bunun için okuma sırasında Trim yapan bir value converter'ı yukarıdaki comparer ile birleştirmeyi önerir. Binary anahtarlarda ise EF Core kendisi doğru davranır: anahtar olmayan byte[] property'leri referansla karşılaştırılırken, anahtar olan byte[] property'leri içerik olarak karşılaştırılır, çünkü bir FK'nin PK ile aynı dizi nesnesini taşıması pek olası değildir.

Değer nesnesi olarak anahtar

Bazı projeler anahtarları tip güvenliği için küçük değer nesnelerine sarar: BlogKey ve PostKey gibi iki ayrı struct, bir blog anahtarının yanlışlıkla yazı anahtarına atanmasını derleme anında engeller. Bu anahtarlar value converter ile alttaki int kolonuna eşlenir. Belgeler bu kalıbı göstermenin onu önermek anlamına gelmediğini, soyutlamanın geliştirme deneyimine yardım edip etmediğini düşünmenizi ve anahtar değerleriyle doğrudan uğraşmak yerine navigation'ları ve üretilen anahtarları kullanmayı değerlendirmenizi açıkça not eder. Dönüştürücülü anahtar property'lerinin üretilen anahtar değerlerini kullanabilmesi EF Core 7.0 ile mümkün olmuştur.

csharp
public readonly struct BlogKey
{
    public BlogKey(int id) => Id = id;
    public int Id { get; }
}

public class Blog
{
    public BlogKey Id { get; set; }
    public string Name { get; set; }
}

modelBuilder.Entity<Blog>().Property(e => e.Id).HasConversion(
    v => v.Id,
    v => new BlogKey(v));

Alternate key: ikinci tekil tanımlayıcı

Alternate key, birincil anahtarın yanında her kaydı tekil olarak tanımlayan ikinci bir anahtardır ve bir ilişkinin hedefi olabilir. İlişkisel veritabanında alternate key kolonları üzerinde bir unique index/constraint oluşturulur; ona başvuran foreign key constraint'leri de bu kolonları hedef alır. Alternate key'ler çoğu zaman siz istemeden, ihtiyaç olduğunda konvansiyonla eklenir: bir ilişkide birincil anahtar olmayan bir property'yi hedef olarak gösterdiğinizde EF Core onu alternate key yapar. Aşağıdaki örnekte Post, blogu BlogId ile değil Url ile işaret eder; HasPrincipalKey çağrısı Blog.Url'yi alternate key yapar.

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasOne(p => p.Blog)
        .WithMany(b => b.Posts)
        .HasForeignKey(p => p.BlogUrl)
        .HasPrincipalKey(b => b.Url);
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public string BlogUrl { get; set; }
    public Blog Blog { get; set; }
}

İlişki olmadan da alternate key tanımlayabilirsiniz: tek property için HasAlternateKey(c => c.LicensePlate), birden çok property için HasAlternateKey(c => new { c.State, c.LicensePlate }). Konvansiyonla oluşan index ve unique constraint AK_<tip adı>_<property adı> biçiminde adlandırılır; composite alternate key'lerde property adları alt çizgiyle birleştirilir. Bu adı da HasName("AlternateKey_LicensePlate") ile değiştirebilirsiniz.

Geliştirme aşamasındaki EF Core 11: complex type içindeki anahtarlar

Yerel belgelerde, şu anda geliştirme aşamasında olan EF Core 11.0 için bir not daha var: anahtarlar, koleksiyon olmayan complex type'ların içindeki skaler property'leri kullanabilecek; örneğin HasKey(c => c.CustomerId.Value) ya da adla HasKey("CustomerId.Value"). Yol üzerindeki complex property'ler zorunlu olmalı; anahtar complex koleksiyonlardan ya da nullable complex property'lerden geçemeyecek. Bu özellik yayımlanmış EF Core 10'da yoktur; EF Core 11 yayımlanana kadar ayrıntılar değişebilir.

Sık yapılan hatalar

Sık yapılan hatalar: Anahtar property'sini setter'sız yapmak., Üretilen anahtara ekleme sırasında değer vermek., Composite key parçalarının veritabanında üretileceğini beklemek...
  • Anahtar property'sini setter'sız yapmak. Veritabanının ürettiği anahtarlar için okunur-yazılır bir property gerekir; salt okunurluk istiyorsanız private setter kullanın.
  • Üretilen anahtara ekleme sırasında değer vermek. EF entity'yi var olan kayıt sanıp güncellemeye çalışır.
  • Composite key parçalarının veritabanında üretileceğini beklemek. Değer üretimi konvansiyonu yalnızca tek kolonlu sayısal ve GUID anahtarlar içindir.
  • Büyük/küçük harf farkı olan string anahtarları gözden kaçırmak. Veritabanında eşleşen değerler EF tarafında eşleşmeyebilir.
  • Yalnızca tekillik için alternate key tanımlamak. Bu iş için unique index daha uygundur; alternate key ilişki hedefi olacaksa anlamlıdır.

Sık Sorulan Sorular

Guid mi int mi anahtar kullanmalıyım?

Belgeler ikisi için de konvansiyonla değer üretimi kurar. SQL Server'da int anahtar bir IDENTITY kolonu olur ve değeri veritabanı verir; Guid anahtarlarda ise sağlayıcı sıralı GUID'i istemcide üretir, bu da bağımlı bir kaydı aynı anda eklerken veritabanına ek bir gidiş dönüş gerektirmez. Seçim uygulamanızın ihtiyacına bağlıdır; belgeler bir sıralama vermez.

Birincil anahtarın adını PK_ ile başlamayacak şekilde değiştirebilir miyim?

Evet, HasKey(...).HasName("...") ile constraint adını istediğiniz gibi verebilirsiniz. Bu yalnızca veritabanındaki constraint adını etkiler, property adını değiştirmez.

Alternate key ile unique index arasında ne fark var?

İkisi de veritabanında tekillik sağlar. Alternate key ayrıca bir foreign key'in hedefi olabilir ve EF'te salt okunurdur. Yalnızca tekrarı engellemek istiyorsanız unique index yeterlidir.

Anahtarsız bir tabloyu nasıl eşlerim?

Birincil anahtarı olmayan tablolar ve view'lar için keyless entity tipi kullanılır; bu tipler değişiklik takibine girmez ve salt okunurdur. Ayrıntılar model yapılandırma yazısında.

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 · Değer üretimi ve IDENTITY · Index ve unique index · İlişkiler ve foreign key · Property ve kolon yapılandırma · Kalıtım ve anahtar üretimi