TOPlist

Programujeme pro WP: Používáme SQL CE ve Windows Phone 7.5 (2. díl)

V druhém díle seriálu o SQL CE databázi a jejím používání ve Windows Phone se podíváme na to, jak si vytvořit GUI a následně si napíšeme trochu kódu pro vlastní obchod.

V předchozím díle jsme se naučili, jak se tvoří jednoduchá databáze na platformě SQL CE a také jsme si předvedli tvorbu poměrně základní tabulky. Ovšem nadefinované třídy se nerozpohybují samy, proto si teď vytvoříme GUI a napíšeme špetku kódu pro náš obchod s (potenciálně tajným) černým zbožím. Hlavně pozor na policii, aby nic nezjistila… :-)

LINQ to SQL aneb co to celé pohání

Slovo úvodem: V minulém díle seriálu jste si mohli všimnout, že jsem vynechal samotnou podstatu fungování databáze, kterou se chystáme implementovat. Pokusím se to teď napravit. Vnímavější z vás si možná všimli spojitosti s LINQ (Language Integrated Query), což je vlastně jazyk integrovaný v .NET Frameworku už od verze 3.0 a slouží pro různé dotazy nad jakýmikoliv daty. A jeho bratříček, LINQ to SQL, se stará jednak o překlad dotazů v LINQ na databázové SQL dotazy a poté o převod a mapování získaných dat na objekty a třídy. Jinými slovy, díky LINQ to SQL můžete provádět databázové dotazy a zároveň stále manipulovat s původními objekty (třídami, instancemi apod.) bez přebytečných mezivýsledků. LINQ to SQL tak za nás vlastně dělá veškerou špinavou práci. Je to zároveň jediný (dobře, jediný snadný) způsob, jak na Windows Phone 7 přistupovat k SQL CE databázi, a to pomocí DataContextu (který reprezentuje databázi) a upravených tříd (tabulek) s atributy [Table] a [Column], které jsme si minulý týden také vytvářeli.

Pár dalších úprav Zboží

Z minula máme připravený projekt včetně třídy Zbozi a databázového prostředníka ObchodDataContext. Použít ho takhle, připojení k databázi by sice fungovalo, ale do budoucna se nám ale bude hodit několik dalších úprav v třídě Zbozi, ať už z důvodu výkonu, sledování změn instancí nebo paměťové spotřeby.

Přidejte si do souboru Zbozi.cs následující deklaraci using:

using System.ComponentModel;

A k třídě Zbozi implementujte použití dvou rozhraní: INotifyPropertyChanged a NotifyPropertyChanging. Budou se hodit nejen při bindingu v rámci XAML, INotifyPropertyChanging má v LINQ to SQL svůj hlubší smysl. Normálně se při získávání dat z databáze vytváří (pro účely sledování změn objektů) dvě navzájem propojené kopie získaných dat: jedna s původním obsahem a druhá aktuální. Druhá kopie se však vytvoří společně s tou první, a to i tehdy, kdy se v položce nic nezměnilo. Implementací obou rozhraní INotifyPropertyChanged a INotifyPropertyChanging docílíme toho, že se druhá kopie vytvoří až tehdy, když se v instanci skutečně nějaká hodnota změní.

Implementace těchto rozhraní je velmi snadná. Potřebujeme je nejprve zapsat do definice třídy:

[Table]
public class Zbozi : INotifyPropertyChanged, INotifyPropertyChanging
{…

Dále umístíme do kódu třídy eventy a jejich „vyvolávače“:

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (null != handler)
    {
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
public event PropertyChangingEventHandler PropertyChanging;
private void NotifyPropertyChanging(String propertyName)
{
    PropertyChangingEventHandler handler = PropertyChanging;
    if (null != handler)
    {
        handler(this, new PropertyChangingEventArgs(propertyName));
    }
}

A nakonec upravíme settery jednotlivých vlastností. Ukážeme si to nejprve na vlastnosti Nazev.

[Column]
public string Nazev
{
    get { return nazev; }
    set 
    {
        NotifyPropertyChanging("Nazev");
        nazev = value;
        NotifyPropertyChanged("Nazev");
    }
}

NotifyPropertyChanging() voláme vždy před přiřazením nové hodnoty do proměnné a NotifyPropertyChanged() až poté.

Těmito jednoduchými kroky šetříme RAM, které je na mobilních strojích vždycky málo (a stejně ji vždycky spolehlivě spolykají memory leaky při zobrazování :-) ).

Výsledná třída tedy bude vypadat takto:

public class Zbozi : INotifyPropertyChanged, INotifyPropertyChanging
{
    private int id;
    private string nazev;
    private string vyrobce;
    private string distributor;
    private double cena;
    private double vaha;
    private double slevaProcent;
    private double mnozstviNaSklade;

    [Column(IsPrimaryKey = true, CanBeNull = false, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
    public int ID
    {
        get { return id; }
        set
        {
            id = value;
        }
    }

    [Column]
    public string Nazev
    {
        get { return nazev; }
        set 
        {
            NotifyPropertyChanging("Nazev");
            nazev = value;
            NotifyPropertyChanged("Nazev");
        }
    }
    [Column]
    public string Vyrobce
    {
        get { return vyrobce; }
        set 
        {
            NotifyPropertyChanging("Vyrobce");
            vyrobce = value;
            NotifyPropertyChanged("Vyrobce");
        }
    }
    [Column]
    public string Distributor
    {
        get { return distributor; }
        set 
        {
            NotifyPropertyChanging("Distributor");
            distributor = value;
            NotifyPropertyChanged("Distributor");
        }
    }
    [Column]
    public double Cena
    {
        get { return cena; }
        set 
        {
            NotifyPropertyChanging("Cena"); 
            cena = value;
            NotifyPropertyChanged("Cena");
        }
    }
    [Column]
    public double SlevaProcent
    {
        get { return slevaProcent; }
        set 
        {
            NotifyPropertyChanging("SlevaProcent");
            slevaProcent = value;
            NotifyPropertyChanged("SlevaProcent");
        }
    }
    [Column]
    public double Vaha
    {
        get { return vaha; }
        set 
        {
            NotifyPropertyChanging("Vaha");
            vaha = value;
            NotifyPropertyChanged("Vaha");
        }
    }
    [Column]
    public double MnozstviNaSklade
    {
        get { return mnozstviNaSklade; }
        set
        {
            NotifyPropertyChanging("MnozstviNaSklade");
            mnozstviNaSklade = value;
            NotifyPropertyChanged("MnozstviNaSklade");
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (null != handler)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public event PropertyChangingEventHandler PropertyChanging;
    private void NotifyPropertyChanging(String propertyName)
    {
        PropertyChangingEventHandler handler = PropertyChanging;
        if (null != handler)
        {
            handler(this, new PropertyChangingEventArgs(propertyName));
        }
    } 
}

Zobrazení zboží

Teď, když máme hotovou naši tabulku, se pomaličku přesuneme na tvorbu ListBoxu a k němu tzv. DataTemplate, což je šablona pro zobrazení jednotlivých položek v ListBoxu. Obojí máme v projektu sice už připraveno, ne však pro naše zboží (a už vůbec ne pro černý obchod, to však nevadí :o) ).

Šablonu si připravíme právě pro instance třídy Zbozi. Bude jednoduchá, našim potřebám zcela vyhovující: tři TextBlocky, jeden větší, druhý menší tmavý a třetí taky menší, napravo, ten bude zobrazovat cenu. A nahradíme jí původní šablonu ve FirstListBoxu. Rovnou také upravíme binding ItemsSource na {Binding SeznamZbozi}.

<ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding SeznamZbozi}">
    <ListBox.ItemTemplate>
        <DataTemplate>
          <Grid Margin="0,0,0,17" Width="432" Height="78">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <StackPanel Grid.Column="0">
                    <TextBlock Text="{Binding Nazev}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
                    <TextBlock Text="{Binding Vyrobce}" TextWrapping="Wrap" Style="{StaticResource PhoneTextSubtleStyle}"/>
                </StackPanel>
                <TextBlock Grid.Column="1" Text="{Binding Cena}" TextWrapping="Wrap" Margin="12,12,12,0" Style="{StaticResource PhoneTextLargeStyle}"
                           VerticalAlignment="Top"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

To by bylo prozatím vše k návrhu šablony. Doplním, že k takové piplačce se šablonami se náramně hodí Expression Blend, který dokáže vizualizovat šablonu v době tvorby a nemá problém s bindingem. Je součástí SDK pro Windows Phone 7, nemusíte ho nikde dodatečně stahovat.

Přesuneme se do MainPage.xaml.cs a přesvědčíme se, že se v konstruktoru nachází přiřazení MainViewModelu k DataContextu naší stránky. Hodit se bude také handler MainPage_Loaded, který zavolá funkci LoadData() v MainViewModelu a ta bude načítat data z databáze.

public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();

        // Set the data context of the listbox control to the sample data
        DataContext = App.ViewModel;
        this.Loaded += new RoutedEventHandler(MainPage_Loaded);
    }

    // Load data for the ViewModel Items
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (!App.ViewModel.IsDataLoaded)
        {
            App.ViewModel.LoadData();
        }
    }
}

Příprava MainViewModelu

Přesuňme se tedy do MainViewModel.cs. Nejdříve přidáme na začátek stránky důležitou using deklaraci:

using System.Linq;

Pak vytvoříme novou ObservableCollection s druhem položek Zbozi a v konstruktoru ji inicializujeme:

public MainViewModel()
{
    this.SeznamZbozi = new ObservableCollection<Zbozi>();
}

public ObservableCollection<Zbozi> SeznamZbozi { get; private set; }

A na LoadData toho přijde zatím nejvíc. Vyvoříme novou instanci třídy ObchodDataContext. Zjistíme, zda už databáze je vytvořená, pokud ne, jednu si uděláme. Rovnou taky do tabulky Zbozi nasypeme několik prvních položek, ať máme co vypisovat.

ObchodDataContext oDataContext = new ObchodDataContext();
if (!oDataContext.DatabaseExists())
{
    oDataContext.CreateDatabase();
    // Vytváření nových položek:
    oDataContext.Zbozi.InsertOnSubmit(new Zbozi
    {
        Nazev = "Dopisní obálka černá C5, 50 ks",
        Vyrobce = "Papírny ABC",
        Distributor = "Česká kočka",
        Vaha = 0.08, // kg
        MnozstviNaSklade = 25,
        Cena = 39, // Kč
        SlevaProcent = 0
    });
    oDataContext.Zbozi.InsertOnSubmit(new Zbozi
    {
        Nazev = "Peněženka černá kožená",
        Vyrobce = "Well-Made Leather Inc.",
        Distributor = "Well-Made Leather s.r.o.",
        Vaha = 0.35, // kg
        MnozstviNaSklade = 12,
        Cena = 399, // Kč
        SlevaProcent = 5
    });
    oDataContext.Zbozi.InsertOnSubmit(new Zbozi
    {
        Nazev = "Myš dotyková černá",
        Vyrobce = "Crapple Inc.",
        Distributor = "Crapple Česká republika s.r.o.",
        Vaha = 0.15, // kg
        MnozstviNaSklade = 8,
        Cena = 699, // Kč
        SlevaProcent = 15
    });
    oDataContext.SubmitChanges();
}

Funkce InsertOnSubmit() je přístupná v každé tabulce a s její pomocí se naplňuje fronta položek k přidání do databáze. Funkcí SubmitChanges() provedeme zápis všech změn (čili nejen přidání, ale i úpravy či smazání záznamů).

V druhé půlce této metody načteme z databáze LINQ dotazem všechny položky a postupně je přidáme do seznamu. Díky bindingu v XAML kódu se naplní i seznam na obrazovce telefonu.

var result = from polozka in oDataContext.Zbozi
             select polozka;

foreach (var item in result)
{
    this.SeznamZbozi.Add(item);
}

oDataContext.Dispose();

this.IsDataLoaded = true;

Trochu si popíšeme, co se teď děje. LINQ dotaz se nevykonává okamžitě po provedení daného kusu kódu, místo toho se vytvoří „kolekce“ IQueryable<Zbozi>, která v sobě vlastně drží jen informace o dotazu, který se má vykonat. A dokud se nepokusíte tuto kolekci nějakým způsobem projít (iterace foreach, First, ToList apod.), dotaz se neprovede. Má to ovšem i svou výhodu, můžete tak dotaz různě upravovat, kombinovat, přidávat podmínky a hrát si s ním, a teprve až jste s ním spokojeni, vyvoláte položky a tím i dotaz na databázi. Krásný článek k tomuto tématu si můžete přečíst na blogu Evana Naglea.

A po dotazu nesmíme zapomenout databázové spojení zavřít; toho docílíme funkcí Dispose(). Nechtěných otevřených spojení se ovšem nemusíte bát, pokud příkazy pro databázi zabalíte do direktivy using:

using (ObchodDataContext oDataContext = new ObchodDataContext())
{
    if (!oDataContext.DatabaseExists())
    {
        // … a tak dále …
    }
    // … a tak dále …
} // v tomto bodě se provede Dispose instance oDataContext

A když si nyní zkusíte aplikaci zkompilovat a spustit, uvidíte obrazovku podobnou té na obrázku níže:

 

Gratuluji, to byl váš první databázový dotaz.

Co dál?

Už jsme si ukázali, jak se dá jednoduše vytvořit databáze s jednou tabulkou. Stačí k tomu jedna třída reprezentující tabulku (Zbozi), atributy [Table], [Column] a nakonec třída odvozená od DataContext, která reprezentuje databázi. LINQ dotazem pak můžeme snadno vyvolávat nejrůznější záznamy. Příště si ukážeme, jaké další schopnosti LINQ to SQL nabízí a čím můžeme databázové dotazy ještě zrychlit a zefektivnit. Samozřejmě jsem zvědavý na vaše názory v komentářích, nebojte se a ptejte se.

Pokud si chcete stáhnout archiv s celým projektem, klikněte na tento odkaz.

Autor článku Paulos
Paulos

Kapitoly článku