Paweł Łukasiewicz
2021-07-05
Paweł Łukasiewicz
2021-07-05
Udostępnij Udostępnij Kontakt
Wprowadzenie

Kasowanie danych odbywa się na encjach, których EntityState ustawiony jest na Deleted. Warto mieć na uwadze, że nie ma różnicy w usuwaniu encji w scenariuszu połączonym lub rozłączonym. EF Core pozwala na łatwe usunięcie encji z kontekstu, który z kolei dokona usunięcia rekordu przy użyciu dostępnych metod. Mam tutaj na myśli metody Remove oraz RemoveRange, które opisywałem we wprowadzeniu do scenariusza rozłączonego.

Przechodzimy od razu do konkretów i analizy poniższego przykładu:

// Scenariusz rozłączony
// encja do usunięcia
var toDelete = new Person()
{
	Id = 3
};

using(var context = new ApplicationDbContext())
{
	context.Remove<Person>(toDelete);

	context.SaveChanges();
}
W powyższym przykładzie zdefiniowaliśmy rekord do usunięcia (na bazie poprawnego identyfikatora). Przy pomocy metody Remove dokonujemy zmiany EntityState a użycie SaveChanges usunie dany rekord z bazy danych:
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

',N'@p0 int',@p0=3

Co jeśli określony/zdefiniowany identifykator nie istnieje w bazie danych? EF Core rzuci wyjątek DbUpdateConcurrencyException, który w polskim tłumaczeniu przyjmie poniższą postać:

Operacja wykonana na bazie danych oczekiwała wpłynąć na 1 wiersz(e) ale w rzeczywistości wpłynęła na 0 wierszy. Dane mogły zostać zmodyfikowane lub usunięcie od czasu załadowania jednostek.

Co zatem możemy zrobić? Powinniśmy odpowiednio obsłużyć wyjątek lub upewnić się, że dane o odpowiednim Id istnieją w bazie danych przed ich usunięciem:

// Scenariusz rozłączony
// encja do usunięcia
var toDelete = new Person()
{
	Id = 33
};

using (var context = new ApplicationDbContext())
{
	try
	{
		context.Remove<Person>(toDelete);
		context.SaveChanges();
	}
	catch (DbUpdateConcurrencyException ex)
	{
		// "Obsługa" w takiej postaci na potrzeby przykładu
		Console.WriteLine("Rekord od wskazanym idetyfikatorze nie istnieje w bazie danych.");
	}
	catch (Exception)
	{
		throw;
	}
}

Usuwanie wielu rekordów

Usunięcie wielu rekordów możliwe jest przy wykorzystaniu metody RemoveRange dostępnej w ramach kontekstu lub wskazanej encji:

List<Person> toDelete = new List<Person>()
{
	new Person() { Id = 4},
	new Person() { Id = 5},
	new Person() { Id = 6},
	new Person() { Id = 7},
};

using(var context = new ApplicationDbContext())
{
	context.RemoveRange(toDelete);
	context.SaveChanges();
}
W tym wypadku usunięcie wszystkich danych odbywa się "za jednym zamachem" (optymalizacja po stronie EF Core) :
exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [Person]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

DELETE FROM [Person]
WHERE [Id] = @p1;
SELECT @@ROWCOUNT;

DELETE FROM [Person]
WHERE [Id] = @p2;
SELECT @@ROWCOUNT;

DELETE FROM [Person]
WHERE [Id] = @p3;
SELECT @@ROWCOUNT;

',N'@p0 int,@p1 int,@p2 int,@p3 int',@p0=4,@p1=5,@p2=6,@p3=7

Usuwanie danych powiązanych

Jeżeli dana encja jest w relacji z innymi encjami (jeden do jednego lub jeden do wielu) to usunięcie powiazanych danych po usunięciu encji głównej zależy od sposobu konfiguracji tego powiązania. Osoby uważne doskonale pamiętają konfigurację przy wykorzystaniu Fluent API oraz poniższy zapis:

modelBuilder.Entity<…>()
    .HasOne<…>(s => …)
    .WithMany(g => …)
    .HasForeignKey(s => …)
    .OnDelete(DeleteBehavior.Cascade); // Konfiguracja usuwania danych powiązanych

W naszym przykładzie wykorzystujemy relację jeden do jednego pomiędzy osobą i przypisanym paszportem:

// Konfiguracja relacji jeden do jednego
modelBuilder.Entity<Person>()
	.HasOne<Passport>(p => p.Passport)
	.WithOne(pp => pp.Person)
	.HasForeignKey<Passport>(pp => pp.PassportNumberOfPersonId);

modelBuilder.Entity<Passport>()
	.HasOne<Person>(pp => pp.Person)
	.WithOne(p => p.Passport)
	.HasForeignKey<Passport>(pp => pp.PassportNumberOfPersonId);
Wykorzystując powyższą konfiguracje wprowadzimy drobną zmianę, która wraz z usunięciem konkretnej osoby doprowadzi do usunięcia danych dotyczących paszportu:
modelBuilder.Entity<Person>()
	.HasOne<Passport>(p => p.Passport)
	.WithOne(pp => pp.Person)
	.HasForeignKey<Passport>(pp => pp.PassportNumberOfPersonId)
	.OnDelete(DeleteBehavior.Cascade);