370 likes | 569 Views
Pove z ivanje podataka sa kontrolama na formi - Data Binding sa DLINQ -om. DLINQ omogućava lakše i elegantnije izvršavanje SQL upita u bazi podataka, nego korišćenjem klasa iz ADO.NET biblioteke
E N D
Povezivanjepodataka sa kontrolama na formi - Data Binding sa DLINQ-om • DLINQ omogućava lakše i elegantnije izvršavanje SQL upita u bazi podataka, nego korišćenjem klasa iz ADO.NET biblioteke • DLINQ se nalazi iznad ADO.NET-a i koristi ga, tako da se može reći da DLINQ predstavlja jednu vrstu API-a za korišćenje ADO.NET-a • DLINQ ublažava razliku između SQL-a i objektne paradigme koja se koristi u C#-u i time čini blaži prelaz između razlike rada sa podacima u relacionoj bazi i objektnog koda programa • VS 2008 ima alat za podršku i automatizaciju rada sa DLINQ-om, Object Relational Designer
Object Relational Designer • Object Relational Designer – ORL, omogućava automatsko povezivanje sa bazom podataka i kreiranje čitave infrastrukture klasa koje su potrebne za korišćenje DLINQ-a • ORL se ubacuje u WPF projekat iz menu-a Project / Add New Item... / Data / LINQ to SQL Classes, i zada se novo odgovarajuće ime • ORL automatski kreira novu klasu koja nasleđuje od DataContext klase i čije ime se automatski formira sa prefiksom zadatog imena ORL i sufiksom DataContext • Ako se ORL nazove Northwind, onda je ime nove klase NorthwindDataContext
Object Relational Designer • Da bi ORL mogao da automatski kreira entity klase koje odgovaraju tabelama baze podataka, neophodno je povezati odgovarajuću bazu podataka iz VS2008 • Povezivanje VS i baze podataka se vrši iz prozora Server Explorer (SE) • U gornjem delu prozora SE se nalazi ikona Data Connection koja se selektuje i iz menu-a Tools / Connect to Database... se dobija dijalog box za podešavanje konekcije sa bazom podataka • Za konekciju sa bazom je potrebno da postoji baza podataka kao file tipa *.mdf koji može biti već povezan sa SQL Express serverom – levi deo sledeceg slide-a ili ga tek treba povezati – Attach a data base file – desni deo sledeceg slide-a
Object Relational Designer • Kada se poveže baza podataka sa VS, onda mogu da se uvezu tabele iz povezane baze podataka za koje će ORL automatski da kreira odgovarajuće entity klase • Ispod ikone Data Connection u VS se nalazi ikona nove baze podataka koja je povezana, a ispod koje se nalazi kolekcija Tabels baze podataka • Otvaranjem ove kolecije – klik na + sa leve strane, dobija se spisak svih tabela u povezanoj bazi • Sa desne strane se odabere prozor ORL – Northwind.dbml i jednostavno se prevuku željene tabele u prozor ORL
Object Relational Designer • Za svaku prevučenu tabelu u prozor ORL se automatski kreira odgovarajuća entity klasa čiji je naziv jednak nazivu odgovarajuće tabele • U prevučenim tabelama se mogu brisati polja koja nisu potrebna i koja se neće koristiti u radu sa tabelama baze podataka • Osim navedenih tabela, ORL automatski kreira i konfiguracioni file app.config koji se dodaje projektu • File app.config je xml dokument koji sadrži connection string koji definiše konekciju sa bazom podataka i omogućava da se promene detalji konekcije bez izmene u kodu aplikacije • Od app.config se kreira file application.exe.configgde je application naziv aplikacije i distribuira se zajedno sa application.exe u istom folder-u
app.config xml file <?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> </configSections> <connectionStrings> <add name="Suppliers.Properties.Settings.NorthwindConnectionString" connectionString="Data Source=.\SQLExpress;Initial Catalog=Northwind;Integrated Security=True“providerName="System.Data.SqlClient" /> </connectionStrings> </configuration> • File pod nazivom application.exe.configima potpuno isti sadržaj kao i gore prikazani sadržaj file-a app.config, koji treba da se nalazi u istom folderu kao i prevedeni application.exe file. • Parametri konekcije se mogu promeniti u xml application.exe.configfile-u bez izmene application.exe
Definisanje izgleda stavki u combo box-u za dobavljače - suppliers <Window x:Class=”WpfApplication1.Window1” ...> <Window.Resources> <DataTemplate x:Key=”SuppliersTemplate”> <StackPanel Orientation=”Horizontal”> <TextBlock Text=”{Binding Path=SupplierID}” /> <TextBlock Text=” : “ /> <TextBlock Text=”{Binding Path=CompanyName}” /> <TextBlock Text=” : “ /> <TextBlock Text=”{Binding Path=ContactName}” /> </StackPanel> </DataTemplate> </Window.Resources> <Grid> ... </Grid> </Window> <ComboBox ... Name=”suppliersList” IsSynchronizedWithCurrentItem=”True” ItemsSource=”{Binding}” ItemTemplate=”{StaticResource SuppliersTemplate}” />
Definisanje izgleda stavki u list box-u za proizvode - products <ListView ... Name=”productsList” ...> <ListView.View> <GridView> <GridView.Columns> <GridViewColumn Width=”75” Header=”Product ID” DisplayMemberBinding=”{Binding Path=ProductID}” /> <GridViewColumn Width=”225” Header=”Name” DisplayMemberBinding=”{Binding Path=ProductName}” /> <GridViewColumn Width=”135” Header=”Quantity Per Unit”DisplayMemberBinding=”{Binding Path=QuantityPerUnit}” /> <GridViewColumn Width=”75” Header =”Unit Price” DisplayMemberBinding=”{Binding Path=UnitPrice}” /> </GridView.Columns> </GridView> </ListView.View> </ListView>
Klasa za konverziju vrednosti [ValueConversion(typeof(string), typeof(decimal?))] class PriceConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value != null) return String.Format(“{0:C}”, value); else return “”; } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } } • Klasa za konverziju PriceConverter iz tipa decimal? koji je nullable u entity klasi – može da fali, u tip String u kontroli ListView na formi
Objekat klase PriceConverter kao resurs <Window x:Class=”Suppliers.SupplierInfo” ... xmlns:app=”clr-namespace:Suppliers” ...> <Window.Resources> <app:PriceConverter x:Key=”priceConverter” /> ... </Window.Resources> ... </Window> <GridViewColumn ... Header =”Unit Price” DisplayMemberBinding= “{Binding Path=UnitPrice, Converter={StaticResource priceConverter}}” /> • U XAML kodu se sa: • <app:PriceConverter x:Key=”priceConverter” /> • Kreira objekat priceConverter klase PriceConverter kao resurs koji se koristi u GridViewColumn
Dinamičko povezivanje kontrole ComboBox sa entity klasom <Window x:Class=”Suppliers.SupplierInfo” ... Title=”Supplier Information” ... Loaded=”Window_Loaded”> ... </Window> public partial class SupplierInfo : Window { private NorthwindDataContext ndc = null; private Supplier supplier = null; private BindingList<Product> productsInfo = null; ... } private void Window_Loaded(object sender, RoutedEventArgs e) { ndc = new NorthwindDataContext(); this.suppliersList.DataContext = ndc.Suppliers; }
Dinamičko povezivanje kontrole ComboBox sa entity klasom private void suppliersList_SelectionChanged(object sender, SelectionChangedEventArgs e) { supplier = this.suppliersList.SelectedItem as Supplier; IList list = ((IListSource)supplier.Products).GetList(); productsInfo = list as BindingList<Product>; this.productsList.DataContext = productsInfo; } • EntitySet<Product> klasaimplementiraIListSource interface, koji deklariše GetList metod za kopiranje podataka iz entity set-au IList objekat list • BindingList implementira interface-e koji deklarišu događaje INotifyPropertyChangingiINotifyPropertyChangedna koje reaguju povezane kontrole i vrednosti koje pokazuju se update-uju - ažuriraju
Ažuriranje podataka sa DLINQ-om • Do sada kreirana aplikacija omogućava povezani prikaz podataka o proizvođačima - ComboBox i proizvodima – ListBox • Odnos je M : 1 – jedan proizvođač više proizvoda, i jedan proizvod jedan proizvođač • Promena – ažuriranje podataka nije moguća • Podaci iz baze su povezani sa kontrolama koje ih prikazuju preko Table kolekcija • Promena podataka u kontrolama menja podatke u povezanim entity klasama – u memoriji, ali ne menja podatke u bazi
Ažuriranje podataka sa DLINQ-om NorthwindDataContext ndc = new NorthwindDataContext(); Product product = ndc.Products.Single(p => p.ProductID == 14); product.ProductName = “Bean Curd”; ndc.SubmitChanges(); ndc.Refresh(RefreshMode.OverwriteCurrentValues, ndc.Products); • Podaci u bazi se menjaju preko SQL ili DLINQ upita • Metod SubmitChanges klase ContextData upisuje promene u bazu • Metod Refresh vraća podatke iz baze u Table kolekcije, tj. tako se mogu poništiti promene u kolekciji koje još nisu upisane u bazu • Metod Refresh može da prihvati više kolekcija – parameters kao drugi parametar
Ažuriranje podataka sa DLINQ-om • Metod SubmitChanges klase ContextData upisuje sve promene u bazu • Ako neka promena dovede do greške upisa u bazu, sve se vraća na prethodno stanje, tj. vrši se transakcija i ako bilo koja od operacija izmene ne uspe, sve se vraća na prethodno stanje – roll back • Pri tome se podaci u kolekcijama entity klasa ne menjaju • Pre ponovnog pokušaja ažuriranja treba promeniti podatke koji su doveli do neuspeha • Ako sve izmene u bazi uspeju, transakcija se izvršava – commit, i promene se upisuju u bazu
Konflikti pri ažuriranju podataka • Pri ažuriranju podataka može doći do međusobnog konflikta promena istih podataka koje unose dva korisnika • Ako dva korisnika menjaju iste podatke u bazi onda može da dođe do izgubljenih podataka koje je uneo jedan od korisnika • Metod SubmitChanges može da otkrije kada se to desi i da aktivira ChangeConflictException • SvojstvoChangeConflicts objekta klase DataContextje kolekcija objekata tipa ObjectChangeConflict • SvojstvoIsDeletedje boolean i ukazuje da li drugi korisnik pokušava da briše podatke koji se menjaju
Konflikti pri ažuriranju podataka • Zatim svojstvo MemberConflictskoje je kolekcija objekata tipa MemberChangeConflict koji sadrže korespodentnu vrednost podatka u memoriji, tekuću vrednost podatka u bazi kao i prvobitnu vrednost podatka u bazi koja je učitana • Na osnovu toga se može kreirati procedura razrešenja konflikta – koja od te tri vrednosti će se prihvatiti • Moguće je i da korisnik aplikacije odluči šta da se radi u cilju rezolucije konflikta • KlasaObjectChangeConflict sadrži method Resolve
Method Resolve klaseObjectChangeConflict • Method Resolveima argument tipa enumeracije RefreshModečije vrednosti ukazuju na način rezolucije konflikta • RefreshMode.KeepCurrentValues – tekuće vrednosti u memoriji prepisuju konfliktne vrednosti – tekući korisnik je pobednik konflikta • RefreshMode.OverwriteCurrentValue – tekuće vrednosti iz memorije se prepisuju vrednostima iz baze – tekući korisnik je gubitnik konflikta • RefreshMode.KeepChanges – izmenjene vrednosti od oba korisnika se merdžuju, ako su u različitim kolonama – oba korisnika su pobednici konflikta
Primer rezolucije konflikta try { ndc.SubmitChanges(); } catch (ChangeConflictException) { foreach (ObjectChangeConflict conflict in ndc.ChangeConflicts) { Foreach (MemberChangeConflict changeConflict in conflict.MemberConflicts) { Console.WriteLine(“Conflict Details”); Console.WriteLine(“Original value retrieved from database: {0}”, changeConflict.OriginalValue.ToString()); Console.WriteLine(“Current value in database: {0}”, changeConflict.DatabaseValue.ToString()); Console.WriteLine(“Current value in memory: {0}”, changeConflict.CurrentValue.ToString()); } conflict.Resolve(RefreshMode.OverwriteCurrentValues); } }
Method Resolve klaseObjectChangeConflict • Kolekcija ChangeConflicts klaseDataContext sadrži ResolveAll metod koji omogućava da se ista RefreshMode vrednost primeni za rezoluciju svih konflikata • Ako se metod • ndc.SubmitChanges(ConflictMode.ContinueOnConflict); • Pozove sa ConflictMode.ContinueOnConflict vrednošću enumeracije, onda se prvo probaju sve izmene nakon čega se aktivira ChangeConflictException ako ima jedan ili više konflikata • Ako se metod SubmitChanges zove bez tog argumenta, onda se generiše ChangeConflictException pri svakom konfliktu • Podrazumevano ponašanje je sa vrednošću kolekcije ConflictMode.FailOnFirstConfl ict.
Dodavanje i brisanje podataka NorthwindDataContext ndc = new NorthwindDataContext(...); Table<Product> products = ndc.Products; Product newProduct = new Product() {ProductName = “New Product”, ... }; products.Add(newProduct); ... ndc.SubmitChanges): • Posle poziva metoda SubmitChanges, DataContext objekat će generisatiSQL INSERT naredbu za svaku novu stavku u Table kolekciji Product product = products.Single(p => p.ProductID == 14); products.Remove(product); ... ndc.SubmitChanges): • Posle poziva metoda SubmitChanges, DataContextobjekat će generisati SQL DELETEnaredbu za svaku izbrisanu stavku u Table kolekciji
Ažuriranje proizvoda private void productsList_KeyDown(object sender, KeyEventArgs e) { switch (e.Key) { case Key.Enter: editProduct(this.productsList.SelectedItem as Product); break; case Key.Insert: addNewProduct(); break; case Key.Delete: deleteProduct(this.productsList.SelectedItem as Product); break; } } • Metod productsList ispituje pritisnutu tipku tastature kada se selektuje stavka liste productsList preko svojstva Key objekta e klase KeyEventArgs koja sadrži vrednosti enumeracije System.Windows.Input.Key na osnovu čega se poziva odgovarajući metod za ažuriranje
deleteProduct method private void deleteProduct(Product prod) { MessageBoxResult response = MessageBox.Show(“Delete “ + prod.ProductName, “Confirm”, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); if (response == MessageBoxResult.Yes) { supplier.Products.Remove(prod); productsInfo.Remove(prod); this.saveChanges.IsEnabled = true; } } • supplier je objekat entity klase koja služi za povezivanje sa tabelom, Products je kolekcija entity objekata za datog supplier-a – dobavljača • productsInfo je BindingList koja služi za povezivanje sa ListView kontrolom productsList
Izmena i dodavanje proizvoda • Za izmenu i dodavanje proizvoda potrebno je kreirati novu pomoćnu formu sa poljima proizvoda koja se dodaju / menjaju
<Window x:Class="Suppliers.ProductForm" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ProductForm" Height="225" Width="515" ResizeMode="NoResize"> <Grid> <Label Height="23" Margin="17,20,0,0" Name="label1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="120">Product Name</Label> <Label Margin="17,60,0,0" Name="label2" Height="23" VerticalAlignment="Top" Width="120" HorizontalAlignment="Left">Quantity Per Unit</Label> <Label Margin="17,100,0,0" Name="label3" Height="23" VerticalAlignment="Top" HorizontalAlignment="Left" Width="120">Unit Price</Label> <TextBox Height="21" Margin="130,24,0,0" Name="productName" VerticalAlignment="Top" Width="340" HorizontalAlignment="Left" /> <TextBox Height="21" Margin="130,64,0,0" Name="quantityPerUnit" VerticalAlignment="Top" HorizontalAlignment="Left" Width="340" /> <TextBox Height="21" Margin="130,104,0,0" Name="unitPrice" VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" /> <Button Height="23" HorizontalAlignment="Left" Margin="130,150,0,0" Name="ok" VerticalAlignment="Top" Width="75" Click="ok_Click">OK</Button> <Button Height="23" HorizontalAlignment="Left" Margin="300,150,0,0" Name="cancel" VerticalAlignment="Top" Width="75" IsCancel="True">Cancel</Button> </Grid> </Window>
private void ok_Click(object sender, RoutedEventArgs e) { if (String.IsNullOrEmpty(this.productName.Text)) { MessageBox.Show(“The product must have a name”, “Error”, MessageBoxButton.OK, MessageBoxImage.Error); return; } decimal result; If (!Decimal.TryParse(this.unitPrice.Text, out result)) { MessageBox.Show(“The price must be a valid number”, “Error”, MessageBoxButton.OK, MessageBoxImage.Error); return; } if (result < 0) { MessageBox.Show(“The price must not be less than zero”, “Error”, MessageBoxButton.OK, MessageBoxImage.Error); return; } this.DialogResult = true; }
Dodavanje novog proizvoda private void addNewProduct() { ProductForm pf = new ProductForm(); pf.Title = “New Product for “ + supplier.CompanyName; if (pf.ShowDialog().Value) { Product newProd = new Product(); newProd.SupplierID = supplier.SupplierID; newProd.ProductName = pf.productName.Text; newProd.QuantityPerUnit = pf.quantityPerUnit.Text; newProd.UnitPrice = Decimal.Parse(pf.unitPrice.Text); supplier.Products.Add(newProd); productsInfo.Add(newProd); this.saveChanges.IsEnabled = true; } }
Izmena podataka o proizvodu private void editProduct(Product prod) { ProductForm pf = new ProductForm(); pf.Title = “Edit Product Details”; pf.productName.Text = prod.ProductName; pf.quantityPerUnit.Text = prod.QuantityPerUnit; pf.unitPrice.Text = prod.UnitPrice.ToString(); if (pf.ShowDialog().Value) { prod.ProductName = pf.productName.Text; prod.QuantityPerUnit = pf.quantityPerUnit.Text; prod.UnitPrice = Decimal.Parse(pf.unitPrice.Text); this.saveChanges.IsEnabled = true; } }
Upis izmena u bazu private void saveChanges_Click(object sender, RoutedEventArgs e) { try { ndc.SubmitChanges(); saveChanges.IsEnabled = false; } catch (Exception ex) { MessageBox.Show(ex.Message, “Error saving changes”); } }