Na stronie posiadam formularz w którym ustalam pola do filtrowania danych a następne tabelę, która te dane wyświetla. Po wysłaniu formularza kontroler dostaje obiekt SearchCriteria
, który jest wypełniony ustawieniami, które wprowadził użytkownik.
[HttpPost]
public ActionResult Index(SearchCriteria search, int page = 1, int resultsPerPage = 100)
Teraz chciałbym pobrać te dane z bazy danych na podstawie kryteriów wyszukania. Stworzyłem więc klasę, która jest modelem opcji filtrowania
public class SearchCriteria
{
public DateTime? DateFrom { get; set; }
public DateTime? DateTo { get; set; }
public int? AmountFrom { get; set; }
public int? AmountTo { get; set; }
public string BankName { get; set; }
}
A następnie klasę, która podpowiada za pobieranie danych z bazy i ich filtrowanie:
public class HistorySearchBusinessLogic
{
private DbBanki _context;
public HistorySearchBusinessLogic()
{
_context = new DbBanki("db");
}
public IQueryable<History> GetHistories(SearchCriteria searchModel)
{
var result = _context.BankHistory.AsQueryable();
if (searchModel != null)
{
if (searchModel.DateFrom.HasValue)
result = result.Where(x => x.Received >= searchModel.DateFrom);
if (searchModel.DateTo.HasValue)
result = result.Where(x => x.Received <= searchModel.DateTo);
if (searchModel.AmountFrom.HasValue)
result = result.Where(x => x.Amount >= searchModel.AmountFrom);
if (searchModel.AmountTo.HasValue)
result = result.Where(x => x.Amount <= searchModel.AmountTo);
if (!string.IsNullOrEmpty(searchModel.BankName))
{
var bank = _context.BankAccounts.Where(x => x.Name == searchModel.BankName).FirstOrDefault();
if (bank != null)
result = result.Where(x => x.Bankaccount.Id_BankShort == bank.Id);
}
}
return result;
}
}
Tak więc wygląda uzupełniony kontroler:
[HttpPost]
public ActionResult Index(SearchCriteria searchCriteria, int page = 1, int resultsPerPage = 100)
{
var logic = new HistorySearchBusinessLogic();
var results = logic.GetHistories(searchCriteria)
.OrderBy(x => x.Id)
.Skip((page-1) * resultsPerPage)
.Take(resultsPerPage)
.Select(x => new HistoryResultModel
{
Received = x.Received,
Amount = x.Amount,
BankName = x.BankAccount.Name
})
.ToList();
return View(results);
}
Wyżej pisałem o HistoryResultModel
. Jest to model, który odpowiada danym, które chce wyświetlić na mojej stronie:
public class HistoryResultModel
{
public DateTime Received { get; set; }
public int Amount { get; set; }
public string BankName { get; set; }
}
I jak na razie wszystkie działa. Mam jednak kilka pytań:
-
Najważniejsze - czy coś powinienem zmienić?
-
W bazie
Amount
jest przechowywane jako int (czyli cena w groszach). Ja w tabeli chciałbym wyświetlić to w formacie złotówek z przecinkiem. Gdzie powinienem dokonać konwersji? W widoku?
static class MoneyFormatter {
public static string PenniesToString(int pennies)
=> Convert.ToString(pennies / 100.0m);
}
<td>@MoneyFormatter.PenniesToString(Model.Amount)<td>
Czy może utworzyć ViewModel
?
public class HistoryResultViewModel
{
public HistoryResultModel HistoryResultModel {get; set;}
public string HistoryResultModelAmountFormatted {get; set;}
}
Albo po prostu dodać właściwość:
public class HistoryResultModel
{
public DateTime Received { get; set; }
public int Amount { get; set; }
public string AmountFormatted { get { return Amount / 100m; } }
public string BankName { get; set; }
}
A może coś innego?
-
Co gdybym chciał zrobić identyczne filtrowanie, ale pobierającej dane z innej tabeli? Taki przypadek wystąpiłby, gdybym miał 2 tabele: archiwum i bieżące.
Powinienem do metodyGetHistories
przkeazywać kontekst z którego ma korzystać? A może powinienem stworzyć klasę generyczną? A może wzorzec projektowy dekorator? -
Co gdybym chciał mieć przycisk, który eksportuje mi dane z tabelki? Powinienem zapisać przy każdym wyciąganiu danych zapisywać sobie je w sesji, by potem mógł z niej wyciągać przy eksportowaniu? Tak właściwie, to zaimplementowałem eksportowanie. Ma działać tak:
Jeśli użytkownik nie zaznaczył w tabelce żadnego rekordu - eksportuj wszystkie
Jeśli użytkownik zaznaczył jakiś rekordy - eksport zaznaczone
Jeśli w tabeli nie ma rekordów - nie eskportuj
Zaznaczanie rekordów odbywa się przez zaznaczenieCheckBox
, który jest w ostatniej kolumnie
Obecnie działa to tak:
string guid = StringGenerator.GetRandomString(10);
ViewBag.dataGuid = guid;
Session[guid] = results;
Session[guid + "amount"] = elements;
return View("Index", search);
O co tu chodzi? Najpierw generuję sobie losowy ciąg znaków, który będzie identyfikował moje dane w sesji. W Session[guid + "amount"]
zapisuję informacje o ilości danych, które zwróciło mi wyszukiwanie. Ale chwila, co to jest to elements
? A no bo ja wyciągam z bazy danych zawsze resultsPerPage
danych. Niezależnie od tego ile jest wyników, to zawsze wyciągnę np. 100 sztuk. Ale ja chcę mieć paginację! W takim razie potrzebuję 2 metody - jedna, która zwróci mi informację o tym ile jest wyników spełniających kryteria wyszukania oraz drugą, która zwróci mi np. pierwsze resultsPerPage
wyników, które owe wyszukania spełniają.
Mam więc informację o tym ile łącznie jest wyników, więc mogę stworzyć paginację! W sesji zapisuję tą informację, bo gdy w tabeli nie ma zaznaczonego żadnego rekordu, to nie wiem, czy użytkownik chce wyeksportować wszystkie, czy nie ma żadnych do wyeksportowania.
Ok, w gruncie rzeczy sprowadza się do tego, że w sesji zapisuję wyniki wyszukiwania aktualnie wyświetlanej strony. Jeśli więc użytkownik chce wyeksportować rekordy z aktualnej strony, to pobieram je z sesji. jeśli natomiast chce wyeksportować wszystkie rekordy jakie spełniają kryteria wyszukania to niestety muszę ponownie je wyszukać.
Z widoku do kontrolera obsługującego eksportowanie przesyłam te rzeczy:
- id zaznaczonych checkboxów (czyli id rekordu w bazie danych)
- guid danych w sesji
- kryteria wyszukania
I wszystko działa jak powinno - jeśli użytkownik eksportuje z aktualnie wyświetlanej strony wyniki dostaje od razu (bo są w sesji). Jeśli chce wyniki z wszystkich stron paginacji, to musi poczekać na zassanie ich z bazy.
Co sądzicie o takim rozwiązaniu? Troche jest karkołomne. Może po prostu powinienem za każdym razem przekazywać tylko informacje o zaznaczonych id rekordów oraz model kryteriów wyszukiwania i pobierać dane z bazy na nowo?