ExecuteUpdate i ExecuteDelete

Uwaga

Ta funkcja została wprowadzona w programie EF Core 7.0.

ExecuteUpdate i ExecuteDelete to sposób zapisywania danych w bazie danych bez korzystania z tradycyjnego śledzenia zmian i SaveChanges() metody ef. Aby zapoznać się z wprowadzeniem do porównania tych dwóch technik, zobacz stronę Przegląd na temat zapisywania danych.

Executedelete

Załóżmy, że musisz usunąć wszystkie blogi z oceną poniżej określonego progu. SaveChanges() Tradycyjne podejście wymaga wykonania następujących czynności:

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

context.SaveChanges();

Jest to dość nieefektywny sposób wykonywania tego zadania: wysyłamy zapytanie do bazy danych dla wszystkich blogów pasujących do naszego filtru, a następnie wysyłamy zapytania, materializujemy i śledzimy wszystkie te wystąpienia; liczba pasujących jednostek może być ogromna. Następnie informujemy monitor zmian platformy EF, że każdy blog musi zostać usunięty, i zastosuj te zmiany, wywołując metodę SaveChanges(), która generuje instrukcję DELETE dla każdego z nich.

Oto to samo zadanie wykonywane za pośrednictwem interfejsu ExecuteDelete API:

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

Korzysta to ze znanych operatorów LINQ, aby określić, których blogów należy dotyczyć — tak jak w przypadku wykonywania zapytań względem nich — a następnie nakazuje programowi EF wykonanie bazy danych SQL DELETE :

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

Oprócz tego, że jest prostsze i krótsze, wykonuje to bardzo wydajnie w bazie danych, bez ładowania żadnych danych z bazy danych lub angażowania monitora zmian ef. Należy pamiętać, że możesz użyć dowolnych operatorów LINQ, aby wybrać blogi, które chcesz usunąć — są one tłumaczone na język SQL na potrzeby wykonywania w bazie danych, tak jak w przypadku wykonywania zapytań dotyczących tych blogów.

Executeupdate

Zamiast usuwać te blogi, co zrobić, jeśli chcemy zmienić właściwość, aby wskazać, że powinny one być ukryte? ExecuteUpdate metoda podobna do wyrażenia instrukcji SQL UPDATE :

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

Podobnie jak w przypadku ExecuteDeleteprogramu , najpierw użyjemy LINQ, aby określić, które blogi powinny mieć wpływ, ale musimy ExecuteUpdate również wyrazić zmianę, która ma zostać zastosowana do pasujących blogów. Odbywa się to przez wywołanie SetPropertyExecuteUpdate wywołania i podanie go z dwoma argumentami: właściwości, która ma zostać zmieniona (IsVisible), a nowa wartość powinna mieć (false). Powoduje to wykonanie następującego kodu SQL:

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

Aktualizowanie wielu właściwości

ExecuteUpdate umożliwia aktualizowanie wielu właściwości w ramach pojedynczego wywołania. Aby na przykład ustawić IsVisible wartość false i ustawić Rating wartość zero, wystarczy połączyć dodatkowe SetProperty wywołania:

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

Spowoduje to wykonanie następującego kodu SQL:

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

Odwoływanie się do istniejącej wartości właściwości

Powyższe przykłady zaktualizowały właściwość do nowej wartości stałej. ExecuteUpdate umożliwia również odwoływanie się do istniejącej wartości właściwości podczas obliczania nowej wartości; na przykład, aby zwiększyć ocenę wszystkich pasujących blogów według jednego, użyj następującego polecenia:

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

Zwróć uwagę, że drugi argument do SetProperty jest teraz funkcją lambda, a nie stałą, jak poprzednio. Jego b parametr reprezentuje aktualizowany blog; w ramach tej lambda zawiera b.Rating klasyfikację przed jakąkolwiek zmianą. Spowoduje to wykonanie następującego kodu SQL:

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

ExecuteUpdate obecnie nie obsługuje odwoływania się do nawigacji w obrębie SetProperty lambda. Załóżmy na przykład, że chcemy zaktualizować wszystkie oceny blogów, tak aby nowa ocena każdego bloga był średnią wszystkich ocen wpisów. Możemy spróbować użyć ExecuteUpdate w następujący sposób:

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

Jednak program EF umożliwia wykonanie tej operacji przy użyciu Select metody , aby obliczyć średnią ocenę i przeprojektować ją na typ anonimowy, a następnie użyć tego ExecuteUpdate polecenia:

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

Spowoduje to wykonanie następującego kodu SQL:

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]

Śledzenie zmian

Użytkownicy zaznajomieni z SaveChanges wykonywaniem wielu zmian, a następnie wywoływani SaveChanges w celu zastosowania wszystkich tych zmian do bazy danych. Jest to możliwe przez monitor zmian platformy EF, który gromadzi — lub śledzi — te zmiany.

ExecuteUpdate i ExecuteDelete działają zupełnie inaczej: zaczynają obowiązywać natychmiast, w momencie, w którym są wywoływane. Oznacza to, że chociaż pojedynczy element ExecuteUpdate lub ExecuteDelete operacja może mieć wpływ na wiele wierszy, nie można zebrać wielu takich operacji i zastosować je jednocześnie, np. podczas wywoływania metody SaveChanges. W rzeczywistości funkcje są całkowicie nieświadome śledzenia zmian EF i nie mają żadnej interakcji z nim. Ma to kilka ważnych konsekwencji.

Spójrzmy na poniższy kod:

// 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 = context.Blogs.Single(b => b.Name == "SomeBlog");

// 2. Increase the rating of all blogs in the database by one. This executes immediately.
context.Blogs.ExecuteUpdate(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.
context.SaveChanges();

Co najważniejsze, gdy ExecuteUpdate jest wywoływana, a wszystkie blogi są aktualizowane w bazie danych, śledzenie zmian ef nie jest aktualizowane, a śledzone wystąpienie platformy .NET nadal ma oryginalną wartość klasyfikacji, od momentu, w którym został zapytany. Załóżmy, że ocena bloga wynosiła pierwotnie 5; Po wykonaniu drugiego wiersza ocena w bazie danych wynosi teraz 6 (ze względu ExecuteUpdatena ), natomiast ocena w śledzonym wystąpieniu platformy .NET wynosi 7. Po SaveChanges wywołaniu program EF wykrywa, że nowa wartość 7 różni się od oryginalnej wartości 5 i utrzymuje zmianę. Zmiana wykonywana przez program ExecuteUpdate jest zastępowana i nie uwzględniana.

W rezultacie zazwyczaj dobrym pomysłem jest unikanie mieszania zarówno śledzonych SaveChanges modyfikacji, jak i nieśledzonych modyfikacji za pośrednictwem metody/ExecuteUpdateExecuteDelete .

Transakcje

Kontynuując powyższe czynności, ważne jest, aby zrozumieć, że ExecuteUpdate i ExecuteDelete nie uruchamiaj niejawnie wywoływanej transakcji. Spójrzmy na poniższy kod:

context.Blogs.ExecuteUpdate(/* some update */);
context.Blogs.ExecuteUpdate(/* another update */);

var blog = context.Blogs.Single(b => b.Name == "SomeBlog");
blog.Rating += 2;
context.SaveChanges();

Każde ExecuteUpdate wywołanie powoduje wysłanie pojedynczego kodu SQL UPDATE do bazy danych. Ponieważ żadna transakcja nie jest tworzona, jeśli jakikolwiek błąd uniemożliwia pomyślne ukończenie drugiego ExecuteUpdate , efekty pierwszego są nadal utrwalane w bazie danych. W rzeczywistości cztery powyższe operacje ExecuteUpdate— dwa wywołania , zapytanie i SaveChanges — każde wykonuje w ramach własnej transakcji. Aby opakowować wiele operacji w jednej transakcji, jawnie uruchom transakcję za pomocą polecenia DatabaseFacade:

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

    ...
}

Aby uzyskać więcej informacji na temat obsługi transakcji, zobacz Using Transactions (Używanie transakcji).

Kontrola współbieżności i wiersze, których dotyczy problem

SaveChanges Zapewnia automatyczną kontrolę współbieżności przy użyciu tokenu współbieżności, aby upewnić się, że wiersz nie został zmieniony między momentem załadowania go i momentem zapisania w nim zmian. Ponieważ ExecuteUpdate i ExecuteDelete nie wchodzą w interakcje z monitorem zmian, nie mogą automatycznie stosować kontroli współbieżności.

Jednak obie te metody zwracają liczbę wierszy, których dotyczy operacja; Może to być szczególnie przydatne do samodzielnego implementowania kontroli współbieżności:

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

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

W tym kodzie używamy operatora LINQ Where , aby zastosować aktualizację do określonego bloga i tylko wtedy, gdy jego token współbieżności ma określoną wartość (np. ten, który widzieliśmy podczas wykonywania zapytania w blogu z bazy danych). Następnie sprawdzamy, ile wierszy zostało rzeczywiście zaktualizowanych przez ExecuteUpdateparametr ; jeśli wynik wynosi zero, nie zaktualizowano wierszy, a token współbieżności prawdopodobnie został zmieniony w wyniku współbieżnej aktualizacji.

Ograniczenia

  • Obecnie obsługiwane jest tylko aktualizowanie i usuwanie; wstawienie musi odbywać się za pomocą i DbSet<TEntity>.AddSaveChanges().
  • Chociaż instrukcje SQL UPDATE i DELETE umożliwiają pobieranie oryginalnych wartości kolumn dla wierszy, których dotyczy problem, nie jest to obecnie obsługiwane przez ExecuteUpdate program i ExecuteDelete.
  • Nie można wsadować wielu wywołań tych metod. Każde wywołanie wykonuje własną rundę do bazy danych.
  • Bazy danych zwykle zezwalają na modyfikowanie tylko jednej tabeli za pomocą funkcji UPDATE lub DELETE.
  • Te metody obecnie działają tylko z dostawcami relacyjnej bazy danych.

Dodatkowe zasoby

  • Sesja standup programu .NET Data Access Community, w której omawiamy ExecuteUpdate i ExecuteDelete.