Wprowadzenie
Instrukcja UPDATE jest wykonywana dla jednostek, których stan określony jest jako Modified. W przypadku scenariusza połączonego (omawianego wcześniej) DbContext śledzi wszystkie jednostki dzięki czemu wie, które zostały zmodyfikowane ustawiając automatycznie odpowiedni EntityState.
W naszym przypadku kontekst nie posiada tych informacji ponieważ zostały one zmodyfikowane poza DbContext - w pierwszej kolejności musimy ręcznie dokonać dołączenia odłączonych jednostek a następnie ustawić odpowiedni EntityState.
W poniższym przykładzie będziemy bazować na utworzonej wcześniej tabeli Person:
// Scenariusz rozłączony
// Załóżmy, że dane zostały pobrane z bazy danych
var john = new Person() { Id = 3, FullName = "Krzysztof" };
// Aktualizacja imienia wskazanej osoby
john.FullName = "John";
using(var context = new ApplicationDbContext())
{
// Dołączenie encji do kontekstu i zmiana EntityState na Modified
context.Update<Person>(john);
// Zapisanie zmian i wywołanie instrukcji UPDATE
context.SaveChanges();
}
Alternatywnym zapisem użycia metody Update jest poniższy sposób dokonany bezpośrednio na encji:
context.Person.Update(john);
Efektem działania powyższego fragmentu kodu jest wygenerowanie poniższej instrukcji SQL:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Person] SET [FullName] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=3,@p0=N'John'
Aktualizacja wielu rekordów
W tym przypadku do naszej dyspozycji została oddana metoda UpdateRange.
Bazując na poprzednim wpisie dokonamy aktualizacji kilku rekordów – zasymulujemy proces pobrania danych w scenariszu rozłączonym a następnie dokonamy aktualizacji imion naszych pracowników:
// Scenariusz rozłączony
// Dane zostały pobrane oraz zaktualizowane
var update1 = new Person() { Id = 1, FullName = "Sample1" };
var update2 = new Person() { Id = 2, FullName = "Sample2" };
var update3 = new Person() { Id = 3, FullName = "Sample3" };
// Tworzymy listę rekordów do aktualizacji
var updatedEntries = new List<Person>()
{
update1,
update2,
update3
};
using(var context = new ApplicationDbContext())
{
context.Person.UpdateRange(updatedEntries);
context.SaveChanges();
}
Podobnie jak w poprzednim przypadku operacje wykonywane są per rekord:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Person] SET [FullName] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=1,@p0=N'Sample1'
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Person] SET [FullName] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=2,@p0=N'Sample2'
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [Person] SET [FullName] = @p0
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;
',N'@p1 int,@p0 nvarchar(4000)',@p1=3,@p0=N'Sample3'
Z pomocą przychodzi (wspomniana w poprzednim wpisie biblioteka), która pozwala na przeprowadzenie operacji Bulk Update, która aktualizuje wszystkie rekordy za jednym zamachem:
exec sp_executesql N'MERGE INTO [Person] AS DestinationTable
USING
(
SELECT TOP 100 PERCENT * FROM (SELECT @0_0 AS [Id], @0_1 AS [FullName], @0_2 AS ZZZ_Index
UNION ALL SELECT @1_0 AS [Id], @1_1 AS [FullName], @1_2 AS ZZZ_Index
UNION ALL SELECT @2_0 AS [Id], @2_1 AS [FullName], @2_2 AS ZZZ_Index) AS StagingTable ORDER BY ZZZ_Index
) AS StagingTable
ON DestinationTable.[Id] = StagingTable.[Id]
WHEN MATCHED THEN
UPDATE
SET [FullName] = StagingTable.[FullName]
;',N'@0_0 int,@0_1 nvarchar(max) ,@0_2 int,@1_0 int,@1_1 nvarchar(max) ,@1_2 int,@2_0 int,@2_1 nvarchar(max) ,@2_2 int',@0_0=1,@0_1=N'Sample11',@0_2=0,@1_0=2,@1_1=N'Sample22',@1_2=1,@2_0=3,@2_1=N'Sample33',@2_2=2
Zrozumieć EntityState
We wprowadzeniu do scenariusza rozłączonego pisałem (w tabelce), że EntityState w przypadku użycia metody Update jest ustawiany na bazie właściwości klucza. Jeżeli właściwość klucza (encji głównej lub podrzędnej – z zależności od określonej relacji) jest pusta, jest wartością null lub jest to wartość domyślna dla zdefiniowanego typu danych to metoda Update uznaje taki obiekt za nową encję i ustawia wartość EntityState na Added.
Przeanalizujmy poniższy przykład w którym jeden obiekt ma zdefiniowaną wartość klucza a drugi nie – prześledzimy zmiany stanu EntityState:
static void Main(string[] args)
{
// Scenariusz rozłączony
// Zmiany w EntityState
var person1 = new Person()
{
FullName = "Robert"
};
var person2 = new Person()
{
Id = 3,
FullName = "Updated_Person"
};
using (var context = new ApplicationDbContext())
{
// EntityState: Added (brak Id)
context.Person.Update(person1);
// EntityState: Modified (podano Id w celu dokonania aktualizacji)
context.Person.Update(person2);
CheckEntityState(context.ChangeTracker.Entries());
}
}
// EntityEntry wymaga paczki: using Microsoft.EntityFrameworkCore.ChangeTracking
private static void CheckEntityState(IEnumerable<EntityEntry> records)
{
foreach (var row in records)
{
Console.WriteLine($"Encja: {row.Entity.GetType().Name}, EntityState: {row.State}");
}
}