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}");
	}
}