Czy należy pobierać encję podczas aktualizacji?

Wątek przeniesiony 2024-11-07 01:24 z Bazy danych przez somekind.

0

Załóżmy, że chciałbym zaktualizować dane w tabeli account. Które podejście jest bardziej właściwe?
SELECT encji + UPDATE całej encji

public class AccountService : IAccountService
{
	private readonly IAccountRepository accountRepository;
	private readonly IAccountValidationService accountValidationService;
	
	public AccountService(IAccountRepository accountRepository, IAccountValidationService accountValidationService)
	{
		this.accountRepository = accountRepository;
		this.accountValidationService = accountValidationService;
	}
	
	public void SetName(int id, string name)
	{
		accountValidationService.ValidateName(name);
		
		var entity = accountRepository.GetById(id);
		entity.name = name;
		
		accountRepository.Update(entity);
	}
}

samo UPDATE 1 kolumny

public class AccountService : IAccountService
{
	private readonly IAccountRepository accountRepository;
	private readonly IAccountValidationService accountValidationService;
	
	public AccountService(IAccountRepository accountRepository, IAccountValidationService accountValidationService)
	{
		this.accountRepository = accountRepository;
		this.accountValidationService = accountValidationService;
	}
	
	public void SetName(int id, string name)
	{
		accountValidationService.ValidateName(name);
		accountRepository.UpdateName(id, name);
	}
}
1

Samo Update. Jeśli coś można zrobić jednym zapytaniem to tak robisz, bo wtedy znikają ci problemy z transakcyjnością, jest mniejsza szansa na błąd i jest szybciej.

0

I rozumiem, że w sytuacji, gdy chciałbym sprawdzić, czy dany użytkownik może modyfikować dany wpis w tabeli (np. tutaj chciałbym sprawdzić, czy osoba modyfikująca post jest jego autorem), to zamiast pobierać encje, to użyć WHERE?

public class PostService : IPostService
{
	private readonly IPostRepository postRepository;
	private readonly IPostValidationService postValidationService;
	
	public PostService(IPostRepository postRepository, IPostValidationService postValidationService)
	{
		this.postRepository = postRepository;
		this.postValidationService = postValidationService;
	}
	
	public void Update(UpdatePostDTO dto, int accountId)
	{
		postValidationService.ValidateContent(dto.Content);
		
		postRepository.UpdateContent(dto.Content, accountId);
		// 
		// UPDATE dbo.post 
		// SET content = @Content 
		// WHERE account_id = @AccountId
        //
	}
}
2

Nie ma lepszego. Oba podejścia mają swoje wady i zalety - pytanie których zalet potrzebujesz, a jakich wad nie możesz tolerować.

dasek napisał(a):

I rozumiem, że w sytuacji, gdy chciałbym sprawdzić, czy dany użytkownik może modyfikować dany wpis w tabeli (np. tutaj chciałbym sprawdzić, czy osoba modyfikująca post jest jego autorem), to zamiast pobierać encje, to użyć WHERE?

Standardowym podejściem byłoby jednak zrobić SELECT, sprawdzić czy user może edytować post, i wtedy zrobić UPDATE.

Potencjalnym problemem mogłoby być, gdyby między SELECTem i UPDATEm autor postu został zaktualizowany, ale wydaje mi się że w Twoim przypadku to nie ma znaczenia.

Wybierz podejście które Ci się bardziej podoba.

2

Jeżeli już ktoś używa ORMa to powinien robić po ORMowemu.
Ponieważ mogą być zapięte listenery, audyt, zapis encji historycznych itp. to zawsze należy pobrać encję (ale dzieci już niekoniecznie, LazyLoading co nie) wykonać na niej operacji i zapisać jak Pan Bóg przykazał.

Jak ktoś chce update'ować kolumny to po co używa ORMa? Brać w takim przypadku microORM'a jak Jooq lub QueryDSL i się nie bawić w Hibernaty.

3
dasek napisał(a):

Załóżmy, że chciałbym zaktualizować dane w tabeli account. Które podejście jest bardziej właściwe?

Jeśli chcesz aktualizować dane w tabeli, to napisz polecenie SQL.

Jeśli na siłę stosujesz DDD i chcesz edytować encję, to używaj repozytorium.

Jeśli chcesz po prostu zrobić endpoint, który służy do edycji jakichś wartości w bazie, to nie potrzebujesz ani DDD ani repozytoriów - to jest najbardziej zbędny wzorzec ze wszystkich powszechnie stosowanych.

samo UPDATE 1 kolumny

public class AccountService : IAccountService
{
	private readonly IAccountRepository accountRepository;
	private readonly IAccountValidationService accountValidationService;
	
	public AccountService(IAccountRepository accountRepository, IAccountValidationService accountValidationService)
	{
		this.accountRepository = accountRepository;
		this.accountValidationService = accountValidationService;
	}
	
	public void SetName(int id, string name)
	{
		accountValidationService.ValidateName(name);
		accountRepository.UpdateName(id, name);
	}
}

Z metodą UpdateName, to nie jest repozytorium tylko jakaś cieknąca abstrakcja.

3
dasek napisał(a):

Załóżmy, że chciałbym zaktualizować dane w tabeli account. Które podejście jest bardziej właściwe?
SELECT encji + UPDATE całej encji
samo UPDATE 1 kolumny

Z perspektywy wydajności silnika bazy danych lepsze jest podejście 2, czyli update pojedynczej kolumny. Sprawa się komplikuje gdy patrzymy z perspektywy aplikacji, bo masz ORM i coś co udaje wzorzec Repository.

A teraz odpowiem pytaniem na pytanie, tak żeby spojrzeć na temat z szerszej perspektywy i skłonić do jakiejś rozkminy,

Czy jeżeli zastosujesz podejście samo UPDATE 1 kolumny to będziesz miał w swoim repozytorium czy czego tam używasz metodę na każde pole encji?

Teraz masz UpdateName za miesiąc będzie UpdateAge, a za rok będzie nie wiadomo co. Jak tak, to po co ci to repozytorium?. Użyj DbContextu (które samo implementuje wzorzec Repository i Unit of Work) w serwajsie i po sprawie. Pisze o tym @somekind DbContext pozwala efektywnie robić aktualizacje pojedynczych kolumn i nie potrzebujesz w tym celu dodatkowej abstrakcji, która jest.. no właśnie, czym? Nakładką na ORM-a? Bezsensu.

Jak idziesz w kierunku Rich Domain Model, gdzie chcesz mieć logikę na poziomie encji to wtedy bierzesz całą encję + to czego potrzebuje do wykonania algorytmu jak np. powiązane z użytkownikiem uprawnienia i w metodzie account.HasPermission(action) robisz odpowiedni check uprawnień i jak wszystko jest ok, to modyfikujesz encję (czy bardziej poprawnie agregat) i zapisujesz encję z powrotem do bazy. Pisze o tym @Riddle Możesz w tym celu użyć bezpośrednio DbContesxt albo Repository, zależnie od architektury. Czasem lepsze będzie Repository a czasem DbContext.

0

Rozumiem, że w sytuacji, gdy chciałbym sprawdzić, czy dany użytkownik może modyfikować dany wpis w tabeli (np. tutaj chciałbym sprawdzić, czy osoba modyfikująca post jest jego autorem), to zamiast pobierać encje, to użyć WHERE?

Zacząłem pisać, ale widzę, że częściowo temat "polizał" @markone_dev w poście powyżej.
Pamiętaj, że takie proste WHERE nie zawsze/nie do końca ogarnie temat. Bo często stosuje się bardziej skomplikowane systemy kontroli uprawnień/dostępu/ACL i tam wcale nie ma prostej zależności że autor wpisu może w nim grzebać - masz całe grupy (np. wszyscy z działu handlowego), masz udostępnianie poszczególnych elementów innym użytkownikom itp. Także tutaj jak najbardziej masz rację, że przed zrobieniem UPDATE trzeba sprawdzić uprawnienia, ale nie będzie to takie proste WHERE autor_wpisu = XXX.

4

Jest jeszcze jeden aspekt. W realnych systemach bardzo często każda encja ma pola audytowe np Created, Modified, CreatedBy. Są one uzupełniane w momencie SaveChanges. Więc sam update jednej kolumny to tak średnio by pasował. Nawet jak pobierasz encje to ona się cała nie aktualizuje tylko zmienione pola jeśli obiekt jest trackowany.

1
dasek napisał(a):

Załóżmy, że chciałbym zaktualizować dane w tabeli account. Które podejście jest bardziej właściwe?
SELECT encji + UPDATE całej encji
...
samo UPDATE 1 kolumny
...

Masz jakieś szczególne wymagania i potrzeby odnośnie tego, co się powinno wydarzyć dookoła tego update?

Jeżeli nie, to prawdopodobnie prostsze podejście sprawdzi się lepiej. Jeśli to prosty update rekordu, i zmiana kolumny nie ma na nic wpływu ani od niczego szczególnie nie zależy, to drugie podejście jest prostsze i pewnie w zupełności Ci wystarczy. Wydajnościowo też będzie lepiej.

Jeśli jednak coś od tego zależy - jak choćby weryfikacja uprawnień, albo od wartości w tej kolumnie zależy spójność jakichkolwiek innych danych, to z tego względu wybrałbym pierwsze podejście kosztem dodatkowej roboty i nieco gorszej wydajności (o ile te różnice byłyby w ogóle mierzalne na tym etapie projektu), ale za to byłbym w stanie wykonać bardziej kompleksową interakcję.

A gdybym nie był pewien, czego konkretnie się spodziewać w przyszłości - a prawie nigdy nie jestem - wybrałbym prostsze podejście z założeniem, że muszę przygotować kod na przyszłą przeróbkę pod podejście bardziej złożone, i starał się tak skonstruować moduły aplikacji, uważać na cieknące abstrakcje i wydzielić odpowiedzialności, żeby w przypadku takiej podmianki zmiany miały minimalny zakres.

dasek napisał(a):

I rozumiem, że w sytuacji, gdy chciałbym sprawdzić, czy dany użytkownik może modyfikować dany wpis w tabeli (np. tutaj chciałbym sprawdzić, czy osoba modyfikująca post jest jego autorem), to zamiast pobierać encje, to użyć WHERE?

No i to na przykład sugeruje, że jednak możesz potrzebować czegoś bardziej złożonego, ale to też zależy. Dopóki to jest trywialny warunek WHEN user_id = xyz, pełen select może być jeszcze do uniknięcia. Ale jeśli to się zacznie rozrastać, tobym się zastanowił czy złożonych warunków nie lepiej przenieść do kodu aplikacji, bo logika aplikacji zaszyta w zapytaniach SQL jest ciężka w utrzymaniu, nawet jeśli wydajnościowo lepiej strzelić takim selectem niż pobierać dane.

Alternatywnie jest jeszcze podejście gdzieś pomiędzy, czyli najpierw strzelić zapytaniem SELECT FOR UPDATE i w nim sprawdzić, czy warunki wstępne jak uprawnienia / ownership są spełnione - nie musisz wyciągać całego obiektu i budować encji, żeby to zrealizować, wystarczy że zapytanie zwróci informację. Możesz to wyciągnąć gdzieś obok update'u samego w sobie, zasłonić szczegóły implementacyjne jakąś nie wiem, metodą IsUserAuthorised(userId) i w razie potrzeby wymienić implementację na taką z logiką po stronie aplikacji.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.