Keri sisuni

PagedResult - andmete kuvamine lehtedena

Kuigi näited andmete kuvamise kohta õppematerjalides näitavad enamasti kuidas andmeid loendina kuvada, ei ole see just praktiline lähenemine loenditele, kus võib andmeridu olla palju. Inimene suudab hoomata ja korraga töötada ainult piiratud hulga andmetega. Näiteks 1000 rida andmetabelit on liiga suur ja sellega oleks meil ebamugav töötada. Sama asi käib andmete kohta, mida me rakendustes kasutajatele näitame.

Rakendustes - olgu need veebi-, töölaua-, või mobiilirakendused - lahendatakse probleem selliselt, et andmeid kuvatakse määratud suurusega lehtedena. Korraga kuvatakse kasutajale ühe lehe jagu andmeid. Et lehtede vahel liikuda saaks, kuvatakse tavaliselt andmetabeli kohale või alla lehtedel navigeerimise komponenti, mille nimetuseks on pager.

Pageri klassid

Enne päris koodi juurde asumist peame mõtlema võimalikule lahendusele, mis oleks piisavalt universaalne, et seda saaks korduvalt kasutada. Näitena vaatasime tunnis tahvli peal joonist lehtedeks jagatud andmetabelist. Me jagasime selle vaate kahte mõttelisse osasse:

  • Andmete osa - see on joonisel kujutatud tabel andmetega. Meil on erinevaid vaateid, kus kuvatakse erinevaid andmeid ja seega on andmete osa lehtedeks jaotatud vaadetes alati seotud vaatele vastava andmetüübiga. Näiteks autode loendis näidatakse autosid, tootjate loendivaates kuvatakse tootjaid ning need andmed on struktuurilt erinevad
  • Pageri osa - pageri osa on lihtne ja ainus, mida seal peame teadma on lehe suurus (mitu rida andmeid moodustab ühe lehe), mitmendat lehte kuvame ja kui palju on lehti kokku. Kusjuures see on nii kõikides vaadetes, kus me pagerit kasutada tahame. Pager ei sõltu andmetest, vaid ainult andmeid kirjeldavatest arvudest.

Klasside osas on esimene mõte see, et teeks kaks klassi. Üks oleks abstraktne baasklass, mis kannaks pageri arvude osa ja teine klass oleks see, mis kannaks lehtedeks jagatud andmeid (sõltuvus andmete tüübist). Loome need kaks klassi.

public abstract class PagedResultBase
{
    public int CurrentPage { get; set; }
    public int PageCount { get; set; }
    public int PageSize { get; set; }
    public int RowCount { get; set; }
}

public class PagedResult<T> : PagedResultBase
{
    public IList<T> Results { get; set; }

    public PagedResult()
    {
        Results = new List<T>();
    }
}

PagedResultBase on abstraktne klass, sest ilma andmeteta me tüüpiliselt pagerit ei kasuta. PagedResult omab tüübiparameetrit, mis ütleb meile kuvatavate andmete tüübi. Need kaks klassi on nii universaalsed, et neid saame kasutada ka erinevates projektides ilma midagi muutmata.

Andmebaasi päringu tulemuste jagamine lehtedeks

Järgmiseks on meil vaja andmebaasist pärida lehtedeks jagatud andmeid. Entity Framework Core kasutab LINQ-meetode päringute koostamiseks ja sama teed võiks ka minna lehtedeks jagatud andmetega. Meil on vaja staatilist klassi, kus on EF Core laiendusmeetod lehtedeks jagatud andmete tagastamiseks.

Klass koos GetPagedAsync() meetodiga on selline.

public static class DataExtensions
{
    public static async Task<PagedResult<T>> GetPagedAsync<T>(this IQueryable<T> query, int page, int pageSize)
    {
        // leia esimene leht (see ei saa olla negatiivne)
        page = Math.Max(page, 1);

        // koosta PagedResult, RowCount on ridade arv, mis päring muidu tagastaks
        var result = new PagedResult<T>
        {
            CurrentPage = page,
            PageSize = pageSize,
            RowCount = await query.CountAsync()
        };

        // leia lehtede koguarv
        var pageCount = (double)result.RowCount / pageSize;
        result.PageCount = (int)Math.Ceiling(pageCount);

        // leia käesoleva lehe esimese rea indeks
        var skip = (page - 1) * pageSize;

        // küsi andmebaasist käesolev leht andmetega
        result.Results = await query.Skip(skip).Take(pageSize).ToListAsync();

        return result;
    }
}

Me saame GetPagedAsync() meetodit kasutada suvaliste EF Core päringutega, mis andmeid tagastavad.

Pageri view component

Meil on nüüd olemas kõik vajalik selleks, et saada lehtedeks jagatud andmed andmebaasist kätte. Järgmiseks tuleb neid andmeid ka kasutajatele kuvada. Andmete kuvamist me juba oskame, kuid küsimus tekib pageriga - kas on võimalik teha mingi universaalne pageri komponent, mille korra kirjutame ja mida pärast saame loendivaadetes läbivalt kasutada?

ASP.NET Core pakub meile selle jaoks kaks lahendust - view component ja tag helper. Tag helper jääks meie jaoks liiga kitsaks antud juhul ja seepärast on mõistlik valida view component.

View component on vahend, mille abil saame luua vaadetesse komponente, mis teevad midagi tarka. Pageri kuvamine sobib sellise komponendi tööks kenasti. View component kordab oma struktuurilt veidikene kontrollereid.

Kontroller ViewComponent
Eraldi klass + +
Dependency Injection + +
Mudelite tugi + +
Vaadete tugi + +

View componentide juures on aga siiski mõningad erinevused. Erinevalt kontrolleritest, millel võib olla mitmeid action-meetode, on view componentil ainult üks meetod, mis selle käivitab - InvokeAsync(). View componentite vaated asuvad /Views/Shared/components/ kataloogis. Igal view componentil on oma alamkataloog. Pageri korral on selleks kataloogiks /Views/Shared/components/pager/.

Pageri view componenti jaoks teeme projekti alla kausta Components ja lisame sinna klassi nimega PagerViewComponent:

public class PagerViewComponent : ViewComponent
{
    public async Task<IViewComponentResult> InvokeAsync(PagedResultBase result)
    {
        return await Task.FromResult(View("Default", result));
    }
}

View component saab vaatest kaasa PagedResultBase tüüpi objekti - rohkemat pole talle ju pageri joonistamiseks vaja - ning annab selle kaasa oma vaatesse, kus pager lõpuks välja joonistatakse.

Kataloogi /Views/Shared/components/Pager/ lisame failid Default.cshtml koos koodiga, mis joonistab meile pageri välja kui seda küsime. Pageri vaadet ma siia eraldi välja ei too, see on lehe alguses viidatud näitefailis.

Andmete pärimine andmebaasist

Andmete pärimiseks kasutame EF Core päringuid, mis baseeruvad LINQ-il. Kui seni kasutasime loendivaadetesse andmete küsimiseks peamiselt ToList() ja ToListAsync() meetode, siis nüüd saame kasutada ka GetPagedAsync() meetodi. Vaatame näite põhjal kui lihtne või raske oleks meil kontrolleris või teenuse klassis küsida lehtedeks jagatud andmeid.

Alustame lihtsast LINQ-päringust:

var products = await _dataContext.Products
                                 .Include(p => p.Manufacturer)
                                 .OrderBy(p => p.Manufacturer.Name)
                                 .ThenByDescending(p => p.Name)
                                 .ToListAsync();

See päring tagastab meile kõik tooted, võtab toodetele külge tootja, sorteerib tulemused esiteks tootja nime ja seejärel toote nime järgi ning koostab listi.

Saamaks andmeid lehtede kaupa, peame seda päringut õige pisut muutma. Ainus muutus on päringu viimasel real, kõik muu jääb samaks.

var products = await _dataContext.Products
                                 .Include(p => p.Manufacturer)
                                 .OrderBy(p => p.Manufacturer.Name)
                                 .ThenByDescending(p => p.Name)
                                 .GetPagedAsync(1, 10);

See päring tagastab esimesed kümme toodet. Esimene GetPagedAsync() argument ütleb mitmendat andmelehte on vaja, teine argument ütleb lehe suuruse. Ülejäänud töö teeb juba GetPagedAsync() meetod.

Pageri kuvamine loendivaates

Pageri kuvamiseks loendivaadetes kasutame ASP.NET Core-i trikki, mille nimeks on tag helper syntax for view components (seda te ei pea peast teadma). Kohta, kuhu Pager tuleb kuvada, kirjutame järgmise rea:

<vc:pager result="@Model"></vc:pager>

Veenduge, et teie rakenduse kohta oleks tag helper registreeritud /Views/_ViewImports.cshtml failis. Probleemide korral küsige õpetaja käest abi.