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 ExecuteDelete
programu , 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 SetProperty
ExecuteUpdate
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
Nawigacje i powiązane jednostki
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 ExecuteUpdate
na ), 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/ExecuteUpdate
ExecuteDelete
.
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 ExecuteUpdate
parametr ; 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>.Add SaveChanges().
- 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 iExecuteDelete
. - 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
iExecuteDelete
.