WPF i LINQ to SQL


U narednih nekoliko postova biće riječi o novoj microsoftovoj tehnologiji za razvoj windows aplikacija Windows Presentation Foundation, kao i razvoj aplikacija koje se oslanjaju na baze podataka preko LINQ to SQL i LINQ to Entity. Biće prikazani osnovni modeli aplikacije sa prikazom implementacije osnovnih CRUD operacija razvijani na WPF tehologiji.

Kratki uvod

WPF je nova tehnologija razvoja windows aplikacija bazira na DirectX grafičkoj biblioteci, umjesto klasičnih GDI aplikacija na kojim su se zasnivale sve dosadašnje microsoftove tehnologije. Ovim prelazom omogućen je razvoj windows grafičkih interfejsa na sličan način kako se to radi sa web aplikacijama. U tom smislu razvijen je novi programski jezik XAML (čitaj zaml) koji je sastavni dio .NET Frameworka. Sa razvojem XAML razvijen je novi skup programskih rješenja koja pomažu u dizajnu windows aplikacija nazvan Expression Studio. Ovaj paket aplikacija uključuje Expression Blend, Expresion Design i druge aplikacije kojim razvijamo GUI dok logiku ispod kontrola GUI-a razvijamo u klasičnim razvojnim alatima poput Visual Studia. Sa ovim se razdvojio dizaj i logika aplikacije te uvela nova tehnologija razvoja aplikacija kao i sistematičnija kolaboracija izmedju dizajnera i developera.

Informacije o WPF mogu se pronaći na internetu sa vrlo različitim novoima predznanja, te u ovom vrlo kratkom uvodu želio sam samo natuknuti neke od osnovnih značajki ove tehnologije. Svakako prije čitanja ovih postova potrebno je minimalno predznanje o WPF tehnologiji. Prvi post će biti prikazan u obliku tutorijala s kojim ćemo implementirati jednostavan primjer povezivanja baze podataka sa WPF aplikacijom, te upoznati osnovne operacije umetanja, modifikacije i brisanja zapisa iz baze podataka. Primjer će također prikazati neke od metoda na koji način koristimo Visual Studio 2008 i Microsoft Expression Blend u razvoju aplikcija. Za ovaj i primjere koji će biti prikazani u narednim postovima, potrebna je instalacija Visual Studio 2008 SP1, Expresion Blend 2, Microsoft SQL Server 2005 ili 2008,NorthWind baza podataka, a svi se oni mogu skinuti na microsoftovim stranicama, kao i u besplatin express verzijama.

NorthWind WPF primjer

Prije početka potrebno je NorthWind bazu podataka instalirati na server, kako bi se kroz Visual studio mogao formirati model baze podataka.

Pokrenimo VS 2008 te formirajmo novi WPF projekat kao na sljedećoj slici.

Nakon formiranja WPF projekta potrebno je formirati LINtoSQL model northwind baze podataka. Način na koji formiramo model možete pogledati na jednom od mojih prethodnih blog postova. Ovaj put naš model se sastoji od sljedećih tabela baze podataka: Customer, Employee, Order, Order_Detail. Sada naš projekat izgleda kao na slici 2.

U solucijskog exploreru možemo vidjeti da se naš projekat sastoji od Window1.xaml i korespodentne cs datoteke, App.xaml te korespodentne cs datoteke, kao i novoformiranog modela baze podataka kojeg smo objašnjavali u prethodnim postovima. Da bi NorthWind model baze podataka inkorporirali sa aplikacijom potrebno je dodatno implementirati logiku.

Kada sa prethodne slike desnim klikom miša odaberemo opciju View Code dobijamo implementaciju klase NorthWindKlaseDataContext. U klasi je potrebno implementirati metode za izvlačenje svih Narudžbi (Odrers), Dobavljača i Zaposlenika. Implementacija je prikazana u sljedećoj slici:

namespace NorthWindWPFPrimjer
{
    using System.Collections.ObjectModel;
    using System.Linq;

    partial class NorthWindKlaseDataContext
    {
        public ObservableCollection<Order> IzvuciNarudze()
        {
            var query = from p in Orders select p;
            return new ObservableCollection<Order>(query);
        }
        public ObservableCollection<Customer> IzvuciDobavljace()
        {
            var query = from p in this.Customers select p;
            return new ObservableCollection<Customer>(query);
        }
        public ObservableCollection<Employee> IzvuciZaposenike()
        {
            var query = from p in this.Employees select p;
            return new ObservableCollection<Employee>(query);
        }
    }
}

Važna napomena: Nemojte ni u kom slučaju using naredbe postavljati iznad prostora imena NorthWindWPFPrimjer jer će vam se izgubiti datoteka dizajnera i vaš model će nestati. Nažalost ovo je bug koji se nalazi u Visual Studio 2008 SP1.

Na ovaj način implementirali smo metode za izvlačenja podataka iz baze podataka. Sada je na redu da to implementiramo u window1.xaml datoteci. Da bi prikazali podatke o naružbama u window1.xaml datoteci potrebno je formirati objekat ObjectDataProvider kojim ćemo povezati kontrole za prikaz narudžbi. Naravno, ovaj tutorijal neće ulaziti u detalje i objašnjenja svih pojmova i tipova podataka, jer to i nije svrha. Možemo kazati da ObjectDataProvider obuhvata i formira objekat koji omogućuje povezivanje sa izvorom podataka. U WPF izvor podataka mogu biti klasični izvori podataka: vanjska baza podataka, XML datoteka, in memory kolekcija podataka, kao i svaka windowsova standardna ili kastomizirana kontrola, odnosno bilo koji .NET objekat. Ovo je i jedan od najvažnijih aspekata ove tehnologije. Kako smo na samom početku rekli XAML kao novi prograski jezik nije zamjena za klasične .NET jezike, on u biti predstavlja jedno proširenje .NET programskim jezicima, tako da sve što se može implementirati pomoću XAML-a možemo to učiniti i sa C# odnosno VB jezikom, dok obrnuto ne vrijedi.

Prilikom razvijanja aplikacije u WPF, implementacija u XAML datoteci označava deklarativnu implementaciju dok se implementacija u korespondentnom C# ili VB jeziku zove code-behind implementacija.

Dizaj aplikacije

Sada je potrebno našu aplikaciju dizajnirati, u principu potrebno je definisati kontrole koje će kasnije biti povezane sa bazom podataka odnosno sa tabelama i kolonama. Dizaj aplikacije sastoji se od jedne ListBox kontrole u kojoj će biti prikazane sve narudžbe, dok će sa desne strane biti polja u kojim će biti prikazani detalji svake narudžbe pojedinačno. Donja slika prikazuje dizaj prozora.

Sa desne strane postavili smo detalje svake narudžbe od kojih je najinteresantniji dio kolone Customer i Employee koji predstavljeju vanjske ključeve za tabele Customer i Employee. Preko ComboBoxa ostvarićemo ovu relaciju, gdje ćemo u combobox listi prikazati sve partnere (Customers) i zaposlenike (Employees). Obzirom da u standardnom setu kontrola ne postoji kontrola izbora Datuma i Vremena koristimo običnu edit kontrolu Text Box za prikaz datuma.

Cjelokupan dizajn gornjeg prozora uradili smo u XAML datoteci dok ćemo logiku aplikacije implementirati u cs datoteci ili tzv. Code-behind datoteci. Ovo je klasičan primjer upotrebe WPF tehnologije. Dizaj aplikacije implementirati u XAML, a logiku u code-behind datoteci.

Povezivanje sa bazom podataka

Da bi povezali aplikaciju sa bazom podataka potrebno je formirati objekat klase NorthWindKlaseDataContext preko kojeg ćemo vršiti upit prema bazi podataka. Ovdje ćemo pokazati dva načina implementacije. Jedan način je da u code-behind formiramo objekat te povežemo kontrole sa izvorom podataka, a drugi da u XAML-u uradimo isto. Oba načina su ispravna i nema nikave razlike u pogledu performansi. Na sljedećem ispisu prikazan je code-behind način. Listing datoteke Window1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace NorthWindWPFPrimjer
{
    public partial class Window1 : Window
    {
        NorthWindKlaseDataContext dx = new NorthWindKlaseDataContext();
        public Window1()
        {
            InitializeComponent();

            listBox1.ItemsSource = dx.IzvuciNarudze();
            listBox1.DisplayMemberPath = "OrderID";

            Binding binding = new Binding();
            binding.Source = listBox1.ItemsSource;
             //OrderID
            binding.Path = new PropertyPath("OrderID");
            textBox1.SetBinding(TextBox.TextProperty, binding);

            //OrderID
            Binding binding1 = new Binding();
            binding1.Source = listBox1.ItemsSource;
            binding1.Path = new PropertyPath("CustomerID");
            comboBox1.ItemsSource = dx.IzvuciDobavljace();
            comboBox1.DisplayMemberPath = "ContactName";
            comboBox1.SelectedValuePath="CustomerID";
            comboBox1.SetBinding(ComboBox.SelectedValueProperty, binding1);

            //EmployeeID
            Binding binding2 = new Binding();
            binding2.Source = listBox1.ItemsSource;
            comboBox2.ItemsSource = dx.IzvuciZaposenike();
            comboBox2.DisplayMemberPath = "FirstName";
            comboBox2.SelectedValuePath = "EmployeeID";
            binding2.Path = new PropertyPath("EmployeeID");
            comboBox2.SetBinding(ComboBox.SelectedValueProperty, binding2);

            Binding binding3 = new Binding();
            binding3.Source = listBox1.ItemsSource;
            //OrderDate
            binding3.Path = new PropertyPath("OrderDate");
            textBox2.SetBinding(TextBox.TextProperty, binding3);

            Binding binding4 = new Binding();
            binding4.Source = listBox1.ItemsSource;
            //ShipVia
            binding4.Path = new PropertyPath("ShipVia");
            textBox3.SetBinding(TextBox.TextProperty, binding4);

            Binding binding5 = new Binding();
            binding5.Source = listBox1.ItemsSource;
            //Freight
            binding5.Path = new PropertyPath("Freight");
            textBox4.SetBinding(TextBox.TextProperty, binding5);

            Binding binding6 = new Binding();
            binding6.Source = listBox1.ItemsSource;
            //ShipPostalCode
            binding6.Path = new PropertyPath("ShipPostalCode");
            textBox5.SetBinding(TextBox.TextProperty, binding6);
        }
    }
}

Pokrenimo aplikaciju iz VS i dobićemo sljedeću sliku:

Ako pokušamo da selektujemo drugu stavku iz liste automatski cemo dobti druge vrijednosti u desnim kontrolama. Ovo automatsko sihroniziranje selektovane stavke iz liste i kontrola obezbjedjeno je formiranjem ObservableCollection klase te osobine liste IsSynchronizedWithCurrentItem=”True”.

Sljedeći listing prikazuje prethodno ali implementirano u XAML-u.

<Window x:Class="NorthWindWPFPrimjer.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:NorthWindWPFPrimjer"
    Title="NorthWind Prijmer" Height="357" Width="514" Closing="Window_Closing">
    <Window.Resources>
        <!--Formiranje ObjectDataProvidera preko NazivaMetode bez parametara-->
        <ObjectDataProvider x:Key="listaNarudzbi"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciNarudzbe"/>
        <ObjectDataProvider x:Key="listaPartnera"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciDobavljace"/>
        <ObjectDataProvider x:Key="listaZaposlenika"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciZaposenike"/>
    </Window.Resources>
    <Grid>
        <!--Rešetka za raspored kontrola-->
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
        <!--List box kontrola za naslovom-->
        <TextBlock Margin="0,0,0,0" Name="textBlock1" Text="Lista Naružbi"
                   VerticalAlignment="Center" HorizontalAlignment="Center"
                   FontSize="16" FontWeight="Bold" Foreground="Chocolate" />
        <ListBox Name="listBox1" Grid.RowSpan="7" Grid.Row="1" IsSynchronizedWithCurrentItem="True"
                 ItemsSource="{Binding Source={StaticResource listaNarudzbi}}" DisplayMemberPath="OrderID" />

        <!--Labele za oznakama polja za editovanje-->
        <Label Grid.Column="1" Margin="10,12,0,0" Name="label1" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">OrderID:</Label>
        <Label Margin="6,6,4,6" Name="label2" Grid.Column="1" Grid.Row="1" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Customer:</Label>
        <Label Margin="6,12,4,0" Name="label3" Grid.Column="1" Grid.Row="2" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Employee:</Label>
        <Label Margin="6,6,4,6" Name="label4" Grid.Column="1" Grid.Row="3" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Order Date:</Label>
        <Label Margin="6,6,4,6" Name="label5" Grid.Column="1" Grid.Row="4" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Ship Via:</Label>
        <Label Margin="6,6,4,6" Name="label6" Grid.Column="1" Grid.Row="5" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Freight:</Label>
        <Label Margin="6,12,4,0" Name="label7" Grid.Column="1" Grid.Row="6" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Postal Code:</Label>
        <!--Kontrole za modifikaciju i prikaz kolona narudžbe-->
        <TextBox Margin="0,8,0,8" Grid.Column="2" Grid.Row="0" Name="textBox1"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=OrderID}" />
        <ComboBox Margin="0,8,0,8" Grid.Column="2" Grid.Row="1"  Name="comboBox1"
                  ItemsSource="{Binding Source={StaticResource listaPartnera}}"
                  SelectedValuePath="CustomerID"
                  SelectedValue="{Binding Source={StaticResource listaNarudzbi}, Path=CustomerID}"
                  DisplayMemberPath="ContactName"/>
        <ComboBox Margin="0,8,0,8" Name="comboBox2" Grid.Column="2" Grid.Row="2"
                  ItemsSource="{Binding Source={StaticResource listaZaposlenika}}"
                  SelectedValuePath="EmployeeID" SelectedValue="{Binding Source={StaticResource listaNarudzbi},  Path=EmployeeID}"
                  DisplayMemberPath="FirstName"/>
        <TextBox Margin="0,8,0,8" Name="textBox2" Grid.Column="2" Grid.Row="3"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=OrderDate }" />
        <TextBox Margin="0,8,0,8" Name="textBox3" Grid.Column="2" Grid.Row="4"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=ShipVia}" />
        <TextBox Margin="0,8,0,8" Name="textBox4" Grid.Column="2" Grid.Row="5"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=Freight}" />
        <TextBox Margin="0,8,0,8" Name="textBox5" Grid.Column="2" Grid.Row="6"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=ShipPostalCode}" />
        <!--Dugmad za brisanje i dodavanje stavki u bazu podataka-->
        <Button Grid.Column="2" Grid.Row="7" Margin="65,6,23,10" Name="button1">Obriši</Button>
        <Button Margin="21,6,17,10" Name="button2" Grid.Column="1" Grid.Row="7" Click="button2_Click">Nova</Button>
    </Grid>
</Window>

Podcrtani XAML kod označava novi kod koji smo dodali pri povezivanju kontrola sa bazom podataka.

Na samom početku definisali smo ObjectDataProvide preko NazivaMetode, i to za svaku tabelu. ObjectDataProvider uvijek se definiše unutar resursa Kontrole roditelja te na taj način objekt postaje vidljiv u svim kontrolama koje pripadaju datom roditelju.

Definisanje logike aplikacije

Prethodno smo definisali model baze podataka preko LINQtoSQL dizajnera, dodatno implementirali poslovnu logiku modela. Zatim smo dizajnirali našu aplikaciju na način da smo definisali raspored i vrste kontrola. Poslije toga povezali smo bazu podataka sa aplikacijom i prikazali podatke preko kontrola.

Za ovaj tutorijal ostaje da implementiramo CRUD operacije odnosno modifikaciju podataka, brisanje i dodavanje redova u bazu podataka. Za početak implementirajmo modifikaciju podataka na način da kad se aplikacija zatvori, da se provjeri da li su podaci mijenjani u odnosu na originalne podatke i baze podataka te na osnovu toga izvršiti modifikaciju baze podataka.

Za ovo nam je potrebno implementirati Closing dogadjaj.

Implementacija unosa novog reda u tabeli

Da bi uopće manipulisali prikazanim podacima u našoj aplikaciji potrebno je dobiti referencu na ListCollectionView a koja predstavlja tekuće podatke u našoj aplikaciji. Takodjer ako smo povezivanje uradili u XAML-u potrebno je definisati referencu na objekat NorthWindKlaseDataContext, a koju ćemo dobiti iz ObjectDataProvidera.

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
 {
  //U koliko je u toku zatvaranja prozora jos uvijek postojala modifikacija odredjene stavke
  //potrebo napraviti Refresh da bi sve promjene bile aktualizirane
  defaultView.Refresh();

  //Provjera da li postoji bilo kakva promjena koja bi se trebala zapisati u bazu podataka
  int br = dataContext.GetChangeSet().Deletes.Count +
           dataContext.GetChangeSet().Inserts.Count +
           dataContext.GetChangeSet().Updates.Count;

  if (br > 0)
    {
      if (MessageBox.Show("Da li želite pohraniti nastale promjene?",
           "WPF Primjer", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            dataContext.SubmitChanges();
        }
    }
 }

Iz tog razloga potrebno je na novou Windows objekta formirati članove na sljedeći način:

//Referenca na DefaultView koja je definisan u list kontroli
ListCollectionView defaultView;
//Referenca da DataContext
NorthWindKlaseDataContext dataContext;

Prilikom učitavanja aplikacije odnosno pozivanja dogadjaja Loaded potrebno je ove reference definisati:

private void Window_Loaded(object sender, RoutedEventArgs e)
 {
   //Referenca na DefaultView
   defaultView = (ListCollectionView)CollectionViewSource.GetDefaultView(listBox1.ItemsSource);
   dataContext = (NorthWindKlaseDataContext)(FindResource("listaNarudzbi") as ObjectDataProvider).ObjectInstance;

    Debug.Assert(defaultView != null);
    Debug.Assert(dataContext != null);
 }

Dodavanje nove stavke vrši se jednostavno na sljedeći način:

//Dodavanje nove stavke
private void button2_Click(object sender, RoutedEventArgs e)
 {
   //dodavanje nove stavke
   Order newOrder = (Order)defaultView.AddNew();
   //novoformiranu stavku pohraniti u bazu podataka prvi put kada se pozove SubmitCange
   dataContext.Orders.InsertOnSubmit(newOrder);

 }

Pozivom AddNew metode iz defaltView i novu stavku potrebno je registrovati u DataContext da bi se pohranile promjene. Kada pokrenemo aplikaciju i kliknemo da dugme Dodaj, u listBox na kraju se pojavi nova stavka. Sa desne strane popunimo polja. Prilikom zatvaranja aplikacija pojavljuje se upit za pohranu podataka.

Implementacija brisanja stavke

Birisanje stavke iz liste vršimo listingom prikazanim na sljedećoj slici:

//Brisanje tekuce stavke
private void button1_Click(object sender, RoutedEventArgs e)
  {
    if (MessageBox.Show("Da li želite obrisati tekuću stavku?",
         "WPF Primjer", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
          return;
    //Prvo je potrebno objekat da se objekat oznaci obrisan u DataContext
    dataContext.Orders.DeleteOnSubmit((Order)defaultView.CurrentItem);
    //Označeni objekat brisemo iz DafaulView
    defaultView.Remove(defaultView.CurrentItem);

  }

Pokrenemo aplikaciju te klikom na dugme Obriši pojavljuje se poruka za potvrdu brisanja stavke.

Zaključak

Ovim jednostavnim primjerom prikazali smo jedan od načina implementacije LINQ to SQL u WPF. Sljedećim postovima obradićemo manipulaciju sa podacima u smislu sortiranja grupiranja podataka. Također u narednim postovima govorićemo o konvertorima, kastomizaciji listbox kontrole i sl.

Izvorni kod za ovaj primjer možete skinuti sa ovog linka.

About Bahrudin Hrnjica

PhD in Mechanical Engineering, Microsoft MVP for .NET. Likes .NET, Math, Mechanical Engineering, Evolutionary Algorithms, Blogging.

Posted on 22/12/2008, in .NET, C#, WPF and tagged , , , . Bookmark the permalink. 2 Comments.

  1. Odlično :) drago mi je pročitati od domaćih ljudi o WPF-u :)

    Bilo bi dobro da si koristio entity framework umjesto Linq 2 SQL (kao što i sam kažeš link 2 sql is thing of past i nebi trebali nove korisnike navikavati na njega.)

    Ja znam da sam imao problem i sam pri odlučivanju šta da koristim Linq 2 SQL ili Entity Framework. Oboje na prvi pogled izgledaju isto, al ustvari su totalna razlika. (Za one koji još razmišljaju šta je bolje: Entity Framework)

    Također efektivniji način povezivanja kontrola u WPF-u sa podacima iz baze je preko MVVM patterna. Ovo što si trenutno predložio izgleda kao da dolazi od winforms programera :)

    Pozdrav,
    Haris

  2. Hvala na komentaru,
    LINQ2SQL je bila vrlo obećavajuća tehnologija, jer se prije pojavila nego EF, i ja sam ga objeručke prihvatio. Dobrim dijelom sam je ugradio u svoje komercijalne proizvode. U vrijeme kad sam pisao ovu seriju članaka (2008 god) Entity Framework je bio samo priča. Ništa ozbiljno se nije moglo napraviti na njemu, osim da mapiras tabelu, query i jednostavnu stornu proceduru koja vraća single value a najbolje kad stavis da ne vraća nista :), a za Linq2SQL se nije pričalo da se odustaje od njega.

    Naravno danas koristim WPF/Silverlight tehnologije za razvoj aplikacija uz MVVM tehnike programiranja i Entity Framework za mapiranje baze podataka, i konačno se polako odvajam od WinForms-a po meni najbolje i najuspješnije Microsoftove tehnologije za razvoj windows aplikacija.

    Pozdrav!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s