ORTA
Seed Data (Başlangıç Verisi)
Veritabanı ilk oluştuğunda veya test ortamında ihtiyaç duyulan başlangıç verilerini (kategoriler, roller, ülke listesi gibi) otomatik eklemek için kullanılır.
Veritabanı sağlayıcısı
Bu sayfadaki eşleşen örnekleri seçilen sağlayıcıya göre gösterir.
HasData — Migration-Based Seed
Kritik: HasData runtime'da çalışmaz. Migration dosyasına SQL olarak yazılır ve migration sadece bir kez uygulanır.
- Kayıt zaten varsa → tekrar eklenmez (migration bir daha çalışmaz)
- Veriyi silersen → geri gelmez (migration tekrar uygulanmaz)
- HasData'daki değeri değiştirirsen → yeni migration oluşturduğunda
UPDATEüretilir🚨 HasData'da ASLA dinamik değer kullanma:
DateTime.UtcNow,DateTimeOffset.Now→ Migration derleme anındaki sabit değer yazılır (hermigrations add'da farklı SQL üretir!)Guid.NewGuid()→ Her migration'da yeni GUID = gereksiz UPDATE SQL'leri- Doğrusu:
new DateTime(2024, 1, 1)veyanew Guid("sabit-guid-string")gibi deterministik değerler kullan
public class CategoryConfiguration : IEntityTypeConfiguration<Category>
{
public void Configure(EntityTypeBuilder<Category> builder)
{
builder.ToTable("Categories");
builder.HasKey(c => c.Id);
builder.Property(c => c.Name).IsRequired().HasMaxLength(100);
// Seed data — migration'a dahil edilir
builder.HasData(
new Category { Id = 1, Name = "Elektronik" },
new Category { Id = 2, Name = "Giyim" },
new Category { Id = 3, Name = "Kitap" },
new Category { Id = 4, Name = "Ev & Yaşam" }
);
}
}
Migration'da oluşan SQL:
-- Up metodu
INSERT INTO [Categories] ([Id], [Name]) VALUES (1, N'Elektronik');
INSERT INTO [Categories] ([Id], [Name]) VALUES (2, N'Giyim');
INSERT INTO [Categories] ([Id], [Name]) VALUES (3, N'Kitap');
INSERT INTO [Categories] ([Id], [Name]) VALUES (4, N'Ev & Yaşam');
-- IDENTITY_INSERT otomatik açılır/kapanır (Id belirttiğimiz için)
SET IDENTITY_INSERT [Categories] ON;
-- ... insert'ler ...
SET IDENTITY_INSERT [Categories] OFF;
-- Up metodu
INSERT INTO categories (id, name) OVERRIDING SYSTEM VALUE VALUES (1, 'Elektronik');
INSERT INTO categories (id, name) OVERRIDING SYSTEM VALUE VALUES (2, 'Giyim');
INSERT INTO categories (id, name) OVERRIDING SYSTEM VALUE VALUES (3, 'Kitap');
INSERT INTO categories (id, name) OVERRIDING SYSTEM VALUE VALUES (4, 'Ev & Yaşam');
-- GENERATED ALWAYS sütununa manuel değer yazmak için OVERRIDING SYSTEM VALUE gerekir
-- Sequence değerini güncellemek için EF otomatik ekler:
SELECT setval(pg_get_serial_sequence('categories', 'id'), MAX(id)) FROM categories;
Kurallar:
HasDataile PK değerini zorunlu olarak belirtmek gerekir (Id = 1, 2, ...)- Navigation property kullanılamaz — FK değerini doğrudan yaz
- Owned entity seed'lemek için anonim tip kullan
// İlişkili veri seed'leme — FK ile
builder.HasData(
new Product { Id = 1, Name = "Laptop", CategoryId = 1 },
new Product { Id = 2, Name = "T-shirt", CategoryId = 2 }
);
// Owned entity seed — anonim tip
builder.OwnsOne(c => c.Address).HasData(
new { CustomerId = 1, Street = "Atatürk Cad.", City = "İstanbul" }
);
UseSeeding — Koşullu Seed (EF Core 9+)
| Özellik | HasData | UseSeeding (EF Core 9+) |
|---|---|---|
| Migration gerektir | Evet | Hayır |
| Koşullu seed ("yoksa ekle") | ||
| Async desteği | ||
| Navigation property | FK ile manuel | Normal entity gibi |
| Ne zaman çalışır | Migration uygulanırken (bir kez) | MigrateAsync() / EnsureCreated() her çağrısında |
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
options.UseSqlServer(connectionString)
.UseAsyncSeeding(async (context, _, ct) =>
{
if (!await context.Set<Category>().AnyAsync(ct))
{
context.Set<Category>().AddRange(
new Category { Name = "Elektronik", Slug = "elektronik" },
new Category { Name = "Giyim", Slug = "giyim" },
new Category { Name = "Kitap", Slug = "kitap" }
);
await context.SaveChangesAsync(ct);
}
}));
// Çalıştırma — MigrateAsync içinde otomatik tetiklenir
await context.Database.MigrateAsync();
Hangisini ne zaman kullan:
- Sabit lookup tabloları (ülkeler, roller, para birimleri) →
HasData(deterministik, migration'da)- Koşullu / dinamik seed (admin kullanıcı yoksa oluştur) →
UseSeeding
PostgreSQL Seed Farkları:
HasDataher iki provider'da aynı Fluent API ile çalışır (SQL farkını EF halleder)- PostgreSQL'de
HasDataüretilen SQL:INSERT INTO ... ON CONFLICT DO NOTHINGdeğil,INSERT INTOdüzdür (migration'da idempotent değil!)
HasDefaultValueSql provider farkları:
builder.Property(p => p.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
builder.Property(p => p.Id).HasDefaultValueSql("NEWSEQUENTIALID()");
builder.Property(p => p.CreatedAt).HasDefaultValueSql("NOW() AT TIME ZONE 'UTC'");
builder.Property(p => p.Id).HasDefaultValueSql("gen_random_uuid()");
Upsert (varsa güncelle, yoksa ekle) — Raw SQL karşılaştırması:
// SQL Server: MERGE statement
await context.Database.ExecuteSqlRawAsync(@"
MERGE INTO [Categories] AS target
USING (VALUES (1, N'Elektronik', 'elektronik'), (2, N'Giyim', 'giyim'))
AS source (Id, Name, Slug)
ON target.Id = source.Id
WHEN MATCHED THEN UPDATE SET Name = source.Name, Slug = source.Slug
WHEN NOT MATCHED THEN INSERT (Id, Name, Slug) VALUES (source.Id, source.Name, source.Slug);
");
// PostgreSQL: ON CONFLICT (çok daha temiz syntax)
await context.Database.ExecuteSqlRawAsync(@"
INSERT INTO categories (id, name, slug)
VALUES (1, 'Elektronik', 'elektronik'),
(2, 'Giyim', 'giyim')
ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, slug = EXCLUDED.slug;
");