Windows Phone fejlesztés: MVVM és adatbázis példakód

írta: Karma, 10 éve

Ez a bejegyzés eredetileg egy hozzászólásnak készült a C# programozás topikban, de annyira hosszúra sikeredett, hogy inkább kiszerveztem ide. Ennek fényében bárminemű hozzászólást itt és ott is szívesen fogadok. Az eredeti kérdés meg itt érhető el.

És akkor jöjjön a magyarázat Crytec210 kérdéséhez - eredetileg neki címezve.

---

[ÚJ!] A kész projekt GitHubon érhető el.

Perpillanat csak 2013-as Visual Studio van a kezem ügyében, így WP8 projektet tudtam csak létrehozni. De a benne foglaltak módosítás nélkül kell hogy menjenek 7-en is.

Van egy kis plusz körítés a kódban, mert azt a vázat követi amit minden WP appomban használok. Ebből végeredményben kivágható a téged érintő szakasz, majd kitérek rá, de lehet látsz benne még valami hasznosat.

Magáról a vázról:
- Az MVVMLight és a WPToolkit NuGet csomagok kulcsfontosságúak. Az előbbiből jön az ObservableObject ősosztály a modell osztályokhoz, hogy ne kelljen kézzel implementálni a linkeken emlegetett INotifyPropertyChanging és -Changed interfészeket, helyette egy sorral letudhatóak a setterek illetve a kézi értesítések is. Az utóbbiból meg a ListPicker controlt használom a szűréshez.
- Utálom a kézzel megírt SQL utasításokat. Ennek anekdotikus oka van: aki nekem elkezdett kardozni mellette a LINQ to SQL-lel vagy bármilyen magasabb szintű megoldással szemben, kritikán aluli egyszerírható szutykot rakott a projektekbe DB réteg gyanánt. YMMV. Ennek örömére a projektben LINQ to SQL van, és csak javasolni tudom, hogy vedd át. Kevesebbet írni mindig jobb.
- Az MVVMLightból kihasználom a ViewModelLocatort és az IOC konténerét is. Ez egy nagyobb falat, de akár figyelmen kívül is hagyhatod. A gyakorlatban annyit tesz itt, hogy kevesebbet kell írni, és ugyanaz a VM osztály használható designerben és futásidőben is.

És akkor az appról:

1) Csináltam egy modell osztályt, aminek olyasmi mezői vannak, mint ami a menetrendes feladatodban lehet az eddigi kérdések alapján: indulási- és célállomás, illetve idők. Tekintsd meg a TimeTableItem.cs fájlt.
- A már említett ObservableObjectből származik, úgyhogy alkalmas mind a UI mind a DB felé adatkötésre. Ehhez kell még, hogy a propertyk mögött tagváltozó álljon, és az eseménykezelők meg legyenek hívva - ami meg is történt.
- A [Table] és a [Column] attribútumokkal kész is van az adatbázishoz mappelés. Az osztály egyébként nem tud arról, hogy hol tárolod. Az én hitvallásom szerint az attribútumok hozzáadása nem jelenti azt, hogy maga az osztály szennyezett lenne, hiszen az interfészén semmi se látszik belőle. Sőt, ha JSON serializálni kéne, akkor még a DataContract attribútumokat is erre az osztályra szórnám fel, szemrebbenés nélkül.

2) A LINQ to SQL alappillére a DataContext, mert ezen keresztül érhetőek el az adatok, és ez végzi el a tényleges DB kezelést.
- Kell a projektben egy saját leszármazott a konkrét táblák miatt, nekem ez a TimeTableDataContext.cs-ben található. Azért ilyen hosszú, mert első indításkor nyom bele némi adatot; egyébként csak a Table típusú tagváltozó és a konstruktor legeleje kellene.
- A beszúrást láthatod is: az InsertOnSubmit/InsertAllOnSubmitnak objektumokat kell adni, majd a SubmitChanges hívással menteni a módosításokat.
- Vicces dolgot vettem észre: az SQL CE-ben a minimum ábrázolható dátum 1753. január 1., ezért inkább a mai nappal tároltam le az indulási időket, mintsem az 1. évvel (ahogy a korábbi hozzászólásomban spekuláltam).

3) Mivel nem szeretném, hogy a UI függjön az adatbázistól (így designerben elég körülményes lenne dolgozni vele), bevezettem az IDataSource interfészt. Lásd a DataSource.cs fájlt.
- Athlon64+ jogos korrekciója után az IQueryable<T> interfészt választottam erre, mert ez lehetővé teszi a LINQ to SQL számára, hogy elvégezhesse a lekérdezések fordítását, viszont a statikus demóadatokkal is átjárható.
- A DesignDataSource osztály kiköp pár demóadatot, ami majd a designerben megjelenik.
- A DbDataSource pedig a DB tábláját passzolja tovább.
- A kettő közötti váltás az IOC konténer feladata, ez a része kicsit hardcore.

4) Akkor most ugorjunk a másik felére a történetnek. A felületet egy fájlba sűrítettem, ez a MainPage.xaml.
- Control szinten van rajta egy ListPicker, amiben a DB-ben lévő összes város jelenik meg; valamint egy ListBox amiben a menetrendsorok jelennek meg.
- Ha a ListPickerben kiválasztasz egy várost, akkor csak azok az elemek jelennek meg, amik onnan indulnak vagy oda mennek.
- Amit itt látni kell: a MainPage.xaml.cs fájl a konstruktoron kívül üres. Nincs szükség itt logikára.
- A ListBoxnak van egy komplex ItemTemplate-je, amin az összes adat megjelenik. Az óra:perc megjelenítés is itt látható, végül maradtam a telefon beállításaira érzékeny (korrektebb) megoldásnál.
- A ListPickernek csak egy FullModeItemTemplate-je van, ami öt vagy több lehetőségnél lesz látható. Egy kicsit nagyobb feliratokat raktam be az alapnál, semmi extra.
- A feliratok mindenhol lokalizáltak, azaz angol nyelvnél angol, magyarnál magyar, német és stb-nél meg angol. Ehhez arra a LocalizedStringsre volt szükség, ami egyébként is benne van a VS sablonjában.
- Az adatok pedig a DataContexten (nem összekeverendő a LINQ-essel!) jönnek be, ami a fájl elején kerül bekötésre. A konkrét bindingot lásd a ListPicker és a ListBox ItemsSource-ánál, vagy a SelectedIndexnél. A hivatkozott propertyk mind a viewmodel részét képezik, ami a következő szakasz tárgya.

5) A QueryPageViewModel.cs fájlban található az a viewmodel, ami a(z egyetlen) képernyőt kiszolgálja.
- A VM ugyebár arra szolgál az MVVM mintában, hogy bindingon keresztül szolgáltasson adatot és elérhető parancsokat a View felé, így abba minimális logikát kell csak írni. Más szóval egy olyan osztály, aminek a mezői megfeleltethetőek a UI elemeinek, és még változáskövetést is tud.
- Ez utóbbit az MVVMLightos ViewModelBase tudja, ami szintén ObservableObject származék, úgyhogy a szabályok ugyanazok.
- Tehát a UI elemek miatt szükség van egy állomáslistára és egy opcionálisan szűrt menetrendlistára, illetve a kiválasztott állomásnak is be kell jönnie valahol. Ezek mind propertyk lesznek, melyből a legutolsó írható-olvasható, a többi csak olvasható.
- Most jött jól a harmadik pontban megírt DataSource: mindegy hogy DB-ből, tömbből, random generátorból jönnek az elemek, akkor is ugyanúgy kell elvégezni rajtuk a szűrést vagy a városok leválogatását. A getterekbe csak ennyi kerül.
- A SelectedStationIndex amellett, hogy a szokásos értesítéses formát hozza, még egy extra jelzést is lead, amitől a ListBox újrahúzza a tartalmát. Az Items getter már az új szűrőfeltétel használatával adja vissza a találatokat.

Nagyjából ennyi az áttekintés. Az újrafelhasználás kapcsán: a legfontosabb a XAML és a viewmodel. Ha valamiért nem tetszik a LINQ to SQL és inkább a lábbalhajtós megoldás mellett maradsz, a XAML-nek akkor se kell eltérnie ettől. A viewmodel is csak annyiban más, hogy a getter mögött egy valódi lista áll, meg mondjuk egy private setter, amivel az új eredményeket beállítod és kiküldöd a jelet.