EF Core scaffold, var olan bir veritabanının şemasını okuyup ondan bir DbContext sınıfı ve her tablo için bir entity sınıfı üreten tersine mühendislik (reverse engineering) işlemidir; “database first” yaklaşımı olarak da bilinir. Entity Framework Core'da bunu .NET CLI ile dotnet ef dbcontext scaffold, Visual Studio Package Manager Console ile Scaffold-DbContext komutuyla yaparsınız. İki komut da bağlantı dizesi ve sağlayıcı adı olmak üzere iki zorunlu argüman alır.

Bu yazı scaffold'un ön koşullarını, bağlantı dizesinin koda gömülmesini engellemeyi, tablo ve şema seçimini, adlandırma ve çoğullaştırma seçeneklerini, data annotation kullanımını, üretilen kodun yapısını, kodu yeniden scaffold ederken değişikliklerinizi korumayı, T4 şablonlarıyla özelleştirmeyi ve sınırlamaları anlatır. Şemanın kaynağı veritabanı değil de C# modeliniz olacaksa doğru yol migration'dır: EF Core migration rehberi. Serinin tamamı için EF Core öğrenme rehberi sayfasına bakın.

Ne zaman scaffold kullanılır?

Belgeler model ile şemayı senkron tutmanın iki ana yolunu, doğruluğun kaynağının hangisi olduğuna göre ayırır. Veritabanı şeması esassa, örneğin yıllardır çalışan bir ERP veritabanına bağlanan yeni bir .NET servisi yazıyorsanız ya da tabloları bir DBA ekibi yönetiyorsa, scaffold ile şemadan model üretirsiniz. C# modeli esassa migration kullanırsınız. İkisi birbirini dışlamaz: scaffold'u bir kez başlangıç noktası olarak kullanıp sonra migration'a geçmek de belgelerde anlatılan bir yoldur (aşağıda).

Ön koşullar

Ön koşullar: PMC araçları (yalnızca Visual Studio) veya tüm platformlarda çalışan .NET CLI..., Scaffold yapacağınız projeye Microsoft.EntityFrameworkCore.Design NuGet paketi...
  • PMC araçları (yalnızca Visual Studio) veya tüm platformlarda çalışan .NET CLI araçları kurulu olmalı. Kurulum için dotnet ef komutları yazısına bakın.
  • Scaffold yapacağınız projeye Microsoft.EntityFrameworkCore.Design NuGet paketi eklenmeli.
  • Okuyacağınız veritabanını hedefleyen sağlayıcının NuGet paketi (örneğin Microsoft.EntityFrameworkCore.SqlServer) eklenmeli. Sağlayıcı listesi için veritabanı sağlayıcıları.
  • Okunacak tabloların birincil anahtarı olmalı: CLI başvurusuna göre komutun bir tablo için entity tipi üretebilmesi için tablonun birincil anahtarı bulunmalıdır.

Temel komut ve iki zorunlu argüman

Birinci argüman bağlantı dizesidir; araçlar şemayı bu bağlantıyla okur. İkinci argüman sağlayıcı adıdır ve genellikle sağlayıcının NuGet paket adıyla aynıdır; SQL Server ve Azure SQL için Microsoft.EntityFrameworkCore.SqlServer. Belgelerdeki örnek, makinedeki SQL Server LocalDB örneğinde duran Chinook veritabanından model üretir:

bash
dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook" Microsoft.EntityFrameworkCore.SqlServer
powershell
Scaffold-DbContext 'Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Chinook' Microsoft.EntityFrameworkCore.SqlServer

Bağlantı dizesinin nasıl tırnaklanıp kaçış karakteriyle yazılacağı kullandığınız kabuğa bağlıdır. Belgelerin verdiği örnek: PowerShell'de $ karakterinin kaçırılması gerekir, \ karakterinin gerekmez. Parolasında $ veya tırnak bulunan bir bağlantı dizesi kabukta beklenmedik biçimde değişebilir; bu da sizi aşağıdaki Name= sözdizimine yönlendiren iyi bir nedendir.

Bağlantı dizesini koda gömmemek

Varsayılan olarak scaffold, bağlantı dizesini üretilen OnConfiguring metoduna yazar ve yanına bir derleyici uyarısı ekler. Belgelerdeki örnek çıktı şöyledir:

csharp
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
    => optionsBuilder.UseSqlServer("Data Source=(LocalDb)\\MSSQLLocalDB;Database=AllTogetherNow");

Belgeler bunun, üretilen kod ilk kullanımda çökmesin diye yapıldığını açıklar; ancak uyarının dediği gibi bağlantı dizeleri production kodunda bulunmamalıdır. İki temiz çözüm var. Birincisi Name= sözdizimidir: bağlantı dizesini yapılandırmadan (örneğin Secret Manager ile user secrets'tan) okutursunuz ve scaffold koda gerçek değeri değil yalnızca adı yazar. İkincisi --no-onconfiguring (PMC'de -NoOnConfiguring) seçeneğidir: OnConfiguring metodu hiç üretilmez; context'i dışarıda, örneğin bağımlılık enjeksiyonu kaydında AddDbContext ile yapılandırırsınız.

bash
dotnet user-secrets set ConnectionStrings:Blogging "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Blogging"
dotnet ef dbcontext scaffold Name=ConnectionStrings:Blogging Microsoft.EntityFrameworkCore.SqlServer
bash
dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=true;" Microsoft.EntityFrameworkCore.SqlServer --no-onconfiguring

Bağlantı dizesi yönetiminin tamamı (appsettings, user secrets, güvenli saklama) connection string yönetimi yazısında, context'i AddDbContext ile kaydetmek ise DbContext yapılandırma yazısında anlatılıyor.

Hangi tablolar ve şemalar?

Hangi tablolar ve şemalar?

Varsayılan olarak veritabanındaki tüm tablolar ve view'lar entity tipine dönüştürülür. Büyük bir veritabanının yalnızca bir bölümüyle çalışacaksanız bunu daraltabilirsiniz. --schema (PMC'de -Schemas) belirtilen şemalardaki tüm tablo ve view'ları alır; bu seçenek kullanıldığında o şemalardaki her şey, --table ile ayrıca belirtilmemiş olsa bile modele girer. --table (kısaca -t, PMC'de -Tables) tek tek tablo veya view seçer; belirli bir şemadaki tablo için schema.table biçimi kullanılır. CLI'da birden fazla değer için seçeneği tekrarlarsınız, PMC'de virgülle ayırırsınız:

bash
dotnet ef dbcontext scaffold ... --table Artist --table Album
dotnet ef dbcontext scaffold ... --schema Customer --schema Contractor
dotnet ef dbcontext scaffold ... --table Customer.Purchases --table Contractor.Accounts --table Contractor.Contracts
powershell
Scaffold-DbContext ... -Tables Artists, Albums
Scaffold-DbContext ... -Schemas Customer, Contractor

Belgelerdeki örneklerde CLI'da tablo adları tekil (Artist, Album), PMC örneğinde çoğul (Artists, Albums) yazılmış; siz her zaman veritabanındaki gerçek tablo veya view adını kullanın. Şema ve tablo seçeneklerini birlikte kullandığınızda sonuç, seçilen şemaların tamamı ile ayrıca listelenen tabloların birleşimidir.

Adlandırma: --use-database-names ve --no-pluralize

Scaffold varsayılan olarak tablo ve kolon adlarını .NET adlandırma kurallarına yaklaştırır. --use-database-names (PMC'de -UseDatabaseNames) bu düzeltmeyi kapatır ve adları mümkün olduğunca veritabanındaki gibi korur; yine de geçersiz .NET tanımlayıcıları düzeltilir ve navigation gibi sentezlenen adlar .NET kurallarına uyar. Çoğullaştırıcıyı (pluralizer) kapatmak için --no-pluralize (PMC'de -NoPluralize) kullanılır. Belgelerdeki örnek tablolar şunlardır:

sql
CREATE TABLE [BLOGS] (
    [ID] int NOT NULL IDENTITY,
    [Blog_Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([ID]));

CREATE TABLE [posts] (
    [id] int NOT NULL IDENTITY,
    [postTitle] nvarchar(max) NOT NULL,
    [post content] nvarchar(max) NOT NULL,
    [1 PublishedON] datetime2 NOT NULL,
    [2 DeletedON] datetime2 NULL,
    [BlogID] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogID]) REFERENCES [Blogs] ([ID]) ON DELETE CASCADE);

Varsayılan ayarlarla belgelerdeki çıktı Blog ve Post sınıflarıdır; Blog_Name kolonu BlogName, post content kolonu PostContent, rakamla başlayan 1 PublishedON kolonu ise geçerli bir tanımlayıcı olsun diye _1PublishedOn olur. --use-database-names ile aynı tablolar BLOG ve post sınıflarına, Blog_Name, postTitle, post_content gibi property'lere dönüşür. Aşağıdaki kod belgelerin varsayılan çıktısından kısaltılmıştır:

csharp
public partial class Blog
{
    public int Id { get; set; }
    public string BlogName { get; set; } = null!;
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

public partial class Post
{
    public int Id { get; set; }
    public string PostTitle { get; set; } = null!;
    public string PostContent { get; set; } = null!;
    public DateTime _1PublishedOn { get; set; }
    public DateTime? _2DeletedOn { get; set; }
    public int BlogId { get; set; }
    public virtual Blog Blog { get; set; } = null!;
}

Hangi seçeneği kullanacağınız ekibinizin tercihine bağlıdır. C# kurallarına uyan adlar kod tarafında daha doğal okunur; veritabanı adlarını korumak ise SQL ile C# arasında gidip gelirken eşleştirmeyi kolaylaştırır. Çoğullaştırıcının ürettiği koleksiyon ve sınıf adlarını beğenmezseniz --no-pluralize ile kapatabilir ya da aşağıda anlatılan T4 şablonlarıyla navigation adlarını kendiniz belirleyebilirsiniz; belgeler şablonlarda navigation adlarının değiştirilebileceğini açıkça örnek verir.

Fluent API mi, data annotation mı?

Entity tipleri varsayılan olarak OnModelCreating içindeki ModelBuilder API'siyle (Fluent API) yapılandırılır. --data-annotations (kısaca -d, PMC'de -DataAnnotations) ile mümkün olan yerlerde bunun yerine eşleme attribute'ları kullanılır:

csharp
// Fluent API ile
entity.Property(e => e.Title)
    .IsRequired()
    .HasMaxLength(160);

// --data-annotations ile
[Required]
[StringLength(160)]
public string Title { get; set; }

Modelin bazı yönleri attribute ile ifade edilemez; scaffold bu durumlarda yine model building API'sini kullanır. İki yaklaşımın genel karşılaştırması model yapılandırma yazısında var.

Context adı, klasörler ve namespace'ler

Üretilen DbContext sınıfının adı varsayılan olarak veritabanı adına Context eklenerek oluşur; farklı bir ad için --context (PMC'de -Context) kullanılır. Entity sınıfları ve context varsayılan olarak projenin kök klasörüne ve projenin varsayılan namespace'ine üretilir. --output-dir entity sınıflarının, --context-dir context sınıfının klasörünü belirler. Namespace varsayılan olarak kök namespace ile alt klasör adlarının birleşimidir; --namespace tüm sınıflar için, --context-namespace yalnızca context için bunu geçersiz kılar:

bash
dotnet ef dbcontext scaffold "Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -o Models -t Blog -t Post --context-dir Context -c BlogContext --context-namespace New.Namespace

Üretilen kod: nullable, ilişkiler, many-to-many

Scaffold sonucunda DbContext'ten türeyen bir sınıf dosyası ve her entity tipi için bir dosya oluşur. Projede C# nullable reference types (NRT) etkinse scaffold bunu otomatik kullanır: NOT NULL bir metin kolonu string ve = null! başlangıç değeriyle, NULL kabul eden bir kolon string? olarak üretilir. Zorunlu bir yabancı anahtar da null olamayan (required) bir ilişkiye dönüşür. Nullable konusunun ayrıntıları property ve kolon yapılandırma yazısında anlatılıyor.

Scaffold basit birleştirme (join) tablolarını da tanır. Yalnızca iki yabancı anahtardan oluşan bir PostTag tablosu için ayrı bir sınıf üretilmez; bunun yerine Post ve Tag sınıflarına birbirine bakan koleksiyonlar eklenir ve UsingEntity ile bir many-to-many yapılandırması yazılır:

csharp
entity.HasMany(d => d.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        r => r.HasOne<Tag>().WithMany().HasForeignKey("TagsId"),
        l => l.HasOne<Post>().WithMany().HasForeignKey("PostsId"),
        j =>
            {
                j.HasKey("PostsId", "TagsId");
                j.ToTable("PostTag");
                j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
            });

Many-to-many ilişkilerinin kendisi many-to-many yazısında anlatılıyor. Microsoft'un yayımladığı paketler C# kodu üretir; scaffold altyapısı başka diller için eklenti modelini destekler ve belgeler Visual Basic ile F# için topluluk projelerini örnek verir.

Bir kez mi scaffold, her değişiklikte mi?

Bir kez mi scaffold, her değişiklikte mi? Hem DbContext hem entity sınıfları partial üretilir. Ek üyeleri, scaffold..., DbContext sınıfı OnModelCreatingPartial adında bir partial...

Üretilen kodu değiştirmenin en iyi yolu, veritabanı değiştiğinde scaffold'u yeniden çalıştırmayı planlayıp planlamadığınıza bağlıdır. Belgeler iki yaklaşımı ayırır.

Bir kez scaffold

Scaffold edilen kod bundan sonraki kod tabanlı eşlemenin başlangıç noktası olur ve projedeki diğer kodlar gibi istediğiniz gibi değiştirilir. Veritabanı ile modeli senkron tutmanın iki yolu vardır: ya migration'a geçip entity tiplerini ve model yapılandırmasını doğruluğun kaynağı yaparsınız, ya da veritabanı değiştiğinde entity'leri ve yapılandırmayı elle güncellersiniz. Örneğin bir tabloya yeni kolon eklendiyse ilgili entity'ye property ekler, gerekiyorsa attribute veya OnModelCreating yapılandırması yazarsınız. Belgelere göre bu görece kolaydır; asıl zorluk, veritabanı değişikliklerinin kayda geçirildiği veya fark edildiği bir süreç kurmaktır.

Tekrarlı scaffold ve partial sınıflar

Diğer yol, veritabanı her değiştiğinde yeniden scaffold etmektir. Bu, daha önce üretilen kodun üzerine yazar; o koddaki tüm değişiklikler kaybolur. Kazara kod kaybını önlemek için komutlar varsayılan olarak mevcut dosyaların üzerine yazmaz; bunu bilinçli olarak --force (PMC'de -Force) ile istersiniz. Üretilen kod yeniden yazılacağı için onu doğrudan değiştirmek yerine EF Core'un geçersiz kılma mekanizmalarına güvenin:

  • Hem DbContext hem entity sınıfları partial üretilir. Ek üyeleri, scaffold sırasında üzerine yazılmayacak ayrı bir dosyaya koyabilirsiniz.
  • DbContext sınıfı OnModelCreatingPartial adında bir partial metot içerir. Bunu context'in partial sınıfında uygularsanız OnModelCreating'ten sonra çağrılır.
  • ModelBuilder API'siyle yapılan yapılandırma, konvansiyonlarla veya attribute'larla yapılan yapılandırmayı ve model builder'da daha önce yapılanı geçersiz kılar. Yani OnModelCreatingPartial içindeki kod, scaffold'un ürettiği yapılandırmayı silmeye gerek kalmadan onu ezebilir.
csharp
// Context.Partial.cs (scaffold bu dosyaya dokunmaz)
public partial class BlogContext
{
    partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Property(b => b.BlogName)
            .HasMaxLength(200);
    }
}

T4 şablonlarıyla üretilen kodu özelleştirmek

EF7'den itibaren üretilen kodu özelleştirmenin en etkili yollarından biri, onu üreten T4 metin şablonlarını değiştirmektir. Belgeler bunun, varsayılanlarla scaffold edip partial sınıflarla düzeltmekten çoğu zaman daha etkili olduğunu söyler. Önce EF Core şablon paketini dotnet new için kurar, ardından proje klasöründe varsayılan şablonları projeye eklersiniz:

bash
dotnet new install Microsoft.EntityFrameworkCore.Templates
dotnet new ef-templates

Bu komut projeye CodeTemplates/EFCore/DbContext.t4 ve CodeTemplates/EFCore/EntityType.t4 dosyalarını ekler. İlki context sınıfını, ikincisi her tablo ve view için entity sınıfını üretir. Uzantının .tt değil .t4 olması bilinçlidir: Visual Studio'nun şablonları kendisi dönüştürmesini önler, dönüştürmeyi EF Core yapar. Projedeki şablonlar scaffold komutları tarafından otomatik kullanılır.

Belgelerdeki örnek değişiklik, koleksiyon navigation'larında varsayılan List<T> yerine WPF, WinUI veya .NET MAUI gibi XAML tabanlı arayüzlerde veri bağlama için uygun olan ObservableCollection<T> kullanmaktır. EntityType.t4 içinde List üreten satırı değiştirir, üst kısımdaki using listesine System.Collections.ObjectModel eklersiniz; daha önce scaffold ettiyseniz --force ile yeniden çalıştırırsınız. Büyük modellerde OnModelCreating çok büyüyorsa üçüncü bir şablon olan EntityTypeConfiguration.t4 ile her entity için ayrı bir IEntityTypeConfiguration<T> sınıfı üretebilirsiniz. Basit join tabloları için entity üretilmesini istiyorsanız EntityType.t4 içindeki IsSimpleManyToManyJoinEntityType() koşulunu devre dışı bırakırsınız.

Scaffold nasıl çalışır ve sınırları nelerdir?

Tersine mühendislik önce veritabanı şemasını okur: tablolar, kolonlar, kısıtlar ve index'ler. Sonra bu bilgiden bir EF Core modeli kurar; tablolar entity tiplerine, kolonlar property'lere, yabancı anahtarlar ilişkilere dönüşür. Son olarak model koda dönüştürülür. Bu akışın doğal sınırları belgelerde şöyle sıralanır:

  • Modelin her şeyi veritabanı şemasında temsil edilmez. Kalıtım hiyerarşileri (TPH, TPT, TPC), owned tipler ve table splitting bilgisi şemada bulunmadığı için asla scaffold edilmez.
  • Bazı kolon tipleri EF Core sağlayıcısı tarafından desteklenmeyebilir; bu kolonlar modele alınmaz.
  • Eşzamanlılık belirteçlerinden yalnızca veritabanında özel bir tiple temsil edilenler (SQL Server'daki rowversion gibi) tersine mühendislikle çıkarılabilir; diğerleri scaffold edilmez. Konu için concurrency yazısına bakın.
  • Azure Cosmos DB sağlayıcısında scaffold desteklenmez, çünkü belgelere göre belgeler için tanımlı bir şema yoktur.

Sık yapılan hatalar

  • Bağlantı dizesini parolasıyla birlikte koda scaffold edip kaynak kontrolüne göndermek.
  • Üretilen dosyaları doğrudan değiştirip sonra --force ile yeniden scaffold ederek değişiklikleri kaybetmek.
  • Birincil anahtarı olmayan bir tablonun neden entity'ye dönüşmediğini aramak.
  • Bağlantı dizesini kabuğun kaçış kurallarını hesaba katmadan yazmak (PowerShell'de $ gibi).
  • Eski bir şablon paketinden eklenmiş T4 şablonlarını hiç güncellememek.

Sık Sorulan Sorular

Scaffold'dan sonra migration kullanabilir miyim?

Evet. Belgelerdeki “bir kez scaffold” yaklaşımında seçeneklerden biri, entity tiplerini doğruluğun kaynağı yapıp şemayı migration'larla yönetmeye geçmektir. Bu noktadan sonra şemayı veritabanında elle değiştirmek yerine model değişikliği yapıp migration eklersiniz.

Yeniden scaffold yaptığımda değişikliklerim kaybolur mu?

--force ile yeniden scaffold ettiğinizde üretilen dosyaların üzerine yazılır. Değişikliklerinizi ayrı partial sınıf dosyalarında ve OnModelCreatingPartial içinde tutarsanız korunurlar.

Yalnızca birkaç tabloyu nasıl alırım?

CLI'da --table seçeneğini her tablo için tekrarlayın; şema adıyla birlikte schema.table biçimini kullanabilirsiniz. Bütün bir şemayı almak için --schema kullanın.

Scaffold hangi sağlayıcılarla çalışır?

Belgelere göre okuyacağınız veritabanını hedefleyen sağlayıcının paketini projeye eklersiniz ve ikinci argüman olarak genellikle bu paketin adını verirsiniz. Cosmos DB sağlayıcısı scaffold'u desteklemez. Üçüncü taraf sağlayıcıların ayrıntıları kendi belgelerindedir.

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 · EF Core migration rehberi · dotnet ef komutları · Connection string yönetimi · Model yapılandırma · Many-to-many ilişki