[asp.core, react] Paginacja, filtrowanie danych.

0

Mam pytanie, jak się powinno poprawnie zaimplementować dynamiczną paginacje i filtrowanie danych.
Powiedzmy, że w UI wyświetlam tabele z użytkownikami a na jednej stronie mogę wyświetlić max 3 osoby, co da mi 4 strony.

page1 -> u1, u2, u3
page2 -> u4, u5, u6
page3 -> u7, u8, u9
page4 -> u10

Na obecną chwilę robię to tak, że poniżej tabelki mam pagination component i kiedy klikam np. na trzecią stronę wysyłam request do api, który zwraca mi tylko użytkowników z wybranej strony.

class UserQueries
{
	public IEnumerable<UserDto> Get(int page = 1)
	{
		return Users.
			Skip((page - 1) * itemsPerPage)
			Take(itemsPerPage);
	}
}

Teraz muszę dodać do tabeli pole z dynamicznym wyszukiwaniem (filtrowaniem), ma to działać podobnie jak wyszukiwarka google czyli np. wpiszę pierwsze 2 litery imienia [ma] i chcę żeby w tabeli bez odświeżania strony, w czasie rzeczywistym, dynamicznie wyświetlili mi się tylko użytkownicy, których imię zaczyna się na Ma.

Do tej pory state z react przechowywał tylko widoczne elementy tabeli - 3 użytkowników ale, żeby filtrować wyniki wydaje mi się, że potrzebuję mieć dostęp do wszystkich użytkowników z bazy danych, jak to zrobić optymalnie?

  1. pobieram wszystkich użytkowników z bazy i dynamicznie filtruję, robię paginację już we froncie?
  2. po każdym znaku wpisanym przez użytkownika wysyłam zapytanie do api?
  3. jest jakiś inny sposób na zrobienie dynamicznego filtrowania?
1

Na szczęście nigdzie nie jest potrzebny dostęp do wszystkich użytkowników z bazy danych:). Warto wykorzystać interfejs IQueryable. Możesz to zacząć w ten sposób pisać:

 public IEnumerable<User> GetUsers(int page = 1, string filterSearch)
{
	var usersQuery = dbContext.Users.AsQueryable();
	usersQuery = ApplyFilterSearch(filterSearch, usersQuery);
    [cdn]
}

private IQueryable<User> ApplyFilterSearch(string filterSearch, IQueryable<User> usersQuery)
{
	filterSearch = filterSearch.ToLower();
	if (!string.IsNullOrWhiteSpace(filterSearch))
	{
		usersQuery = usersQuery.Where(u => u.FullName != null && u.FullName.ToLower().Contains(filterSearch)
			|| filterSearch.Contains(u.FullName.ToLower())
			|| u.EmailAddress != null && (u.EmailAddress.ToLower().Contains(filterSearch)
			|| filterSearch.Contains(u.EmailAddress.ToLower())));
	}
	return usersQuery;
}

Zauważ, że na tym etapie wciąż nie wykonujemy żadnego zapytania do bazy danych. Właściwie to przygotowujemy tylko zapytanie, które dopiero zostanie wykonane w odpowiednim momencie, a zapewnia nam to właśnie IQueryable.

W końcu finalnie wykonujemy nasze zapytanie:

[cd]
var queryResult = usersQuery
				.Skip((page - 1) * itemsPerPage)
				.Take(itemsPerPage)
				.ToList();

return new PagedResult<User>
{
	TotalCount = usersQuery.Count(),
	Items = queryResult
};

Wykorzystałem tutaj przykładową klasę generyczną, która zawiera zwracany item oraz liczbę wszystkich userów na stronie. Ta ostatnia wartość pozwoli Ci ustalić, które następne strony może kliknąć użytkownik. Rozwiązanie nie jest turbo-optymalne, bo wykonywane są dwa zapytania do bazy: pierwsze pozwalające pobrać liczbę użytkowników dla naszego zapytania, a drugie np. 10 użytkowników. Ale myślę, że na Twoje potrzeby powinno wystarczyć.

Podsumowując jeszcze Twoje pytania:

  1. Nigdy nie pobieramy wszystkich użytkowników z bazy danych;) Tworzymy IQueryable. Raz byłem na warsztatach programistycznych w jednej firmie i pewien znawca materiału w tego typu sytuacjach namiętnie używał określenia "to zabiłoby bazę". Więc tak, to zabiłoby bazę.
  2. To dobry pomysł. Ustaw sobie event na poziomie Reacta, że co sekundę po wpisaniu jakiegoś hasła będzie strzał do API. Możesz użyć switchMap (rxjs), żeby po kolejnym wykonanym filtrowaniu to poprzednie, jeśli jeszcze niezakończone, zostało anulowane.
  3. Polecam po każdym dynamicznym filtrowaniu czy sortowaniu zawsze wracać na pierwszą stronę. Z prostego powodu: jeśli w danej chwili byłaś na trzeciej, to po wykonaniu filtrowania może tej strony już nie być (a ilość wszystkich dostępnych stron określisz na podstawie TotalCount zwróconego z API, co ułatwi zrobienie paginacji). Poza tym po wykonaniu filtrowania też raczej nie interesowałyby nas wyniki akurat z trzeciej strony, nawet jakby istniała;)
0

najprościej to chyba stworzyć metodę POST w której przekażesz parametry wyszukania (m.in. name, albo od razu fullTextSearch ;])

0
darkrat napisał(a):
  1. Nigdy nie pobieramy wszystkich użytkowników z bazy danych;) Tworzymy IQueryable. Raz byłem na warsztatach programistycznych w jednej firmie i pewien znawca materiału w tego typu sytuacjach namiętnie używał określenia "to zabiłoby bazę". Więc tak, to zabiłoby bazę.

To zależy. Bo ja np pisałem aplikację, która działa wewnątrz firmy tylko i wyłącznie. I pobieram od razu wszystkich userów (około 550). I wcale nie zabijam bazy. Wszystko zależy od danego przypadku. Ja w moim wiedziałem, że userów nie będzie przybywać i sobie pozwoliłem na taki zabieg.

0
szydlak napisał(a):
darkrat napisał(a):
  1. Nigdy nie pobieramy wszystkich użytkowników z bazy danych;) Tworzymy IQueryable. Raz byłem na warsztatach programistycznych w jednej firmie i pewien znawca materiału w tego typu sytuacjach namiętnie używał określenia "to zabiłoby bazę". Więc tak, to zabiłoby bazę.

To zależy. Bo ja np pisałem aplikację, która działa wewnątrz firmy tylko i wyłącznie. I pobieram od razu wszystkich userów (około 550). I wcale nie zabijam bazy. Wszystko zależy od danego przypadku. Ja w moim wiedziałem, że userów nie będzie przybywać i sobie pozwoliłem na taki zabieg.

Rzeczywiście, są przypadki, gdzie nie trzeba do tego specjalnej wagi przykładać. Założyłem, że zależy nam na możliwie wysokiej wydajności, a nie wiemy, czy koleżanka chciałaby pobrać tych userów 10 czy 10000.

0

Dziękuję za podpowiedzi, spróbuję to zaimplementować jutro bo dzisiaj już czasu nie starczy i dam znać czy będzie działać tak jak widzę to w głowie ;) Target użytkowników w bazie na 95% nie przekroczy 1000.

Pytam bo nie wydaje mi się to aż tak dużo, żeby za jednym razem pobrać wszystkich (dto usera jest 'lekkie', po kliknięciu na konkretnego pobieram dopiero szczegóły) a zależy mi na jak najszybszej responsywności z tego powodu posiadanie w pamięci użytkowników spowoduje, że filtrowanie powinno być błyskawiczne.

A zapytam jeszcze czy posiadanie dwóch metod by miało rację bytu? Powiedzmy do konkretnej liczby wierszy np. 500, pobieram wszystkich ale jeśli liczba jest większa wtedy wykonuję zapytania np. co 1 sec, żeby nie zabić bazy.
Zmienna określająca liczbę rekordów będzie aktualizowana np. co 4h.

0

Hmm mogłabyś sobie porównać, ile czasu zajmuje wykonanie calla pobierającego 500-1000 rekordów, a ile np. 10-20. Miej jednak na uwadze, że wtedy implementujesz dwukrotnie to samo po stronie frontendu/backendu i pojawia się pytanie, czy warto.

1 użytkowników online, w tym zalogowanych: 0, gości: 1