EF Core'da cascade delete, bir ana (principal) varlık silindiğinde ya da bağımlı varlıkla ilişkisi koparıldığında bağımlı (dependent) varlıkların da silinmesidir. Entity Framework Core'da zorunlu ilişkiler (yabancı anahtarı null olamayan) varsayılan olarak cascade delete kullanır; isteğe bağlı ilişkilerde ise varsayılan olarak bağımlıların yabancı anahtarı null yapılır. Davranışı ilişki bazında OnDelete(DeleteBehavior.X) ile değiştirirsiniz ve seçtiğiniz değer hem EF Core'un izlenen varlıklara ne yapacağını hem de veritabanındaki yabancı anahtar kısıtının ON DELETE kuralını belirler.
Bu yazı belgelerdeki cascade delete sayfasını izleyerek silme davranışının ne zaman ve nerede (EF Core'da mı, veritabanında mı) çalıştığını, SQL Server'ın döngü kısıtını, DeleteBehavior değerlerinin şemaya ve SaveChanges'e etkisini ve zamanlamanın nasıl kontrol edileceğini anlatır. İlişkilerin kendisi ve zorunlu/isteğe bağlı ayrımı EF Core ilişkiler, navigation ve foreign key ile one-to-many ilişki yazılarında, gündelik silme kodu SaveChanges yazısında anlatılıyor.
Sorun: sahipsiz kalan yabancı anahtar
EF Core ilişkileri yabancı anahtarlarla temsil eder. Yabancı anahtarı taşıyan varlık bağımlı ya da çocuk (child) varlıktır ve bu anahtarın değeri ana varlığın birincil ya da alternatif anahtarıyla eşleşmelidir. Ana varlık silinirse bağımlıların yabancı anahtarları artık hiçbir ana varlıkla eşleşmez. Bu geçersiz bir durumdur ve çoğu veritabanında referans bütünlüğü (referential constraint) ihlaline yol açar.
Belgelere göre bunu önlemenin iki yolu vardır. Birincisi bağımlıların yabancı anahtarını null yapmaktır; bu yalnızca yabancı anahtar özelliğinin ve eşlendiği kolonun null olabildiği isteğe bağlı ilişkilerde geçerlidir. İkincisi bağımlıları da silmektir; bu her tür ilişkide geçerlidir ve cascade delete olarak adlandırılır.
Cascade ne zaman devreye girer?

Ana varlığı silmek
Belgelerdeki modelde Blog ana, Post bağımlı varlıktır ve Post.BlogId null olamayan bir tamsayıdır. Bu yüzden ilişki konvansiyonla zorunlu olarak yapılandırılır ve zorunlu ilişkiler varsayılan olarak cascade delete kullanır. Bir blogu yazılarıyla birlikte yükleyip sildiğinizde yazıların hepsi de silinir.
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; } // null olamaz: zorunlu ilişki
public Blog Blog { get; set; }
}using var context = new BlogsContext();
var blog = await context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).FirstAsync();
context.Remove(blog);
await context.SaveChangesAsync();Belgelerde SQL Server ile gösterilen sonuçta EF Core önce iki yazı için ayrı DELETE komutları, ardından blog için bir DELETE gönderir. Bağımlılar yüklendiği ve izlendiği için silme işini EF Core kendisi yapar.
-- Belgelerdeki örnek çıktı (SQL Server, SET NOCOUNT ve SELECT @@ROWCOUNT satırları çıkarıldı)
DELETE FROM [Posts]
WHERE [Id] = @p0;
DELETE FROM [Posts]
WHERE [Id] = @p0;
DELETE FROM [Blogs]
WHERE [Id] = @p1;İlişkiyi koparmak: yetim kayıtları silmek
Blogu silmek yerine her yazının ilişkisini koparabilirsiniz: post.Blog = null atayarak ya da blog.Posts.Clear() ile yazıları koleksiyondan çıkararak. İki durumda da blog silinmez ama artık hiçbir bloga bağlı olmayan yazılar silinir. Hiçbir ana varlığa bağlı olmayan bağımlıları silmeye yetim silme (deleting orphans) denir.
var blog = await context.Blogs.OrderBy(e => e.Name).Include(e => e.Posts).FirstAsync();
blog.Posts.Clear(); // ya da her yazı için post.Blog = null;
await context.SaveChangesAsync(); // yazılar silinir, blog kalırBelgeler iki kavramın yakın akraba olduğunu vurgular: ikisinde de zorunlu ana varlıkla ilişkisi kopan bağımlılar silinir. Cascade delete'te kopma, ana varlığın kendisi silindiği için olur. Yetim silmede ana varlık hâlâ vardır ama bağımlılar artık ona bağlı değildir.
Cascade nerede çalışır: EF Core mu, veritabanı mı?

Cascade davranışları iki yerde uygulanabilir: geçerli DbContext tarafından izlenen varlıklarda ve context'e yüklenmemiş, yalnızca veritabanında duran satırlarda.
İzlenen varlıklarda
EF Core yapılandırılmış cascade davranışlarını izlenen varlıklara her zaman uygular. Yani uygulama ilgili tüm bağımlıları context'e yüklemişse, yukarıdaki örneklerde olduğu gibi, veritabanı nasıl yapılandırılmış olursa olsun cascade doğru uygulanır.
Veritabanında
Birçok veritabanı da bir satır silindiğinde tetiklenen cascade davranışları sunar. EF Core, veritabanı EnsureCreated veya migration'larla oluşturulduğunda bu davranışları modeldeki cascade ayarına göre yapılandırır. Belgelerdeki modelle SQL Server'da oluşturulan Posts tablosunun yabancı anahtar kısıtı ON DELETE CASCADE içerir:
-- Belgelerdeki örnek çıktı (SQL Server)
CREATE TABLE [Posts] (
[Id] int NOT NULL IDENTITY,
[Title] nvarchar(max) NULL,
[Content] nvarchar(max) 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
);Veritabanının böyle yapılandırıldığını biliyorsanız blogu yazılarını yüklemeden silebilirsiniz. Include olmadığı için EF Core yalnızca blog için bir DELETE gönderir ve yazıları veritabanı siler. Kısıt cascade için yapılandırılmamış olsaydı aynı kod bir istisnayla sonuçlanırdı.
Belgeler burada üç önemli not düşer. Veritabanlarının yetim kayıtları otomatik silmenin genellikle bir yolu yoktur, çünkü veritabanında navigation yoktur, yalnızca yabancı anahtar vardır; bu yüzden bir ilişkiyi koparmak için genellikle iki tarafı da context'e yüklemek gerekir. EF Core in-memory veritabanı şu anda veritabanı tarafında cascade delete desteklemez; testlerde bu farkı hesaba katın (EF Core test yazma). Ve en önemlisi:
Aynı mantık ExecuteDelete için de önemlidir: bu metot change tracker'ı kullanmadığından EF Core'un izlenen varlıklara uyguladığı cascade burada devreye girmez ve bağımlı satırlara ne olacağını yalnızca veritabanındaki kısıt belirler. Ayrıntılar ExecuteUpdate ve ExecuteDelete yazısında.
SQL Server'da döngü ve çoklu cascade yolu sorunu
Bazı veritabanları, özellikle SQL Server, döngü oluşturan cascade davranışlarına sınır koyar. Belgelerdeki modelde üç zorunlu ilişki vardır: blog silinince yazıları, yazar (Person) silinince yazdığı yazılar, sahip silinince sahip olduğu blog silinir. Mantıklı görünse de bu cascade'lerle SQL Server veritabanı oluşturmaya çalışmak şu hatayı verir:
-- Belgelerdeki hata mesajı
Microsoft.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Posts_Person_AuthorId'
on table 'Posts' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION,
or modify other FOREIGN KEY constraints.Belgeler iki çözüm sunar. Birincisi ilişkilerden birini veya birkaçını cascade delete kullanmayacak hâle getirmektir; örneğin yazı ile blog arasındaki ilişkiyi public int? BlogId { get; set; } ile isteğe bağlı yapmak. İsteğe bağlı ilişkide yazı blogsuz var olabilir, cascade varsayılan olarak yapılandırılmaz ve döngü ortadan kalkar.
İkinci çözüm ilişkiyi zorunlu ve cascade olarak bırakıp bu ayarın yalnızca izlenen varlıklara uygulanmasını, veritabanına yansımamasını sağlamaktır. Bunun için DeleteBehavior.ClientCascade kullanılır:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Blog>()
.HasOne(e => e.Owner)
.WithOne(e => e.OwnedBlog)
.OnDelete(DeleteBehavior.ClientCascade);
}Bu yapılandırmada bir kişi ile sahip olduğu blog birlikte yüklenip kişi silinirse EF Core cascade'i uygular ve önce blogu, sonra kişiyi siler. Ancak blog yüklenmeden kişi silinirse veritabanında cascade tanımlı olmadığı için yabancı anahtar ihlali oluşur. Belgelerdeki hata mesajı, DELETE komutunun FK_Blogs_People_OwnerId referans kısıtıyla çakıştığını söyler. Yani ClientCascade seçtiyseniz bağımlıları yüklemek sizin sorumluluğunuzdadır.
Cascading null: isteğe bağlı ilişkiler
İsteğe bağlı ilişkilerde yabancı anahtar null olabilen bir kolona eşlenir. Bu yüzden ana varlık silindiğinde ya da ilişki koparıldığında yabancı anahtar null yapılabilir. Post.BlogId özelliğini int? yapıp aynı silme kodunu çalıştırdığınızda, belgelerdeki örnekte EF Core önce her yazı için UPDATE [Posts] SET [BlogId] = @p0 (değer NULL) gönderir, ardından blogu siler. İlişkiyi post.Blog = null ya da blog.Posts.Clear() ile koparırsanız yalnızca bu UPDATE komutları gider, hiçbir yazı silinmez.
Belgeler, ilişkilerin bu şekilde düzeltilmesinin Entity Framework'ün 2008'deki ilk sürümünden beri varsayılan davranış olduğunu, EF Core öncesinde bir adı olmadığını ve değiştirilemediğini, artık ClientSetNull olarak bilindiğini anlatır. Veritabanları da isteğe bağlı ilişkide ana varlık silinince null'a çekmek için yapılandırılabilir, ancak bu veritabanında cascade delete kullanmaktan çok daha az yaygındır. Belgeler, SQL Server'da veritabanı tarafında cascade delete ile cascade null'u aynı anda kullanmanın neredeyse her zaman ilişki döngülerine yol açacağı konusunda uyarır.
OnDelete ve DeleteBehavior değerleri

Cascade davranışı ilişki başına OnModelCreating içinde OnDelete ile yapılandırılır. OnDelete, belgelerin de kabul ettiği gibi kafa karıştırıcı olabilen DeleteBehavior enum'undan bir değer alır. Bu enum iki şeyi birden belirler: EF Core'un izlenen varlıklara ne yapacağını ve şema EF ile oluşturulduğunda veritabanındaki cascade yapılandırmasını.
Veritabanı şemasına etkisi
| DeleteBehavior | Migration veya EnsureCreated ile oluşan kısıt |
|---|---|
| Cascade | ON DELETE CASCADE |
| Restrict | ON DELETE RESTRICT |
| NoAction | Veritabanı varsayılanı |
| SetNull | ON DELETE SET NULL |
| ClientSetNull | Veritabanı varsayılanı |
| ClientCascade | Veritabanı varsayılanı |
| ClientNoAction | Veritabanı varsayılanı |
Veritabanında cascade davranışına yol açan değerler yalnızca Cascade ve SetNull'dur; diğer tüm değerler veritabanını hiçbir değişikliği yaymayacak şekilde yapılandırır. Belgeler, ilişkisel veritabanlarında ON DELETE NO ACTION (veritabanı varsayılanı) ile ON DELETE RESTRICT davranışlarının genellikle aynı ya da çok benzer olduğunu, adına rağmen NO ACTION'ın da referans kısıtlarını uyguladığını açıklar; fark varsa, kısıtın ne zaman denetlendiğidir. SQL Server ON DELETE RESTRICT desteklemediği için orada ON DELETE NO ACTION kullanılır.
SaveChanges davranışına etkisi
Belgeler bağımlıların ne olacağını dört tabloyla verir: ilişki zorunlu ya da isteğe bağlı, bağımlılar yüklenmiş ya da yalnızca veritabanında. Aşağıdaki tablo bu dört tablonun ana varlık silindiğindeki sütununu birleştirir:
| DeleteBehavior | Zorunlu, bağımlılar yüklü | Zorunlu, bağımlılar yüklü değil | İsteğe bağlı, yüklü | İsteğe bağlı, yüklü değil |
|---|---|---|---|---|
| Cascade | EF Core siler | Veritabanı siler | EF Core siler | Veritabanı siler |
| Restrict | InvalidOperationException | DbUpdateException | EF Core FK değerini null yapar | DbUpdateException |
| NoAction | InvalidOperationException | DbUpdateException | EF Core FK değerini null yapar | DbUpdateException |
| SetNull | Veritabanı oluşturulurken SqlException | Veritabanı oluşturulurken SqlException | EF Core FK değerini null yapar | Veritabanı FK değerini null yapar |
| ClientSetNull | InvalidOperationException | DbUpdateException | EF Core FK değerini null yapar | DbUpdateException |
| ClientCascade | EF Core siler | DbUpdateException | EF Core siler | DbUpdateException |
| ClientNoAction | DbUpdateException | DbUpdateException | DbUpdateException | DbUpdateException |
Tabloyu okurken belgelerin notları yol gösterir. Zorunlu ilişkiler için varsayılan Cascade, isteğe bağlı ilişkiler için varsayılan ClientSetNull'dur. Zorunlu ilişkide cascade dışında bir değer kullanmak SaveChanges sırasında istisnaya yol açar: bağımlılar yüklüyse EF Core geçersiz durumu kendisi fark eder ve genellikle InvalidOperationException fırlatır; yüklü değilse durumu ancak veritabanı fark eder ve SaveChanges veritabanı hatasını DbUpdateException içine sarar. ClientNoAction, EF Core'a bağımlıların yabancı anahtarlarına dokunmamasını söyler, bu yüzden hatayı veritabanı verir. SetNull zorunlu ilişkide yabancı anahtar kolonu null olamadığı için veritabanı oluşturulurken reddedilir.
İlişkiyi koparma (ana varlık silinmeden) sütunu çoğunlukla aynıdır; farklar şöyledir: zorunlu ilişkide yüklü bağımlılarla ClientNoAction koparmada InvalidOperationException verir, isteğe bağlı ilişkide ise ClientNoAction koparmada yabancı anahtarı null yapar. Bağımlılar yüklü değilse koparma zaten mümkün değildir. İsteğe bağlı ilişkide bağımlılar yalnızca Cascade veya ClientCascade yapılandırılmışsa silinir; veritabanı cascade delete ya da cascade null için yapılandırılmamışsa veritabanı hatasından kaçınmak için bağımlıları yüklemeniz gerekir.
Zamanlamayı kontrol etmek: CascadeDeleteTiming ve DeleteOrphansTiming
Varsayılan olarak cascade delete, ana varlık Deleted olarak işaretlendiği anda izlenen bağımlılara uygulanır; yetimlerin silinmek üzere işaretlenmesi de ilişki değişikliği tespit edildiği anda olur. Bu, ChangeTracker.CascadeDeleteTiming ve ChangeTracker.DeleteOrphansTiming ile SaveChanges anına ertelenebilir ya da tamamen kapatılabilir. Erteleme özellikle bir bağımlıyı eski ana varlıktan çıkarıp SaveChanges öncesinde yeni bir ana varlığa bağlayacaksanız işe yarar:
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post); // hemen Deleted olmaz
dotNetBlog.Posts.Add(post); // yeni bloga bağlandı, artık yetim değil
await context.SaveChangesAsync(); // silme değil güncelleme giderBelgeler, ilk koleksiyondan çıkarıldıktan sonra yazının Deleted olmadığını, ilişki zorunlu olsa bile EF Core'un ilişkinin koptuğunu izlediğini anlatır; yabancı anahtar tipi null olamasa da EF Core onu null kabul eder ve buna conceptual null denir. Yazı kayıttan önce yeni bloga bağlandığı için yetim sayılmaz ve güncellenir.
Zamanlamayı CascadeTiming.Never yaparsanız otomatik yetim silme kapanır ve bir yetim izlenirken SaveChanges çağrılırsa istisna alırsınız. Belgelerdeki mesaj ilişkinin koptuğunu, ilişkinin zorunlu ya da yabancı anahtar null olamadığı için örtük olarak zorunlu olduğunu ve bağımlının silinmesi isteniyorsa ilişkinin cascade delete kullanacak şekilde yapılandırılmasını söyler. ChangeTracker.CascadeChanges() ise cascade ve yetim silmeyi istediğiniz anda zorlar. Belgelerin önerisiyle, zamanlamayı Never yapıp gerektiğinde CascadeChanges() çağırmak, EF Core'a açıkça söylemediğiniz sürece hiçbir silmenin olmamasını garanti eder.
Sık yapılan hatalar
- Bağımlıları yüklemeden ClientCascade veya ClientSetNull ile silmek: veritabanında kural olmadığı için
DbUpdateExceptionalırsınız. - SQL Server'da çoklu cascade yolunu fark etmemek: migration uygulanırken döngü hatası çıkar; bir ilişkiyi isteğe bağlı yapın ya da
ClientCascadekullanın. - Soft delete ile veritabanı cascade'ini birlikte kullanmak: kayıtlar gerçekten silinebilir.
- Koleksiyondan çıkarmanın silme anlamına geldiğini bilmemek: zorunlu ilişkide
RemoveveyaClearbağımlıyı siler; yalnızca bağı koparmak istiyorsanız ilişkiyi isteğe bağlı yapın. - Testleri InMemory sağlayıcıyla yapıp veritabanı cascade'ine güvenmek: InMemory veritabanı tarafında cascade desteklemez.
- Silmeden önce yedek almamak: cascade tek bir komutla çok sayıda satırı silebilir; üretimde şema değişikliği ve toplu silme öncesi yedek stratejisi için veritabanı yedekleme stratejileri yazısına bakın.
Sık Sorulan Sorular
EF Core varsayılan olarak cascade delete kullanır mı?
Zorunlu ilişkilerde (null olamayan yabancı anahtar) evet, varsayılan Cascade'dir. İsteğe bağlı ilişkilerde varsayılan ClientSetNull'dur; bağımlılar silinmez, yüklüyse yabancı anahtarları null yapılır.
Cascade delete'i tamamen nasıl kapatırım?
İlişki başına OnDelete ile cascade olmayan bir değer seçersiniz, örneğin Restrict veya NoAction. Bu durumda bağımlısı olan bir ana varlığı silmek istisna verir; bağımlıları önce siz silmeli ya da başka bir ana varlığa bağlamalısınız.
Restrict ile NoAction arasında fark var mı?
EF Core tarafında belgelerdeki tablolar aynı sonucu gösterir. Veritabanı tarafında Restrict ON DELETE RESTRICT, NoAction veritabanı varsayılanını üretir; bunlar genellikle aynı ya da çok benzerdir ve SQL Server'da ikisi de NO ACTION olur.
Ana varlığı bağımlıları yüklemeden silebilir miyim?
Yalnızca veritabanındaki kısıt ON DELETE CASCADE (ya da isteğe bağlı ilişkide ON DELETE SET NULL) ile yapılandırılmışsa. Bu, Cascade veya SetNull seçip şemayı migration ile oluşturduğunuzda olur.
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 · SaveChanges · One-to-many ilişki · One-to-one ilişki · EF Core migration rehberi · Soft delete