Fujitsu-Siemens
 
M A G A Z I N
 
PROGRAMIRANJE 
  Nikola Radivojević

ClientDataSet – I deo

Trenutno živimo u svetu gde je već skoro svaka pomisao na informacioni sistem vezana za mrežu
Trenutno živimo u svetu gde je već skoro svaka pomisao na informacioni sistem vezana za mrežu, ako ne globalnu, onda barem lokalnu, tako da se polako, ali sigurno i sistemi za upravljenje bazama podataka orijentišu na tu stranu i gube se tragovi desktop (lokalnim) bazama podataka. Takav razvoj na sceni informacionih sistema naveo je i kompaniju Borland da se orijentiše u pravcu velikih mreža i Interneta, a zatvori projekat koji je godinama razvijan (BDE – Borland Database Engine). Sa izlaskom distribucije Delphi 6, odnosno Kylix 1 proizvoda uvedena je nova tehnologija, DBExpress, koju i sam Borland preporučuje za razvoj novih sistema, a BDE je ostavljen za podršku ranijih projeka. Iako ne naročito omiljena platforma za rad sa bazama podataka, BDE je imao mnoge dobre strane i ogromne mogućnosti i olakšice u razvoju aplikacija koje su se oslanjale na lokalne baze podataka (Dbase, Paradox, Access, FoxPro...). Kao alternativu za rad sa lokalnim bazama Borland je ponudio “MyBase” koja se ogleda u komponenti ClientDataSet.
Cilj ovoga članka će biti da se ukratko opišu bitne karakteristike navedene komponente i način primene i mogućnosti u korišćenju lokalnih podataka i komponentu ClientDataSet. Biće pokazan način kreiranja strukture podataka, pregled, pretraga i indeksiranja podataka, dok će u sledećim člancima biti objašnjeni načini ograničavanja prikaza podataka, pretrage po određenim kriteriumima, postavljanja svojstva agregacija, mogućnosti kloniranja kursora podataka i opisane prednosti korišćenja ove komponente u analiti podataka u distribuiranim informacionim sistemima.


Kreiranje lokalnih tabela


Podaci u okviru ClientDataSet komponente se čuvaju u memoriji, a njima se pristupa preko interfesja ove komponente koji ima kompatibilnosti sa standardizovanim operacijama nad bazama podataka. Bitna razlika između ovakvog pristupa i ostalih tehnologija je u brzini. Pošto se ovde podaci drže u memoriji računara, rad sa njima se odlikuje velikom brzinom. Na osnovu date brzine u radu bilo je moguće komponentu proširiti sa mnogim dodatnim metodama i osobinama, a da se ne primeti pad performansi u radu.
U martovskom članku Omega magazina, upotrebio sam ClientDataSet koja je preko komponente DataSetProvider bila povezana sa SQLDataSet komponentom iz koje je crpela podatke. U ovome slučaju DataSetProvider daje meta podatke ClientDataSet komponenti i omogućava na taj način formiranje struktura u memoriji gde će se čuvati podaci. Pošto u ovoj primeni nema DataSetProvider-a potrebnu strukturu podataka ćemo morati sami da napravimo. ClientDataSet podatke o strukturi drži preko kolekcije FieldDefs. Recimo da želimo napraviti tabelu koja će nam omogućavati rad sa podacima o osobama, i da su nam potrebni podaci prezime, ime, datum rođenja, pol osobe, fotografija, adresa stanovanja i elektronska pošta. Da bismo definisali ovu strukturu podataka za vreme projektovanja aplikacije trebamo: otvoriti novi projekat za izradu aplikacije, dodati komponentu ClientDataSet na formu i dodati u kolekciju FieldDefs komponente ClientDataSet potrebne definicije, sa željim atributima tako da naša kolekcija na kraju izgleda kao na slici 1.

Slika 1: Kolekcija FieldDefs


Da bismo završili definisanje meta podataka moramo dodeliti i potrebne atribute datim poljima, tako da polje ID bude tipa ftautoinc, polja Prezime i Ime budu tipa ftString sa dužinom 30, DatumRodjenja tipa ftDateTime, Fotografija tipa ftBloab, a elektronska pošta tipa ftString sa dužinom 250. Da bi nam polje autoinc stvarno bio jedinistveni broj koji korisnik neće moći da menja možemo postaviti atribut faReadonly u ObjectInspector-u, kao što je prikazano na slici 2.

Slika 2: Postavljanje atributa


Kada smo završili definisanje željene strukture podataka, potrebno je napraviti DataSet. Ovo se može za vreme projektovanja odraditi na preko pomoćnog menija na komponenti ClientDataSet, izborom opcije Create DataSet. Sada je ClientDataSet u mogućnost da pruži našoj aplikaciji podršku za rad sa ovim podacima. Za probu stavite na formu DataSource i DBGrid i DBNavigator komponente i povežite ih (DataSource.DataSet = ClientDataSet, DBGrid.DataSource = DataSource i DBNavigator.DataSource = DataSource). Pokrenite aplikaciju i videćete da preko kopmonente DBGrid možete upravljati podacima, kao što se vidi na slici 3.

Slika 3: Primer aplikacije


Sada ćemo dodati mogućnost aplikaciji da učita podatke sa lokalnog diska prilikom startovanja i da po potrebi snimi podatke na hard disk. Da biste snimili strukturu podataka za vreme projektovanja aplikacije iz pomoćnog menija komponente ClientDataSet izaberite jednu od opcija koja počinje sa Save To. Postoje 3 različita formata u koje se mogu snimati podaci i učitati iz datoteke. To su binarni format, xml format i xml-utf8. Izaberite format xml-utf8 i snimite datoteku sa nazivom aplikacije i ekstenzijom xml u direktoriumu gde vam se nalazi i program. Zatim dodajte jedno dugme na formu i dodajte sledeći kod:
procedure TForm1.bSaveClick(Sender: TObject);
begin
ClientDataSet1.SaveToFile(ClientDataSet1.FileName);
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
with ClientDataSet1 do
SaveToFile(FileName);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
with ClientDataSet1 do begin
FileName := ChangeFileExt(Application.ExeName, '.xml');
LoadFromFile(FileName);
end;
end;
Sada već imamo aplikaciju koja je potpuno funkcionalna, ali postoji i sigurniji način. Zamislite da se nekako ošteti datoteka sa podacima, naša aplikacija neće više raditi. Ili da ako želite da distribuirate dalje aplikaciju morali bi da delimo aplikaciju sa praznim podacima. Mnogo je bolje rešenje dinamički kreirati strukturu podataka koriščćenjem FieldDefs osobine, tako da ako se problem sa učitavanjem datoteke javi, možemo kreirati novu praznu datoteku. Dodajte sledeću proceduru u vaš kod,
procedure TForm1.CreateFieldDefs;
begin
with ClientDataSet1.FieldDefs do
begin
Clear;
with AddFieldDef do
begin
Name := 'ID';
DataType := ftAutoInc;
Attributes := [faReadonly];
end; //with AddFieldDef do
Add('Prezime', ftString, 30);
Add('Ime', ftString, 30);
Add('DatumRodjenja', ftDateTime);
Add('Fotografija', ftBlob);
Add('ElektronskaPosta', ftString, 250)
end;
ClientDataSet1.CreateDataSet;
end;
i izmenite sledeću proceduru da izgleda ovako:
procedure TForm1.FormCreate(Sender: TObject);
begin
with ClientDataSet1 do begin
FileName := ChangeFileExt(Application.ExeName, '.xml');
try
LoadFromFile(FileName);
except
if FileExists(FileName) then begin
ShowMessage('Doslo je do greske u datoteci!!!' + #13 + #10 + 'Podaci su sacuvani u .back datoteci');
RenameFile(FileName, ChangeFileExt(FileName, '.back'));
end;
CreateFieldDefs;
SaveToFile(FileName);
end;
end;
end;
Sada imamo potpuno funkcionalu aplikaciju. Sami možete implementirati dodavalje fotografije u istoimeno polje isto kao što biste uradili da je bilo koja druga baza u pitanju i bilo koja druga komponenta koja radi sa tabelama baze podataka.


Navigacija, pretraživanje i promena podataka


Kada posmatramo bilo koju bazu podataka, često se javlja potreba da se izvrši neka kalkulacija programski, gde će biti potrebno proći preko određenog skupa podataka, bilo da ćemo vršiti prolazak kroz sve podatke ili tražiti određeni podatak i promeniti određene podatke prema potrebi. U prethodnom primeru je upotrebljena komponenta DBGrid za prikaz, navigaciju i promenu podataka, što pri dovoljnoj količini podataka koji su ne sortirani i nije neka naročita olakšica. ClientDataSet vam pruya sledece mogucnosti u navigaciji podataka: First, Prior, Next, Last, MoveBy, i postavljanje vredenosti u osobinu RecNo, tako da možete dodati 3 komponente Button i jednu Edit komponentu i dodati sledeći kod:
procedure TForm1.bNext10Click(Sender: TObject);
begin
ClientDataSet1.MoveBy(10);
end;

procedure TForm1.bPrev10Click(Sender: TObject);
begin
ClientDataSet1.MoveBy(-10);
end;

procedure TForm1.bRecNoClick(Sender: TObject);
begin
try
ClientDataSet1.RecNo := StrToInt(eRecNo.Text);
except
ShowMessage('Ilegal RecNO');
end;
end;
Ostale metode se pozivaju analogno prikazanom, ali su već ugrađene u DBNavigator, tako da ih nećemo posebno implementirati. Zamislite sada situaciju da želite da se prikazana elektronska pošta formatira tako da podaci ne mogu sadržati velika slova. Nezgodno je kod tabela sa velikom brojem zapisa vršiti promene podataka prelaženjem preko svih zapisa kada su komponente za prikaz podataka vezane za njih, jer prikazivanje podataka često može trajati mnogo duže od samo lociranja podataka i njihove izmene. Dodajte sada na formi jednu komponentu Button i sledeći kod:
procedure TForm1.bFormatEPClick(Sender: TObject);
begin
with ClientDataSet1 do
try
DisableControls;
if not ClientDataSet1.Active then ClientDataSet1.Open;
First;
while not ClientDataSet1.EOF do
begin
Edit;
FieldByName('ElektronskaPosta').AsString := LowerCase(FieldByName('ElektronskaPosta').AsString);
Post;
Next;
end;
finally
EnableControls;
end;
end;
Komandama DisableControls i EnableControls omogućavamo da se operacije koje dugo traju izvršavaju brzo i bez prikazivanja u kontrolama, tako da operacija u našem slučaju traje veoma kratko i nad velikim brojem podataka. U ovome primeru smo koritili pretragu od prvoga ka zadnjem zapisu, a isto tako možemo koristiti i obrnuti redosled, gde bi kod izgledao:
...
ClientDataSet1.Last;
with
Recimo da ste završili aplikaciju i da želite da je podelite svojim prijateljima, ali biste isto želeli da im ostavite i svoje podatke. To možete uraditi u staru upisujući samo svoje podatke u xml datoteku, ali pošto smo napravili da se tabela sama genriše u slučaju loše datoteke, bilo bi zgodno da i automotski ubacimo naše podatke. Ovo možemo uraditi na sledeći način
Insert; //ili Append
FieldByName(‘Prezime’).asString := ‘Mikic’;
...
Post;
a postoji i alternativa tako da možemo ceo zapis ubaciti odjednom korišćenjem naredbe InsertRecord ili AppendRecord. Dodajte sledeći kod u program:
procedure TForm1.InsertMyData;
begin
ClientDataSet1.InsertRecord(['Mikic', 'Mika', EncodeDate(1970,12,12), null, 'Mika@Mikic.com', 'Ostali']);
end;
Obratite pažnju da vrednost za polje ID nije stavljena u InsertRecoed metodi. Ovo sam namerno izostavio iz razloga što smo dodelili atribut faReadonly polju ID, tako da operacija ne bi uspela u slučaju da smo prosledili neku vrednost. Metodi InsertRecord možemo prosledi onoliko vrednosti koliko želimo, sa time što će se te vrednosti upisivati redom kako su definisane u FieldDefs osobini, tako da morate voditi računa prilikom definisanja polja, kao i o tome da, ako postoje određeni atributi koji su definisani u FieldDefs osobini, budu zadovoljni.

Rad sa indeksima


Kao i kod svih komponenti koje su izvedene iz TDataSet klase postoje indeksi. Indeksi su bitni za rad sa podacima, jer direktno utiču na redosled prikazivanja podataka, olakšavaju pretrage, određivanje ograničenja, povezivanja DataSet naslednika, kao i otvaranje drugih mogućnosti koje programeri mogu koristiti u zavisnosti od potreba.
Nastavićemo sa doradom našeg malog primera. Dodajte sada u FieldDefs kolekciji još jedan podatak. Nazovite ga “Grupa”, postavite mu tip ftString i ostavite predefinisanu dužinu 20. Dodajte i proceduri CreateFieldDefs sledeću liniju:
Add('Grupa', ftString, 20);
odmah ispred definisanja polja “ID”. Izmenite liniju u proceduri InsertMyData, tako da izgleda:
ClientDataSet1.InsertRecord(['Mikic', 'Mika', EncodeDate(1970,12,12), null, 'Mika@Mikic.com', 'Ostali']);
Iz pomoćnog menija komponente DBGrid izaberite opciju Columns Editor. Otvoriće vam se kolekcija za definisanje kolona koje će se prikazivati u ovoj komponenti. Dodajte sve kolone klikom na dugme Add All Fields, tako da vam kolekcija izgleda kao na slici 4. Zatim za kolonu Grupa promenite osobinu PickList. Dodajte stavke, tako da izgledaju kao na slici 5.

Slika 4: Kolekcija Columns

Slika 5: PickList Editor


Proverite da li je osobina ButtonStyle za kolonu Grupa ima vrednost cbsAuto. Kompajlirajte sada program i pokrenite ga. Videćete da je dodata kolona Grupa na komponenti DBGrid.
Sada pretpostavimo da biste iz određenih razloga želeli da vam se podaci u aplikaciji sortiraju prema koloni Grupa. Postavite na komponenti ClientDataSet vrednost osobine IndexFieldNames da bude “Grupa”. Ako opet pokrenete aplikaciju videćete da su vam podaci sortirani prema datoj koloni. Možda biste sada želeli da možete sortirati podatke i prema drugim kolonama. Ovo je jednostavno uraditi. Dodajte na komponentu DBGrid događaj OnTitleClick i dodajte sledeći kod:
procedure TForm1.DBGrid1TitleClick(Column: TColumn);
begin
(Column.Grid.DataSource.DataSet as TClientDataSet).IndexFieldNames := Column.FieldName;
end;
Pokrenite aplikaciju videće promene. No ako biste poželeli da sortirate u opadajućem redsledu, ta neće moći da uradite na ovaj način. Da bi ovo bilo moguće mormo malo oozbiljnije da se pozabavimo indeksima. Iz pomoćnog menija na ClientDataSet komponenti odaberite Create DataSet. Pogledajte sada osobinu IndexName. Videće da postoje dva indeksa CHANGEINDEX i DEFAULT_ORDER. Otvorite sada kolekciju IndexDefs i pogledajte osobine koje može imati indeks. Dobićete prikaz kao na slikama 6 i 7. Sada možete dodati koje god indekse želite i možete im postaviti osobine kako želite. Sve što treba kasnije da uradite jeste da osobini IndexName na komponenti ClientDataSet postavite vrednost na ime vašeg indeksa.

Slika 6: Kolekcija IndexDefs

Slika 7: Osobine Indeksa


Bitno je istaći su osobine IndexFieldNames i vrednosti IndexName međusobno iskjlučive, tj da postavljanje jedne isključuje postojenje druge vrednosti. Bitno je još spomenuti da kada snimate podatke u neku od datoteka se ne snimaju podešavanja za indekse, tako da ih uvek morate posebno postaviti. Kao što se struktura podataka može kreirati za vreme izvršavanja aplikacije, takoo mogu i indeksi. Ma koliko god indeksa kreirali nećete narušiti performanse aplikacije, jer se ClientDataSet fizički pravi indekse tek kada se oni trebaju upotrebiti. Dodajte sada sledeći kod
procedure TForm1.CreateIndexDefs;
begin
with ClientDataSet1 do
begin
with IndexDefs.AddIndexDef do
begin
Name := 'IdxGrupaDesc';
Fields := 'Grupa';
Options := [ixDescending];
end;
IndexName := 'IdxGrupaDesc';
end;
end;
i dodajte poziv ove procedure kao zadnju naredbu u proceduri FormCreate. Videćete da je primenjen vaš indeks i da su podaci sortirani po koloni Grupa i to u opadajućem redu. Sada kada kliknete na bilo koje zaglavlja nad kontrolom DBGrid videćete da se poništava definisan indeks i da se podaci sortiraju u rastućem redosledu prema izabranoj koloni. Sada možete sami izmeniti proceduru DBGrid1TitleClick tako da se dinamički menja vaš definisan indeks, i da se ako je već vrši indeksiranje po datoj koloni da promeni redsled indeksiranja (iz rastućeg u opadajući i obrnuto).


Pregled


Prikazane su osnove rada sa komponentom kao što je ClientDataSet. Opisana mogućnost kreiranja lokalne baze, obješnjen način navigacije kroz podatke, pretrage i indeksiranja podataka. Objeašnjeno je kako se može za vreme projektovanja postaviti i ogranizovati aplikacija, kao i kako se može za vreme izvršavanja mogu odraditi navedene operacije. U sledećim člancima biće objašnjeno kako da ograničite prikaz podataka, pronađete podatke po odgovarajućim kriterijumima, kako da iskoristite mogućnosti agregacije koje pruža ova komponenta, rad sa kursorima podataka i mogućnost njihovog kloniranja i kako da sve ove mogućnosti na pravilan način iskoristite u jednom klijent server okruženju.

 

VRH STRANE

(c) 2003 OMEGA - sva prava zadržana