ExecuteUpdate ve ExecuteDelete

ExecuteUpdate ve ExecuteDelete EF'nin geleneksel değişiklik izleme ve SaveChanges() yöntemini kullanmadan verileri veritabanına kaydetmenin bir yoludur. Bu iki tekniğin giriş niteliğindeki karşılaştırması için veri kaydetmeye ilişkin Genel Bakış sayfasına bakın.

Silme İşlemini Gerçekleştir

Derecelendirmesi belirli bir eşiğin altında olan tüm Blogları silmeniz gerektiğini varsayalım. Geleneksel SaveChanges() yaklaşım için aşağıdakileri yapmanız gerekir:

await foreach (var blog in context.Blogs.Where(b => b.Rating < 3).AsAsyncEnumerable())
{
    context.Blogs.Remove(blog);
}

await context.SaveChangesAsync();

Bu, bu görevi gerçekleştirmenin oldukça verimsiz bir yoludur: Filtremizle eşleşen tüm Bloglar için veritabanını sorgular ve ardından tüm bu örnekleri sorgular, gerçekleştirir ve izleriz; eşleşen varlıkların sayısı çok büyük olabilir. Ardından EF'in değişiklik izleyicisine her Blog'un kaldırılması gerektiğini söyler ve çağrısı SaveChanges()yaparak bu değişiklikleri uygularız. Bu da her biri için bir DELETE deyim oluşturur.

API aracılığıyla gerçekleştirilen aynı görev aşağıda verilmiştir ExecuteDelete :

await context.Blogs.Where(b => b.Rating < 3).ExecuteDeleteAsync();

Bu, hangi Blogların etkilenmesi gerektiğini - tıpkı onları sorguluyormuş gibi - belirlemek için tanıdık LINQ işleçlerini kullanır ve ardından EF'e veritabanına karşı bir SQL DELETE komutunu çalıştırmasını belirtir.

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Bu, daha basit ve kısa olmasının yanı sıra, veritabanından veri yüklemeden veya EF'nin değişiklik izleyicisini içermeden veritabanında çok verimli bir şekilde yürütülür. Silmek istediğiniz Blogları seçmek için rastgele LINQ işleçleri kullanabileceğinizi unutmayın. Bunlar, aynı bu Blogları sorguladığınız gibi veritabanında yürütülmek üzere SQL'e çevrilir.

Güncellemeyi Uygula

Bu Blogları silmek yerine, bir özelliği değiştirip bunların gizlenmesi gerektiğini belirtmek istersek ne olur? ExecuteUpdate bir SQL UPDATE deyimini ifade etmek için benzer bir yol sağlar:

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.IsVisible, false));

ile ExecuteDeleteolduğu gibi, hangi Blogların etkilenmesi gerektiğini belirlemek için ilk olarak LINQ kullanırız, ancak bununla birlikte ExecuteUpdate eşleşen Bloglara uygulanacak değişikliği de ifade etmemiz gerekir. Bu, ExecuteUpdate çağrısı içinde SetProperty çağrısı yapılarak gerçekleştirilir ve iki bağımsız değişken sağlanır: değiştirilecek özellik (IsVisible) ve yeni değeri (false). Bu, aşağıdaki SQL'in yürütülmesine neden olur:

UPDATE [b]
SET [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Birden çok özelliği güncelleştirme

ExecuteUpdate tek bir çağrıda birden çok özelliğin güncelleştirilmesini sağlar. Örneğin, hem IsVisible 'ı false olarak ayarlamak hem de Rating 'i sıfıra ayarlamak için ek SetProperty çağrılarını birbirine zincirlemeniz yeterlidir.

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters
        .SetProperty(b => b.IsVisible, false)
        .SetProperty(b => b.Rating, 0));

Bu işlem aşağıdaki SQL'i yürütür:

UPDATE [b]
SET [b].[Rating] = 0,
    [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Mevcut özellik değerine başvurma

Yukarıdaki örnekler özelliği yeni bir sabit değere güncelleştirdi. ExecuteUpdate ayrıca yeni değeri hesaplarken var olan özellik değerine başvurmaya izin verir; örneğin, eşleşen tüm Blogların derecelendirmesini bir artırmak için aşağıdakileri kullanın:

await context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

için ikinci bağımsız değişkenin SetProperty artık bir lambda işlevi olduğunu ve önceki gibi sabit olmadığını unutmayın. Parametre b güncellenen Blog'u temsil eder; bu lambda içinde, b.Rating ise herhangi bir değişiklik olmadan önceki derecelendirmeyi içerir. Bu işlem aşağıdaki SQL'i yürütür:

UPDATE [b]
SET [b].[Rating] = [b].[Rating] + 1
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

ExecuteUpdate şu anda SetProperty lambda içindeki gezintilere başvurmayı desteklememektedir. Örneğin, tüm Blogların derecelendirmelerini güncelleştirmek istediğimizi ve böylece her Blogun yeni derecelendirmesinin tüm Gönderilerinin derecelendirmelerinin ortalaması olduğunu varsayalım. Şu şekilde kullanmayı ExecuteUpdate deneyebiliriz:

await context.Blogs.ExecuteUpdateAsync(
    setters => setters.SetProperty(b => b.Rating, b => b.Posts.Average(p => p.Rating)));

Ancak EF, önce ortalama derecelendirmeyi hesaplamak ve bunu anonim bir türe yansıtmak için Select kullanarak, ardından bunun üzerinde ExecuteUpdate kullanarak bu işlemi gerçekleştirmeye izin verir.

await context.Blogs
    .Select(b => new { Blog = b, NewRating = b.Posts.Average(p => p.Rating) })
    .ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Blog.Rating, b => b.NewRating));

Bu işlem aşağıdaki SQL'i yürütür:

UPDATE [b]
SET [b].[Rating] = CAST((
    SELECT AVG(CAST([p].[Rating] AS float))
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) AS int)
FROM [Blogs] AS [b]

Değişiklik izleme

c0 ile aşina olan kullanıcılar, birden çok değişiklik yapmaya alışkındır ve ardından bu değişikliklerin tümünü veritabanına uygulamak için SaveChanges çağırırlar; bu, EF'nin bu değişiklikleri biriken veya izleyen değişiklik izleyici tarafından mümkün hale getirilir.

ExecuteUpdate ve ExecuteDelete oldukça farklı çalışırlar: çağrıldıkları noktada hemen etkili olurlar. Bu, tek başına bir ExecuteUpdate veya ExecuteDelete işlemi birçok satırı etkileyebilse de, birden fazla bu tür işlemi biriktirip hepsini bir arada uygulamak mümkün değildir; örneğin SaveChanges çağrılırken. Aslında, işlevler EF'in değişiklik izleyicisinin tamamen farkında değildir ve onunla herhangi bir etkileşime sahip değildir. Bunun birkaç önemli sonucu vardır.

Aşağıdaki kodu inceleyin:

// 1. Query the blog with the name `SomeBlog`. Since EF queries are tracking by default, the Blog is now tracked by EF's change tracker.
var blog = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");

// 2. Increase the rating of all blogs in the database by one. This executes immediately.
await context.Blogs.ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

// 3. Increase the rating of `SomeBlog` by two. This modifies the .NET `Rating` property and is not yet persisted to the database.
blog.Rating += 2;

// 4. Persist tracked changes to the database.
await context.SaveChangesAsync();

Kritik bir şekilde, ExecuteUpdate çağrıldığında ve veritabanındaki tüm Bloglar güncellendiğinde, EF'nin değişiklik izleyicisi güncelleştirilmez ve izlenen .NET örneği, sorgulandığı andaki özgün derecelendirme değerini hala korur. Blog'un derecelendirmesinin başlangıçta 5 olduğunu varsayalım; 3. satır yürütüldikten sonra veritabanındaki derecelendirme 6 olur (nedeniyle ExecuteUpdate), ancak izlenen .NET örneğindeki derecelendirme 7 olur. Çağrıldığında SaveChanges EF, 7 yeni değerinin özgün değer 5'ten farklı olduğunu algılar ve bu değişikliği devam ettirer. ExecuteUpdate tarafından gerçekleştirilen değişiklik üzerine yazılır ve dikkate alınmaz.

Sonuç olarak, hem izlenen SaveChanges değişiklikleri hem de izlenmeyen değişiklikleri ExecuteUpdate/ExecuteDelete aracılığıyla karıştırmaktan kaçınmak genellikle iyi bir fikirdir.

İşlemler

Yukarıdakilere devam ederek anlamamız gereken önemli bir nokta, ExecuteUpdate ve ExecuteDelete çağrıldıklarında örtük olarak bir işlem başlatmadıklarıdır. Aşağıdaki kodu inceleyin:

await context.Blogs.ExecuteUpdateAsync(/* some update */);
await context.Blogs.ExecuteUpdateAsync(/* another update */);

var blog = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");
blog.Rating += 2;
await context.SaveChangesAsync();

Her ExecuteUpdate çağrı veritabanına tek bir SQL UPDATE gönderilmesine neden olur. Hiçbir işlem oluşturulmadığı için, herhangi bir hata türü ikincinin ExecuteUpdate başarıyla tamamlanmasını engellerse, ilkinin etkileri veritabanında kalıcı olmaya devam eder. Aslında yukarıdaki dört işlem - iki çağrısı ExecuteUpdate, bir sorgu ve SaveChanges - her biri kendi işlemleri içinde yürütülür. Birden çok işlemi tek bir işlemde toplamak için, DatabaseFacade kullanarak açıkça bir işlem başlatın.

using (var transaction = context.Database.BeginTransaction())
{
    context.Blogs.ExecuteUpdate(/* some update */);
    context.Blogs.ExecuteUpdate(/* another update */);

    ...
}

İşlem işleme hakkında daha fazla bilgi için, İşlemleri Kullanma'ya bakın.

Eşzamanlılık kontrolü ve etkilenen satırlar

SaveChanges otomatik Eşzamanlılık Denetimi sağlar ve bir satırın yüklediğiniz an ile değişiklikleri kaydettiğiniz an arasında değiştirilmediğinden emin olmak için eşzamanlılık belirteci kullanılır. ExecuteUpdate Değişiklik izleyicisi ile etkileşim kurmadıkları ve ExecuteDelete etkileşimde bulunmadıkları için otomatik olarak eşzamanlılık denetimi uygulayamazlar.

Ancak, bu yöntemlerin her ikisi de işlemden etkilenen satır sayısını döndürür; Bu, eşzamanlılık denetimini kendiniz uygulamak için özellikle kullanışlı olabilir:

// (load the ID and concurrency token for a Blog in the database)

var numUpdated = await context.Blogs
    .Where(b => b.Id == id && b.ConcurrencyToken == concurrencyToken)
    .ExecuteUpdateAsync(/* ... */);
if (numUpdated == 0)
{
    throw new Exception("Update failed!");
}

Bu kodda, belirli bir Bloga güncelleştirme uygulamak için LINQ Where işlecini kullanırız ve yalnızca eşzamanlılık belirtecinin belirli bir değeri varsa (örneğin, blogu veritabanından sorgularken gördüğümüz değer). Ardından tarafından gerçekte kaç satırın güncelleştirildiğini ExecuteUpdatedenetleyeceğiz; sonuç sıfırsa, hiçbir satır güncelleştirilmemiştir ve eşzamanlı güncelleştirme sonucunda eşzamanlılık belirteci büyük olasılıkla değişmiştir.

Sınırlamalar

  • Şu anda yalnızca güncelleme ve silme işlemleri desteklenmektedir; ekleme işlemi ise DbSet<TEntity>.Add ve SaveChanges() aracılığıyla yapılmalıdır.
  • SQL UPDATE ve DELETE deyimleri etkilenen satırlar için özgün sütun değerlerinin alınmasına izin verse de, bu şu anda ExecuteUpdate ve ExecuteDelete tarafından desteklenmemektedir.
  • Bu yöntemlerin birden fazla çağrısı toplu hâlde işlenemez. Her çağrı veritabanına kendi gidiş dönüşünü gerçekleştirir.
  • Veritabanları genellikle UPDATE veya DELETE ile yalnızca tek bir tablonun değiştirilmesine izin verir.
  • Bu yöntemler şu anda yalnızca ilişkisel veritabanı sağlayıcılarıyla çalışır.

Ek kaynaklar