Sunteți pe pagina 1din 40

Indrumar Capitolul 4 LIMBAJE SI BIBLIOTECI DE PROGRAMARE A APLICATIILOR DE BAZE DE DATE

Sistemele de gestiune a bazelor de date relationale prelucreaza instructiuni (comenzi) SQL. Limbajul SQL este un limbaj neprocedural, care permite definirea datelor si operatii de manipulare a acestora, dar nu prevede instructiuni de control al ordinii de executie a operatiilor. De aceea, pentru realizarea aplicatiilor de baze de date au fost dezvoltate o multitudine de limba je si biblioteci de programare: limbaje procedurale de extensie a limbajului SQL, limbajul SQL integrat, biblioteci de functii sau de clase pentru comunicarea cu bazele de date. Un limbaj procedural ( procedural language) este o extensie a limbajului SQL si permite combinarea instructiunilor SQL cu instructiuni de control al ordinii de executie. Astfel de limbaje sunt folosite n principal n cadrul sistemelor de gestiune, pentru stocarea unor proceduri de calcul, dar exista si posibilitatea ca programele de aplicatii sa transmita sistemului SGBD blocuri (loturi) de instructiuni ntr-un limbaj procedural. Pentru dezvoltarea programelor de aplicatii de baze de date se pot aborda doua tehnologii diferite, limbajul SQL integrat ntr-un limbaj de nivel nalt sau interfete de programare a aplicatiilor. n limbajul SQL integrat (Embeded SQL), instructiunile limbajului SQL sunt incluse direct n codul programului sursa scris ntr-un limbaj gazda de nivel nalt (Ada, PL/1, Pascal, Fortran, Cobol, C). Controlul flu xului de operatii este realizat prin instructiunile limbajului gazda, iar operatiile cu baza de date sunt realizate prin instructiuni SQL. Interfetele de programare a aplicatiilor (Application Programming Interface - API) sunt dezvoltate ca biblioteci de functii sau de clase, iar programele de aplicatie folosesc apelul functiilor prevazute de interfata respectiva pentru a comunica cu serverul bazei de date.

4.1

LIMBAJE PROCEDURALE DE EXTENSIE A LIMBAJULUI SQL

Limbajele procedurale asigura controlul ordinii de executie (bucle while , instructiuni conditionale if, etc.) si, de asemenea, ofera suport de creare a cursoarelor, a procedurilor stocate , a functiilor definite de utilizator si a declansatorilor (triggere). Un cursor (cursor) este o structura care permite memorarea (folosind un buffer n memorie) a unei multimi de linii returnate ca rezultat de o instructiune de interogare. Programele de aplicatii nu pot prelucra deodata toate liniile rezultatului si folosesc cursoare pentru extragerea individuala a unei linii (sau a unui grup de linii) din multimea de linii rezultat. n fiecare moment, ntr-un cursor exista o pozitie curenta (linie curenta) n multimea de linii rezultat. La fiecare operatie de extragere, se citesc una sau mai multe linii relativ la pozitia curenta a cursorului, iar aceasta pozitie se actualizeaza conform modului de parcurgere (nainte sau napoi). Cursoarele se pot crea att la nivelul limbajului SQL2 sau a extensiilor procedurale ale acestuia, ct si prin intermediul limbajului SQL integrat (Embedded SQL) sau a bibliotecilor si interfetelor de programare a aplicatiilor de baze de date (ca, de exemplu, interfetele ODBC si JDBC). O procedura stocata (stored procedure) este o procedura care implementeaza o parte din algoritmii de calcul ai aplicatiilor si este memorata n baza de date la fel ca si alte obiecte ale bazei de date. Procedurile stocate sunt compilate si optimizate de sistemul de gestiune o singura data, atunci cnd sunt folosite prima oara si ramn memorate n server pentru oricte apeluri ulterioare. O functie definita de utilizator (user-defined function) este o functie memorata n baza de date, la fel ca o procedura stocata; diferenta ntre acestea este ca o functie returneaza ntotdeauna o valoare si poate fi folosita direct n expresii, pe cta vreme o procedura stocata poate sa nu returneze nici o valoare.

47

Un trigger este o procedura stocata cu o functionare speciala, care se declanseaza automat atunci cnd se efectueaza o operatie de actualizare a unei relatii. Prin triggere se pot specifica si impune procedural constrngerile explicite, cum sunt dependentele de date care nu sunt determinate de chei ale relatiilor. De asemenea, triggerele mai sunt folosite si pentru generarea automata a unor valori care rezulta din valori ale altor atribute, precum si pentru jurnalizarea transparenta a evenimentelor sau culegerea de date statistice n legatura cu accesarea relatiilor. Majoritatea sistemelor de gestiune sunt prevazute cu cel putin un limbaj procedural, cele mai cunoscute fiind: PL/SQL pentru sistemele de gestiune Oracle, Transact-SQL pentru sistemele de gestiune Microsoft SQL Server, PL/PGSQL si PL/Pearl pentru sistemul de gestiune PostgreSQL, etc.

4.1.1

LIMBAJUL TRANSACT-SQL

Programele Transact-SQL pot fi executate ca proceduri memorate n cadrul bazei de date sau ca loturi (batchs) de instructiuni transmise sistemului prin intermediul unei aplicatii sau a unui program utilitar de acces interactiv la baza de date. Programele utilitare d acces interactiv din e distributia SQL Server sunt isql sau osql (la nivel de linie de comanda) si SQL Query Analizer (care ofera si o interfata grafica). De asemenea, numeroase operatii de definire a tabelelor si a altor caracteristici ale bazelor de date se pot face din consola de administrare ( Enterprise Manager) a sistemului SQL Server. 4.1.1.1 Elementele de baza ale limbajului Transact-SQL Loturi de prelucrare. Un lot (batch) consta dintr-o secventa de instructiuni Transact-SQL terminata cu comanda GO. Exista mai multe reguli de grupare a instructiunilor n loturi. De exemplu: orice instructiune CREATE trebuie sa fie prima n lot; de aceea nu pot fi grupate n acelasi lot instructiunile CREATE TABLE, CREATE VIEW, CREATE TRIGGER , CREATE RULE, etc. Variabile locale. n limbajul Transact-SQL pot fi folosite variabile locale pentru memorarea unor valori care pot fi testate sau modificate (ca n orice alt limbaj) si, n plus, asigura transferul datelor catre si de la tabelele bazei de date. Variabilele locale au ca domeniu de definitie lotul, procedura sau blocul n care au fost declarate. O variabila locala se declara folosind instructiunea DECLARE care specifica un identificator (un nume care trebuie sa nceapa cu caracterul @) si tipul variabilei. Sintaxa de declarare arata astfel:
DECLARE @nume_variabila tip_date

Limbajul Transact-SQL suporta toate tipurile de date prevazute n standardul SQL2 si, n plus, permite definirea de catre utilizator a unor noi tipuri de date. Initializarea variabilelor locale se poate face printr-o comanda SELECT, cu urmatoarea sintaxa:
SELECT @nume_variabila = expresie

Instructiuni SQL. Limbajul Transact-SQL suporta toate instructiunile SQL, cu unele modificari de sintaxa, astfel nct sa poata fi folosite n combinatie cu variabilele locale ale programului. De exemplu, forma modificata a instructiunii SELECT , prin care se asigneaza variabile locale cu valori ale unor atribute selectate, este:
SELECT @var1 = col1, @var2 = col2, ... @varn = coln FROM lista_tabele WHERE conditie

O astfel de instructiune este utila pentru interogarile care returneaza o singura linie, deoarece variabilele locale sunt setate cu valorile coloanelor primei linii a rezultatului, iar valorile din celelalte linii se pierd (nemaifiind loc unde sa fie memorate). Atunci cnd o interogare returneaza o multime de linii se poate folosi un cursor, asa cum va fi prezentat n continuare. Instructiuni de control a ordinii de executie. Ordinea de executie a instructiunilor unui lot sau a unei proceduri stocate este controlata prin urmatoarele instructiunile de control:
BEGIN...END GOTO IF...ELSE RETURN WAITFOR WHILE BREAK CONTINUE

48

Semnificatia acestor instructiuni este cea generala, cunoscuta din alte limbaje de programare, cu anumite particularitati. Instructiunile de control nu pot depasi granitele unei lot de executie sau a unei proceduri stocate. 4.1.1.2 Cursoare Transact-SQL n limbajul Transact-SQL se pot defini cursoare cu urmatoarea instructiune:
DECLARE nume_cursor CURSOR [optiuni] FOR instructiune_select

Exista mai multe optiuni care se pot seta si care definesc tipul cursorului. Semnificatia acestor optiuni este descrisa n manualul sistemului (Books Online), iar n programul care urmeaza este dat un exemplu de creare si utilizare a unui cursor Transact-SQL. La deschiderea unui cursor (prin comanda OPEN) se executa interogarea respectiva (instructiunea SELECT) si rezultatul (multimea de linii) se ncarca n cursor. Dupa ncarcarea cursorului, se pot extrage liniile prin operatii de extragere cu instructiunea FETCH, a carei sintaxa simplificata arata astfel:
FETCH [[NEXT|PRIOR|FIRST|LAST|RELATIVE n|ABSOLUTE n] FROM] @nume_cursor [INTO @var1,@var2,...@varn] ]

Clauza INTO permite extragerea valorilor unei linii n variabilele locale. Asocierea dintre variabilele locale si coloanele cursorului este implicit pozitionala: valoarea din prima coloana se nscrie n prima variabila (@var1), valoarea din a doua coloana se nscrie n variabila a doua (@var2), etc. Ordinea coloanelor cursorului este ordinea coloanelor din instructiunea SELECT asociata cursorului. Pentru testarea starii unui cursor se apeleaza functia globala @@FETCH_STATUS, care raporteaza starea ultimei instructiuni FETCH executate pentru conexiunea curenta la server. Valoarea returnata este 0, daca extragerea a fost efectuata cu succes, 1, daca a survenit o eroare si 2, daca nu mai sunt date n multimea de linii rezultat. Dupa terminarea operatiilor asupra datelor cursorului, acesta se nchide cu comanda CLOSE, iar instructiunea DEALLOCATE sterge structurile de date ale cursorului si elibereaza memoria. Programul urmator foloseste un cursor pentru a afisa numele si prenumele angajatilor al caror identificator (IdAngajat) are valori cuprinse ntre 1 si numarul maxim de linii ale tabelului respectiv (tabelul ANGAJATI).
Cursor Transact-SQL DECLARE @id_angajat int, @nr_linii int DECLARE @nume char(20), @prenume char (20) DECLARE cursor_angajati CURSOR FOR SELECT IdAngajat, Nume, Prenume FROM ANGAJATI OPEN cursor_angajati -- Aflarea numarului de linii ale tabelului ANGAJATI SELECT @nr_linii = COUNT(*) FROM ANGAJATI PRINT 'Nr Linii: '+ RTRIM(CAST(@nr_linii AS char(10))) FETCH cursor_angajati INTO @id_angajat, @nume, @prenume WHILE (@@FETCH_STATUS = 0) BEGIN IF @id_angajat <= @nr_linii PRINT RTRIM(CAST(@id_angajat AS char(10)))+ ' '+@nume + @prenume FETCH cursor_angajati INTO @id_angajat, @nume, @prenume END CLOSE cursor_angajati DEALLOCATE cursor_angajati GO

La deschiderea cursorului (cursor_angajati), acesta se ncarca cu toate liniile tabelulu i ANGAJATI. Aceste linii sunt extrase una cte una, cu cte o comanda FETCH, care nscrie valorile atributelor din linia curenta a cursorului n variabilele locale ( @id_angajat,@nume,@prenume).
49

Pentru fiecare linie extrasa se testeaza valoarea atributului IdAngajat (retinuta n variabila locala @id_angajat) si se tiparesc datele (identificator, nume, prenume) din acele linii care au acest atribut mai mic sau egal cu numarul total de linii ale tabelului (nr_linii).

4.1.2

LIMBAJUL PL/SQL

Limbajul PL/SQL este limbajul procedural al sistemelor de gestiune a bazelor de date Oracle, care a fost introdus pentru prima oara n versiunea Oracle 6.0 n 1991 si a fost dezvoltat continuu si folosit intens, inclusiv n cele mai recente versiuni, Oracle9i si Oracle 10g. 4.1.2.1 Elementele de baza ale limbajului PL/SQL Blocuri PL/SQL. Unitatea de programare PL/SQL este blocul, care ofera posibilitatea de dezvoltare modulara a programelor. Exista patru tipuri de blocuri: blocuri anonime, proceduri stocate, triggere si functii definite de utilizator. Blocurile anonime pot fi executate imediat, de obicei dintr-un program utilitar interactiv, cum este SQL*Plus, si nu sunt memorate n baza de date. Acestea sunt echivalente loturilor de prelucrare din Transact-SQL. Celelalte blocuri (procedurile stocate, triggerele si functiile definite de utilizator) sunt memorate n baza de date. Un bloc anonim PL/SQL este compus din mai multe sectiuni si are urmatoarea structura:
[DECLARE variabile] BEGIN instructiuni_executabile [EXCEPTION rutine_tratare_exceptii] END;

n prima sectiune (optionala) se declara variabilele locale ale blocului folosind instructiunea DECLARE. Fiecare variabila se declara printr-un nume, urmat de tipul de date si de caracterul de ncheiere (punct si virgula). Optional, orice variabila poate fi initializata la declarare folosind operatorul de asignare (:=) sau poate avea o valoare implicita, stabilita folosind cuvntul cheie DEFAULT. Tipurile de date care pot fi folosite n limbajul PL/SQL sunt tipurile Oracle SQL, ca number, varchar2, char, etc. Tot n aceasta sectiune se pot declara cursoare, cu o sintaxa care va fi prezentata ulterior. Sectiunea principala a unui bloc PL/SQL este compusa din instructiuni executabile ncadrate de instructiunile BEGIN...END. Dintre instructiunile executabile, o parte sunt chiar instructiuni SQL de manipulare a datelor, cu o sintaxa modificata, astfel nct sa poata fi folosite n combinatie cu variabile locale ale programului. Alte instructiuni sunt instructiuni de control a executiei programulu i. n partea finala a acestei sectiuni se plaseaza (optional) si rutinele de tratare a exceptiilor. Instructiuni SQL. Limbajul PL/SQL nu admite dect instructiuni SQL de manipulare a datelor. Dintre acestea, instructiunea SELECT se poate folosi sub urmatoarea forma generala:
SELECT lista_coloane INTO lista_variabile FROM lista_tabele [WHERE conditie] [optiuni]

Desi clauza WHERE este optionala, ea este necesara pentru a se asigura ca rezultatul are o singura linie, iar valorile din coloanele selectate sunt atribuite (prin corespondenta pozitionala) variabilelor din lista de variabile introdusa dupa cuvntul cheie INTO. Instructiunea SELECT prezentata n continuare asigneaza variabilelor m_nume si m_prenume valorile atributelor (coloanelor) Nume si Prenume din linia care are valoarea cheii primare IdAngajat egala cu variabila locala id_angajat:
SELECT Nume,Prenume INTO m_nume, m_prenume FROM ANGAJATI WHERE IdAngajat = id_angajat;

Instructiunile SQL INSERT si UPDATE se pot integra cu usurinta n limbajul PL/SQL si pot folosi variabile locale pentru asignarea valorilor atributelor. De exemplu:
id_sngajat:= 2; m_nume:= Ionescu; m_prenume:= Ion; INSERT INTO ANGAJATI (IdAngajat,Nume,Prenume) VALUES (id_angajat,m_nume,m_prenume); m_adresa := Bacau UPDATE ANGAJATI SET Adresa = m_adresa WHERE IdAngajat = id_angajat; 50

O precautie speciala trebuie sa fie luata de programatori pentru a evita ca variabilele locale si coloanele din tabele sa aiba aceleasi nume, deoarece astfel de situatii sunt admise ca legale de catre compilatorul PL/SQL, dar produc rezultate ambigue. Instructiuni de control al executiei. Ca si n alte limbaje procedurale, ordinea de executie a instructiunilor unui program PL/SQL poate fi controlata prin mai multe instructiuni de control:
IF conditie THEN ...[ELSE ...] END IF; CASE FOR conditie LOOP WHILE conditie LOOP LOOP ... EXIT WHEN ... conditie

Semnificatia acestor instructiuni este asemanatoare cu cea din limbajele de programare cunoscute, cu unele aspecte mai deosebite care vor fi prezentate pe scurt n continuare. n toate aceste instructiuni conditia care se testeaza este o expresie care se evalueaza la o valoare de tip Boolean (TRUE sau FALSE). Instructiunea IF...THEN permite executia conditionata a unor instructiuni. Daca conditia este TRUE, se executa toate instructiunile dintre cuvintele cheie THEN si ELSE sau ntre THEN si END IF. Clauza ELSE este optionala si ea este urmata de instructiunile care trebuie sa fie executate daca valoarea conditiei este FALSE:
IF x > 100 THEN x := 100; ELSE y := x; END IF;

Instructiunile FOR...LOOP, WHILE...LOOP si LOOP...WHEN se folosesc pentru executia repetata a unui grup de instructiuni. De exemplu, instructiunea FOR...LOOP poate fi folosita pentru executia repetata a grupului de instructiuni cuprins ntre cuvintele cheie LOOP si END LOOP atta timp ct contorul (care este o variabila de tip ntreg, locala acestei constructii si declarata automat) este cuprins ntre doua valori (minima si maxima); la fiecare iteratie contorul este incrementat automat cu 1 (sau decrementat cu 1, daca este introdusa optiunea REVERSE). Aceasta instructiune are sintaxa:
FOR contor IN [REVERSE] valoare_minima .. valoare_maxima LOOP instructiuni; END LOOP;

Instructiunea LOOP creaza o bucla infinita din care se poate iesi cu instructiunea EXIT, care poate fi invocata fie ntr-un bloc de conditie IF , fie cu clauza WHEN. Forma generala a instructiunii LOOP este:
LOOP instructiuni [IF conditie THEN EXIT | EXIT WHEN conditie] ; instructiuni; END LOOP

Tratarea exceptiilor. Partea finala (optionala) a unui bloc PL/SQL este sectiunea EXCEPTION care contine una sau mai multe rutine de tratare a exceptiilor. Tratarea exceptiilor n limbajul PL/SQL este asemanatoare cea din limbajele C++ si Java: atunci cnd apare o conditie de eroare, executia normala a programului se opreste si controlul este pasat rutinei (handler) de tratare a exceptiei. Ca si n C++ (si spre deosebire de Java), tratarea exceptiilor este optionala n PL/SQL. 4.1.2.2 Cursoare PL/SQL n limbajul PL/SQL un cursor se declara n sectiunea DECLARE a unui bloc. Forma cea mai simpla a instructiunii de declarare a unui cursor este:
CURSOR nume_cursor IS instructiune_select;

51

Extragerea liniilor cursorului se realizeaza cu instructiunea:


FETCH nume_cursor INTO lista_variabile;

Ca exemplu, n programul urmator se defineste un cursor PL/SQL cu o functionare asemanatoare programului precedent (afiseaza numele si prenumele angajatilor care au identificatorul ( cuprins ntre 1 si numarul total de linii ale tabelului ANGAJATI).
Cursor PL/SQL SET SERVEROUTPUT ON DECLARE CURSOR cursor_angajati IS SELECT IdAngajat, Nume, Prenume FROM ANGAJATI; id_angajat number; nr_linii number; m_nume varchar2(20); m_prenume varchar2(20); BEGIN SELECT COUNT(*) INTO nr_linii FROM ANGAJATI; DBMS_OUTPUT.PUT_LINE('Nr. Linii: ' ||nr_linii); OPEN cursor_angajati; LOOP FETCH cursor_angajati INTO id_angajat, m_nume, m_prenume; IF cursor_angajati%NOTFOUND THEN EXIT; END IF; IF id_angajat <= nr_linii THEN DBMS_OUTPUT.PUT_LINE(id_angajat||' '||m_nume||' '|| m_prenume); END IF; END LOOP; CLOSE cursor_angajati; END;

Pentru aflarea starii unui cursor se testeaza atributul acestuia %FOUND, care este setat dupa fiecare operatie de FETCH, la valoarea TRUE, daca a fost extrasa o linie si cu valoarea FALSE daca nu s-a putut extrage nici o linie. Alternativ, se poate folosi atributul %NOTFOUND , care are valoarea TRUE atunci cnd operatiunea FETCH a esuat.

4.2

INTERFETE DE PROGRAMARE A APLICATIILOR DE BAZE DE DATE

Interfetele de programare a aplicatiilor (API) reprezinta cea mai cunoscuta tehnica de dezvoltare a aplicatiilor de baze de date, fiind mult mai utilizata dect tehnica de programare n limbajul SQL integrat, deoarece programele rezultate sunt mai flexibile si mai usor de dezvoltat si de ntretinut. Ca interfete de programare a bazelor de date, exista att interfete specifice, oferite de diferite sisteme SGBD, ct si interfete cu un grad mare de generalitate, care pot fi folosite pentru mai multe tipuri de sisteme SGBD, cum sunt interfetele ODBC (Open Database Connectivity ) sau JDBC (Java Database Connectivity ).

4.2.1

INTERFATA ODBC

Tehnologia ODBC (Open Database Connectivity) ofera o interfata de acces la baze de date relationale independenta de sistemul SGBD folosit. Aceasta independenta se obtine prin intermediul unor drivere, care sunt specifice fiecarui SGBD, n timp ce functiile de conectare si interogare a bazei de date folosite n programul aplicatie sunt aceleasi, definite de standardul ODBC. Administratorul de drivere ncarca drverul specific sursei de date folosite de aplicatie. O sursa de date stabileste un nume al bazei de date, mpreuna cu alte informatii (tipul sistemului SGBD, utilizatorul, etc.). Pentru a folosi interfata ODBC, trebuie sa fie instalat driverul pentru sistemul SGBD necesar. Unele drivere ODBC se instaleaza (daca se selecteaza optiunea respectiva) atunci cnd se instaleaza unele sisteme de programe, iar altele trebuie sa fie instalate separat. Pentru orice tip de SGBD pentru

52

care este instalat un driver ODBC se poate defini o sursa de date care atribuie un nume (si alte optiuni, depinznd de driverul instalat) unei baze de date de acel tip. Pentru dezvoltarea unei aplicatii de baze de date cu interfata ODBC se parcurg urmatorii pasi: Se creaza mai nti baza de date dorita (sau se foloseste o baza de date existenta). Se defineste sursa de date ODBC, corespunzatoare bazei de date. Se dezvolta aplicatia client care va accesa baza de date ca sursa ODBC nregistrata.

4.2.1.1 Definirea surselor ODBC Pentru nregistrarea unei baze de date ca sursa de date ODBC (Data Source Name - DSN) se foloseste administratorul ODBC n felul urmator: n Control Panel, se selecteaza comanda Data Sources (ODBC) din grupul de comenzi Administrative Tools; n caseta de dialog ODBC Data Source Administrator se selecteaza pagina System DSN. n aceasta pagina se selecteaza comanda Add, pentru adaugarea unei noi surse de date. n caseta de dialog Add Data Sources, se alege driverul ODBC corespunzator (Microsoft Access, SQL Server, MySQL, Oracle, etc.). Din acest punct, nregistararea sursei de date depinde de tipul driverului de baze de date. Pentru Oracle, se selecteaza tipul driverului Oracle in OraHome 92 si se stabileste numele sursei. Pentru serviciul de nume (TNS Service Name) se introduce denumirea acestuia asa cum a fost stabilit sa instalarea serverului si acest serviciu trebuie sa fie specificat n client. Daca instalarea a fost facuta corect, atunci n fisierul tnsnames.ora din client (ora92\tnsnames.ora) trebuie sa existe o intrare care contine exact datele serviciului de nume afisate de programul Net Manager din server. n laborator, serviciul de nume are denumirea server12.windowsnt.tech.pub.ro, si se conecteaza la serviciu l Oracle (server12.tech.pub.ro) pe portul 1521. Daca, la definirea sursei ODBC pentru Oracle testul de conexiune esueaza, atunci se poate adauga manual aceasta ntrare n fisierul tnsnames.ora. La definirea sursei se stabileste, de asemenea si numele utilizatorului (user ID). Pentru sistemul SQL Server la definirea unei surse de date ODBC se precizeaza numele serverului, modul de autentificare, contul utilizator si parola acestuia. Pentru Microsoft Access, n cmpul Data Source Name din caseta de dialog ODBC Setup se scrie numele sursei (de exemplu IntreprindereAccess), iar n grupul Database, alegerea optiunii Select permite localizarea pe disc a fisierului INTREPRINDERE.mdb creat cu Microsoft Access. Pentru MySQL, se completeaza mai multe informatii de configurare a sursei de date: Windows DNS Name (numele sursei, cu care va fi accesata din programul client); numele bazei de date, al utilizatorului, parola si portul (daca nu este portul implicit al serverului MySQL, 3306). Numele surselor de date se vor stabili diferit pentru fiecare student si pentru fiecare sistem de gestiune n parte. De exemplu, daca studentii au conturile user1, user2, etc, atunci se pot crea sursele de date ODBC cu numele User1Oracle, User1SQLServer, User1MySQL, User2Oracle, User2SQLServer, User2MySQL etc. Daca utilizatorul sistemului de operare Windows are drepturi restrictionate (asa cum este n laborator) atunci sursele ODBC trebuie sa fie create din alt utilizator cu drepturi de Administrator. 4.2.1.2 Dezvoltarea programului client ODBC n biblioteca ODBC sunt definite mai multe tipuri de date. Exista tipuri de date pentru numere (de exemplu SQLSMALLINT, care este un numar ntreg pe 16 biti), pentru tipul returnat de functii (SQLRETURN) pentru tipuri de date pentru de identidficatori (handle) (SQLHANDLE). La rndul lui, identificatorii SQLHANDLE pot avea mai forme:
HENV - identificator al mediului n care se desfasoara operatiile cu sursa de date ODBC. HDBC - identificator al unei conexiuni cu o sursa de date. HINST - identificator al unei instructiuni SQL.

53

ntr-un program pentru interfata ODBC trebuie sa fie definiti astfel de identificatori (de mediu, de conectare si de instructiune) si fiecare identificator trebuie sa fie alocat, operatie care se efectueaza prin apelul functiei SQLAllocHandle():
SQLRETURN SQLAllocHandle( SQLSMALLINT HandleType, SQLHANDLE InputHandle,SQLHANDLE *OutputHandlePtr);

Primul argument al acestei functii (HandleType) este o valoare care indica tipul identificatorului care se va aloca si poate lua una din valorile SQL_HANDLE_ENV, SQL_HANDLE_DBC sau SQL_HANDLE_STMT, pentru alocare a unui identificator de mediu, de conexiune sau de instructiune. Cel de-al doilea argument al functiei este identificatorul de intrare (InputHandle) care trebuie sa fie NULL (pentru alocarea unui identificator de mediu), de tipul HENV (pentru alocarea unui identificator de conexiune) sau de tip HDBC (pentru alocarea unui identificator de instructiune). Cel deal treilea argument (OutputHandlePtr) este un pointer care indica locul unde functia va depune identificatorul alocat. Pentru un identificatior (handle ) alocat corect, functia returneaza unul din codurile SQL_SUCCESS sau SQL_SUCCESS_WITH_INFO. n caz de eroare, functia returneaza unul din codurile SQL_INVALID_HANDLE sau SQL_ERROR. Mai nti se aloca un identificator (handle ) de mediu (henv), si apoi se seteaza atributele acestuia cu comanda SQLSetEnvAttr(). Dupa aceasta, n mediul ODBC definit, se aloca un identificator de conexiune ( hdbc), si se ncearca conectarea la sursa d date prin apelul functiei e SQLConnect(). Daca conectarea reuseste, pentru conexiunea respectiva se aloca un identificator de instructiune (hstmt), dupa care pot fi executate instructiuni (comenzi) SQL. O instructiune SQL se construieste ca un sir de caractere care contine o comanda SQL si parametrii acesteia. Interfata ODBC ofera doua moduri de construire a instructiunilor SQL: instructiuni SQL statice, care sunt cunoscute la compilare, sunt codate n programul executabil si executa ntotdeauna aceleasi operatii si instructiuni SQL dinamice, care se construiesc n timpul executiei programului si contin date care nu sunt cunoscute la compilare. Dupa ce au fost construite ntr-una din aceste doua forme, instructiunile SQL sunt transmise ca argumente unor functii ODBC care le executa si returneaza rezultatul (daca exista). Instructiunile SQL statice pot fi executate direct, folosind functia SQLExecDirect(), sau pot fi mai nti pregatite cu functia SQLPrepare() si apoi executate, posibil de mai multe ori, cu functia SQLExecute(). n general, este mai eficient ca o instructiune SQL sa fie mai nti pregatita si apoi executata, daca instructiunea respectiva se executa de mai multe ori. Prototipurile functiilor de executie a instructiunilor SQL statice sunt:
SQLRETURN SQLExecDirect(SQLHSTMT hstmt, SQLCHAR *StatementText,SQLINTEGER TextLength); SQLRETURN SQLPrepare(SQLHSTMT hstmt, SQLCHAR *StatementText,SQLINTEGER TextLength); SQLRETURN SQLExecute(SQLHSTMT hstmt);

Functiile SQLExecDirect() si SQLPrepare() au ca parametri identificatorul instructiunii ODBC (hstmt), variabila (sir de caractere) care contine instructiunea SQL ce trebuie sa fie executata sau pregatita (StatementText) si lungimea acestui sir de caractere (TextLength), care poate lua si valoarea SQL_NTS, pentru sirurile de caractere terminate cu NULL. Functia SQLExecute() are un singur parametru, un handle al instructiunii ODBC (hstmt). n Programul 4.12 care se afla n directorul acestui capitol se pot remarca cele doua moduri de executie a instructiunilor SQL statice. Prima instructiune (UPDATE) este executata folosind functia SQLExecDirect(); cea de -a doua instructiune (SELECT) este pregatita cu functia SQLPrepare() si apoi executata cu functia SQLExecute(). Acest program este un proiect MSVC 6.0, de tipul Win32 Console Application si permite conectarea la o sursa de date ODBC cu un nume dat. Pentru conectarea la o sursa de date MS access este suficient numele sursei; pentru conectarea la surse Oracle, SQL Server sau MySQL mai este necesar numele unui cont si parola de conectare. De asemenea, pentru SQL Server trebuie ca baza de date a sursei ODBC sa fie baza de date implicita a acelui cont. Programul asigura executarea diferitelor instructiuni SQL, iar pentru o instructiune SELECT care returneaza o multime de linii, se extrag liniile cu functia SQLFetch() si se afiseaza valoarea
54

fiecarui atribut al fiecarei linii folosind functia SQLGetData(). n aceasta situatie, multimea de linii rezultat returnate reprezinta un cursor, care este creat implicit de driverul ODBC. De aceea, n aplicatiile ODBC, foarte rar se definesc n mod explicit cursoare (desi este posibil acest lucru) si se folosesc cursoarele implicite create de driver.

4.2.2

DEZVOLTAREA APLICATIILOR DE BAZE DE DATE FOLOSIND BIBLIOTECA MFC

Biblioteca MFC (Microsoft Foundation Class) face parte din sistemul de dezvoltare MSVC (Microsoft Visual C++) si este o biblioteca C++ de dezvoltare a aplicatiilor n sistemele de operare Windows. Interfata ODBC este o interfata de functii C (deci nu este orientata pe obiecte), iar biblioteca MFC ofera o extensie obiect-orientata a interfetei ODBC. Biblioteca MFC ncapsuleaza functiile ODBC, grupndu-le n mai multe clase prin care se realizeaza operatiile de comunicatie cu baza de date ntr-un mod mai structurat si, desigur, mai simplu. Folosind interfata ODBC si clasele MFC ODBC, poate fi accesata orice sursa de date, locala sau la distanta, pentru care s-a definit o sursa ODBC. n aceasta lucrare se va dezvolta o aplicatie de acces la o baza de date folosind interfata ODBC prin intermediul bibliotecii MFC. Aplicatia se refera la baza de date INTREPRINDERE care a fost nregistrata ca sursa ODBC cu numele Intreprindere. Aplicatia MFC de baze de date se va crea n trei pasi de dezvoltare: Crearea unei aplicatii minimale cu suport pentru baze de date. Adaugarea unei interogari parametrizate. Extinderea intefetei pentru adaugarea, actualizarea si stergerea nregistrarilor. 4.2.2.1 Crearea unei aplicatii minimale cu suport pentru baze de date Clasele MFC care se folosesc n aplicatiile de baze de date prin interfata ODBC sunt:
CDatabase, CRecordset, CRecordView. Clasa CDatabase specifica conexiunea la o baza de date

prin intermediul numelui DSN al acesteia, nregistrat n ODBC. Un obiect de tip CRecordset reprezinta un set de nregistrari selectate dintr-o sursa de date. Obiectul de tipul CRecordset contine o selectie a nregistrarilor dintr-unul sau mai multe tabele ale bazei de date, proiectate pe una sau mai multe coloane. Obiectul vedere asociat acestor nregistrari (de tip CRecordView) corespunde unei vederi care este un formular si permite nglobarea controalelor de editare sau de afisare. Un astfel de obiect are atasata o resursa de tip dialog, care permite afisarea sau/si editarea cmpurilor unui set de nregistrari. Un obiect de tip CRecordView este asociat att cu un obiect de tip CRecordset ct si cu resursa de dialog asociata. Clasa CRecordset ofera suport pentru navigarea prin seturile de nregistrari, folosind comenzile Move First, Move Next, Move Previous si Move Last , utiliznd un cursor, care este actualizat n mod corespuunzator de aceste comenzi. Cnd este modificata valoarea cursorului si se trece la alta nregistrare, valoarea din cmpul corespunzator este actualizata automat. La crearea unei aplicatii se va specifica o sursa de date ODBC si un tabel din baza de date corespunzatoare. AppWizard va crea o pereche de clase: o clasa pentru setul de nregistrari si o clasa vedere pentru vizualizarea acestora, n urmatorii pasi: Se creaza un proiect SDI numit Angajati. n caseta de dialog AppWizard-Step2 se selecteaza optiunea Database View Without File Suport; se apeleaza Data Source; n caseta de dialog Database Options se selecteaza ODBC, iar din lista se selecteaza Intreprindere; n caseta de dialog Select Database Tables se selecteaza tabelul ANGAJATI. Dupa ce AppWizard a creat proiectul Angajati , se poate folosi ClassView pentru a vedea o reprezentare grafica a claselor si a componentelor acestora, create de AppWizard n mod implicit. De asemenea, cu ClassWizard se pot vedea legaturile create de AppWizard ntre variabilele membru si cmpurile tabelului ANGAJATI. Clasa pentru setul de nregistrari CAangajatiSet. AppWizard a legat toate coloanele tabelului Angajati de variabilele membru ale clasei CAngajatiSet. Acestea se numesc "variabile membru corespunzatoare cmpurilor" (field data members). AppWizard le denumeste n mod automat,
55

pe baza numelor coloanelor din tabelul ANGAJATI. De asemenea, AppWizard atribuie tipul corect de date C++ sau MFC pentru aceste variabile membru, n functie de tipul coloanei. Clasa vedere CAngajatiView. Functia membru a clasei de baza CRecordView:: OnInitialUpdate()deschide baza de date daca aceasta nu era deja deschisa, apoi deschide setul de nregistrari si initializeaza formularul, apelnd functia CFormView::OnInitialUpdate(). Clasa document CAngajatiDoc. n alte tipuri de aplicatii, documentul pastreaza datele si le stocheaza ntr-un fisier pe disc prin serializare. ntr-o aplicatie pentru baze de date, datele sunt stocate n baza de date, iar utilizatorul le vede ca nregistrari. O astfel de aplicatie nu are nevoie de un fisier de stocare. Prin urmare, un document al unei aplicatii pentru baze de date nu este folosit ca suport pentru serializare. Rolul clasei document n aplicatia SA este acela de a pastra setul de nregistrari.
class CAngajatiDoc : public Cdocument{ public: CAngajatiSet m_angajatiSet;.... }

Obiectul de tip set de nregistrari m_angajatiSet, este nglobat n obiectul document. De aceea, obiectul set de nregistrari este construit si eliminat automat odata cu obiectul document. Clasa document poate pastra un numar orict de mare de obiecte de tip set de nregistrari. Alegerea optiunii Database without file support (baza de date fara suport pentru fisiere) face ca n meniul File sa nu mai existe comenzile New, Open, Save sau Save as. Pe lnga aceste clase, AppWizard a creat si o resursa de tip dialog, numita IDD_ANGAJATI_FORM, pe care clasa CAngajatiView, derivata din CRecordView, o foloseste pentru a afisa controalele de editare sau de afisare a datelor. Deoarece CRecordView este derivata din CFormView, zona client a unei vederi din clasa CRecordView este ocupata de o resursa de dialog. Se configureaza aceasta resursa de dialog adaugnd sapte controale de editare corespunzatoare coloanelor tabelului ANGAJATI : IdAngajat, Nume, Prenume,..Salariu,IdSectie si controalele statice corespunzatoare. O regula des folosita la proiectarea interfetelor cu utilizatorul a aplicatiilor pentru baze de date este aceea de a nu i se permite utilizatorului sa modifice cmpurile care fac parte din cheia primara sau dintr-o cheie straina, pentru a nu viola constrngerile implicite ale bazei de date, ti de aceea se protejeaza la scriere controalele de editare Id si IdComp, selectndu-se caseta de validare Read Only din pagina Styles a ferestrei de proprietati. n mod normal, se foloseste ClassWizard pentru a lega controalele dintr-o caseta de dialog (sau dintr-un formular) de variabilele membru ale clasei derivate din CDialog (sau CFormView). Totusi, n cazul clasei CRecordView , nu se leaga controalele formularului de datele membru ale acestei clase, ci de datele membru ale clasei pentru setul de nregistrari. Clasa derivata din CRecordView, n acest caz CAngajatiView, are o variabila membru numita m_pSet care este un pointer la CAngajatiSet, clasa pentru setul de nregistrari a aplicatiei. Legaturile ce pornesc de la controalele formularului ajung, prin intermediul variabilei m_pSet , la variabilele membru corespunzatoare ale clasei CAngajatiSet. n fereastra editorului de resurse dialog, tinnd tasta Ctrl apasata, executarea unui dublu-clic pe un controlul de editare conduce la aparitia casetei de dialog Add Member Variable din ClassWizard, propunndu-va un nume n cmpul Member Variable Name. Din caseta combobox se poate alege numele coloanei dorite. Se repeta aceasta operatie pentru fiecare din celelalte controale de editare ale formularului. La executia aplicatiei se deschide setul de nregistrari de tip CAngajatiSet, care va selecta nregistrarile din tabelul ANGAJATI al bazei de date INTREPRINDERE. Prima nregistrare devine cea curenta. Formularul aplicatiei afiseaza n controale valorile din nregistrarea curenta. Meniul Record contine comenzile First Record, Previous Record, Next Record si Last Record, carora le corespund butoane din bara cu instrumente, care pot fi folosite pentru parcurgerea liniilor tabelului ANGAJATI (continute n obiectul din obiectul CAngajatiSet). 4.2.2.2 Crearea unei interogari parametrizate Desi cu AppWizard se creaza initial o aplicatie care are numai o clasa CRecordset si o clasa vedere CRecordView, ulterior se poate folosi ClassWizard pentru a adauga mai multe astfel de clase. Diferite clase vedere pot "vedea" acelasi set de nregistrari si invers, mai multe seturi de nregistrari pot fi "vazute" de aceeasi clasa vedere, dintre care unul singur este setul primar de nregistrari.

56

Pasul al doilea al aplicatiei (Step 2) ilustreaza folosirea a doua seturi de nregistrari ntr-o singura vedere, pentru realizarea unei interogari parametrizate. Se va adauga un obiect set de nregistrari pentru tabelul SECTII, care va fi folosit pentru completarea unei liste din care sa se poata selecta o anumita sectie. Astfel, clasa CAngajatiView va avea o asociere cu obiectul CAngajatiSet (vederea va afisa o nregistrare din CAngajatiSet), n timp ce lista sectiilor este asociata celui de-al doilea set de nregistrari, CSectiiSet. n clasa CAngajatiSet vor fi selectati numai acei angajati care apartin unei anumite sectii, n loc sa fie selectati toti angajatii, ai tuturor sectiilor. Interogarea corespunzatoare acestei functionari este:
SELECT IdSectie,Nume,Prenume,Salariul FROM SECTII, ANGAJATI WHERE SECTII.IdSectie=ANGAJATI.IdAngajat AND SECTII.Nume = nume_selectat;

Pentru a obtine acesta functionare se renunta la controlul de editare IdSectie (si la controalul static corespunzator) si se adauga o caseta combo-box cu numele Sectii, care va fi completata cu toate numele sectiilor existente n ntreprinderea respectiva. Cnd utilizatorul selecteaza un nume de sectie n caseta, se cauta identificatorul acestuia (IdSectie) n tabelul SECTII si va fi reinterogat tabelul ANGAJATI pentru a selecta numai angajatii din sectia aleasa. Operatiile care se vor efectua pentru a realiza aceasta functionare sunt urmatoarele: a) Crearea unei clase pentru setul de nregistrari din tabelul SECTII. Aplicatia are deja o clasa pentru setul de nregistrari din tabelul ANGAJATI, care completeaza controalele de editare ale vederii CAngajatiView cu informatii despre un angajat. Se va adauga o noua clasa pentru setul de nregistrari din tabelul SECTII , care va fi utilizata pentru a completa lista tuturor sectiilor ntreprinderii. Cu ClassWizard se creaza o noua clasa CSectiiSet derivata din clasa de baza CRecordSet. Se deselecteaza caseta de validare Add to Component Gallery. n caseta de dialog Database Options, din lista ODBC se selecteaza INTREPRINDERE. n caseta de dialog Select Database Tables se selecteaza tabelul SECTII. Astfel s-a conectat numele tabelului SECTII la clasa CSectiiSet. b) nglobarea obiectului set de nregistrari n obiectul document. n fisierul AngajatiDoc.h se adauga un obiect de tip CSectiiSet (public: CSectiiSet m_sectiiSet;) De asemenea, se adauga directiva #include "SectiiSet.h" n fisierele AngajatiDoc.cpp, Angajati.cpp si n AngajatiView.cpp nainte de directiva #include
AngajatiDoc.h.

c) nlocuirea controlului de editare IdSectii cu caseta combo-box Sectii. n resursa dialog IDD_ANGAJATI_FORM se sterge controalul de editare IdSectie si se adauga n locul lui o caseta combo-box, pentru care se alege n lista Type din pagina de proprietati Styles optiunea DropList. n clasa CAngajatiView se adauga variabila membru m_ctrlSectie de categorie Control care se asociaza cu caseta combo-box. d) nscrierea n caseta combo -box a listei numelor sectiilor. Locul cel mai potrivit pentru nscrierea n caseta combo-box a listei numelor sectiilor este functia virtuala redefinita OnInitialUpdate() din clasa CangajatiView.. Vederea completeaza lista din caseta n cadrul procesului sau de initializare. Pentru aceasta, n functia OnInitialUpdate() se construieste si se deschide obiectul set de nregistrari CSectiiSet , se sterge continutul curent al casetei, se introduce fiecare nume de compozitor n lista, se fixeaza selectia curenta la primul nume din lista (n ordinea sortarii). Codul care se adauga prin procedura urmatoare completeaza caseta si, totodata, filtreaza, parametrizeaza si sorteaza setul de nregistrari CAngajatiSet.
void CAngajatiView::OnInitialUpdate(){ CAngajatiDoc* pDoc = GetDocument(); m_pSet = &pDoc->m_angajatiSet; m_pSectiiSet = &pDoc->m_sectiiSet; if(!m_pSectiiSet->Open())return; m_pSet->m_strFilter = "IdSectie = ?"; 57

m_pSet->m_IdSectieParam = m_pSectiiSet->m_IdSectie; m_pSet->m_pDatabase = pDoc->m_sectiiSet.m_pDatabase; CRecordView::OnInitialUpdate(); m_ctrSectie.ResetContent(); if (m_pSectiiSet->IsOpen()){ while (!m_pSectiiSet->IsEOF()){ m_ctrSectie.AddString(m_pSectiiSet->m_Nume); m_pSectiiSet->MoveNext(); } } m_ctrSectie.SetCurSel(0); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); }

f) Introducerea filtrului parametrizat.. Filtrele unui set de nregistrari determina submultimea de nregistrari selectate dintr-un tabel sau dintr-o interogare. Acestea folosesc parametri, reprezentati prin semnul "?", n loc de valori literale atribuite la compilare. Aplicatia reselecteaza, sau "reinterogheaza" nregistrarile din tabelul ANGAJATI , ori de cte ori utilizatorul selecteaza un alt nume de sectie din caseta combinata. O modalitate de reinterogare a setului de nregistrari este "parametrizarea" filtrului, adica apelarea functiei Requery() cu o noua valoare a parametrului filtrului. Pentru aceasta se adauga n clasa CAngjatiSet o variabila membru pentru parametru (CString m_IDSectieParam;) si se initializeaza aceasta variabila si numarul de parametri n constructorul clasei CAngajatiSet: m_nParams=1; m_IDSectiiParam=0; De asemenea, se competeaza functia DoFieldExchange()cu urmatoarele linii de cod pentru a identifica parametrul m_strIDCompParam:
pFX->SetFieldType(CFieldExchange::param); RFX_Long(pFX,"IDSectiiParam",m_IDSectiiParam);

Functia DoFieldExchange()recunoaste doua tipuri de cmpuri: coloane si parametri. Apelnd functia SetFieldType()membru al clasei CFieldExchange, ea afla ce tip de cmp (cmpuri) urmeaza n apelul (apelurile) functiei RFX. Semnul "?" din specificatia filtrului parametrizat indica locul unde va fi substituita valoarea parametrului la rulare. Pentru a furniza valoarea parametrului la rulare se atribuie aceasta valoare variabilei membru parametru a setului de nregistrari n metoda OnInitialUpdate() a clasei CangajatiSet.
m_pSet->m_strFilter = "IdSectie = ?"; m_pSet->m_IdSectieParam = m_pSectiiSet->m_IdSectie;

n felul acesta, valoarea parametrului va fi prima valoare a cmpului IdSectii gasita n setul de nregistrari CSectiiSet. Att AppWizard, ct si ClassWizard implementeaza clasele derivate din CRecordSet astfel nct obiectul set de nregistrari sa aiba ca variabila membru un pointer la obiectul baza de date, de tip CDatabase, prin intermediul caruia sa fie conectat la sursa de date. Implementarea implicita a functiei CRecordView:: OnInitialUpdate()apeleaza indirect functia:
CString CAngajatiSet::GetDefaultConnect(){ return _T("ODBC;DSN=Intreprindere"); }

Cadrul-aplicatie transmite acest "sir de caractere de conectare" functiei CDatabase::Open() pentru obiectul de tip CDatabase creat de cadrul-aplicatie la implementarea functiei CRecordSet::Open(). Daca aplicatia are doua sau mai multe seturi de nregistrari, fiecare dintre acestea va crea n mod implicit si va deschide propriul obiect de tip CDatabase. Daca mai multe seturi de nregistrari au acces la aceeasi sursa de date, atunci este mai eficient ca ele sa foloseasca n comun obiectul de tip CDatabase. O modalitate de a permite mai multor seturi de nregistrari sa foloseasca n comun acelasi obiect CDatabase este atribuirea ca parametru functiei Open() pentru celelalte seturi de nregistrari valoarea variabilei membru m_pDatabase a primului obiect:
58

m_pSet->m_pDatabase=pDoc->m_sectiiSet.m_pDatabase; CRecordView::OnInitialUpdate();

Daca functia CRecordset::Open() afla ca variabila membru m_pDatabase este deja alocata, refoloseste obiectul de tip CDatabase care este deschis. nregistrarile din tabelul SECTII au fost sortate dupa coloana Nume:
pDoc->m_sectiiSet.m_strSort = "Nume";

Pentru o anumita sectie, nregistrarile din CAngajatiSet sunt sortate dupa atributul Nume :
m_pSet->m_strSort = "Nume";

g) Reinterogarea bazei de date. De fiecare data cnd utilizatorul selecteaza un alt nume de compozitor din caseta combinata, aplicatia trebuie sa reinterogheze obiectul set de nregistrari CAngajatiSet pentru a-i mprospata nregistrarile. Selectnd un nume de sectie, utilizatorul va vedea numai nregistrarile reprezentnd angajatii sectiei respective. CAngajaiSet contine nregistrarile pentru sectia selectata anterior. Reinterogarea i actualizeaza nregistrarile, n conformitate cu noul nume de sectie ales, folosind valorile curente ale sirurilor de caractere pentru filtrare si sortare. Cnd utilizatorul selecteaza un nume de sectie din caseta combinata, vederea de tip CAngajatiView primeste mesajul de notificare CBN_SELENDOK. Apoi, vederea foloseste functia de tratare a acestui mesaj pentru a reselecta nregistrarile corespunzatoare numelui de sectie selectat, transmitnd ca parametru identificatorul sectiei respective. Implementarea functiei de tratare a mesajului CBN_SELENDOK este:
void CAngajatiView::OnSelendokSectie() { CAngajatiDoc* pDoc = GetDocument(); m_pSet = &pDoc->m_angajatiSet; m_pSectiiSet = &pDoc->m_sectiiSet; CString str; if(!m_pSet->IsOpen()) return; m_ctrSectie.GetLBText(m_ctrSectie.GetCurSel(), str); if(!m_pSectiiSet->IsOpen()) return; m_pSectiiSet->MoveFirst(); while(!m_pSectiiSet->IsEOF()){ if(m_pSectiiSet->m_Nume == str) break; m_pSectiiSet->MoveNext(); } m_pSet->m_IdSectieParam = m_pSectiiSet->m_IdSectie; m_pSet->Requery(); if(m_pSet->IsEOF()){ m_pSet->SetFieldNull(&(m_pSet->m_IdSectie), FALSE); m_pSet->m_IdSectie = m_pSet->m_IdSectieParam; } UpdateData(FALSE); }

Codul adaugat reselecteaza nregistrarile din baza de date n obiectul set de nregistrari, n functie de valoarea parametrului m_IdSectiiParam. Aceasta valoare reprezinta identificatorul compozitorului al carui nume a fost selectat n caseta combinata. Functia GetLBText(), membru al clasei CComboBox, ncarca n cel de-al doilea parametru textul curent selectat n control. Astfel, variabila str va primi ca valoare numele sectiei curent selectate. Apoi se cauta n setul de nregistrari CAngajatiSet identificatorul compozitorului cu numele respectiv, pentru a-i atribui valoarea parametrului m_IdSectiiParam. Daca dupa reinterogarea obiectului CAngajatiSet reiese ca pentru sectia curent selectata nu exista nici un angajat, setul de nregistrari este initializat cu valoarea Null, cu exceptia cmpului IdSectie, care va fi initializat cu identificatorul sectiei respective. Dupa compilarea si rularea aplicatiei, se poate folosi interfata cu utilizatorul pentru a naviga printre angajatii din sectia al carei nume a fost selectat n caseta combinata. Daca se aleg alte nume de sectii, se vor obtine informatiile stocate n baza de date despre angajatii lor. La executia aplicatiei n acesta versiune (Step 2) se va afisa o fereastra ca cea din Fig. 4.1.

59

Fig. 4.1. Fereastra de afisare a aplicatiei Angajati (Step 2).

4.2.2.3 Extinderea interfetei pentru adaugarea, actualizarea si stergerea nregistrarilor n acest ultim pas al aplicatiei (Step 3), se va extinde interfata cu utilizatorul prin adaugarea a trei noi comenzi n meniul Record: Comanda Adauga pregateste o nregistrare vida n care utilizatorul va introduce datele. Acestea sunt salvate cnd utilizatorul trece la alta nregistrare, folosind o comanda de meniu sau un buton din bara cu instrumente pentru navigarea printre nregistrari. n acest fel, se puteau salva si pna acum modificarile aduse unei nregistrari. n plus, utilizatorul poate sa salveze noua nregistrare, alegnd din nou comanda Adauga. Comanda Renunta abandoneaza o operatiune de adaugare sau de editare a unei nregistrari aflate n curs de desfasurare. Totodata, readuce nregistrarea modificata la starea ei initiala sau revine la nregistrarea afisata nainte de alegerea comenzii Adauga. Comanda Sterge sterge o nregistrare. Cele trei articole se adauga n meniul Record. Fiecare dintre articolele nou adaugate are nevoie de o functie de tratare n clasa CAngajatiView. Deoarece resursa meniu a aplicatiei este asociata cu cla sa CMaimFrame, trebuie stabilita o asociere ntre identificatorii articolelor de meniu si clasa CAngajatiView. n operatiunile de adaugare, editare si stergere a nregistrarilor, clasa CRecordView actualizeaza n mod automat nregistrarea curenta atunci cnd utilizatorul trece la alta nregistrare. Clasa CRecordView modifica n trei pasi o nregistrare n obiectul set de nregistrari asociat, atunci cnd utilizatorul trece la alta nregistrare: Pregateste nregistrarea curenta pentru a fi actualizata, apel nd functia membru Edit() a clasei pentru setul de nregistrari. Apeleaza functia membru UpdateData() pentru clasa vedere a aplicatiei, derivata din CFormView, functie care modifica valorile variabilelor membru ale obiectului set de nregistrari, de obicei prin obtinerea noilor valori de la controalele formularului. Apeleaza functia membru Update() a clasei pentru seturi de nregistrari pentru a actualiza efectiv sursa de date cu valorile modificate.

Implementarea comenzii Adauga. n implementarea descrisa, utilizatorul va putea adauga numai angajati ai sectiilor existente n tabelul SECTII. Selectnd comanda Adauga, se intra n modul Adaugare, atribuindu-se variabilei m_bSeAdauga valoarea TRUE; cnd utilizatorul trece la alta nregistrare se iese din modul Adaugare.

60

Pentru implementarea functiei de tratare a comenzii Adauga , n fisierul AngajatiView.h se declara o variabila protected:BOOL m_bSeAdauga care se initializeaza n constructorul clasei CAngajatiView cu valoarea FALSE. Se construieste functia de tratare astfel:
void CAcgajatiView::OnRecordAdauga(){ if(m_bSeAdauga) OnMove(ID_RECORD_FIRST); CString str = m_pSet->m_IdSectie; m_pSet->AddNew(); // Se pregateste o noua inregistrare. m_pSet->SetFieldNull(&(m_pSet->m_IdSectie),FALSE); m_pSet->m_IdSectie=str; m_bSeAdauga=TRUE; UpdateData(FALSE);
}

Datele stocate n baza de date se actualizeaza, prin adaugarea noii nregistrari, atunci cnd utilizatorul trece la o alta nregistrare. Tot atunci se iese si din modul Adaugare. Aceasta functionalitate se realizeaza prin redefinirea functiei membru OnMove() a clasei CRecordView:
BOOL CAngajatiView::OnMove(UINT nIDMoveCommand){ if(m_bSeAdauga){ if(!UpdateData()) return FALSE; CAngajatiDoc* pDoc = GetDocument(); CString str; m_ctrSectie.GetLBText(m_ctrSectie.GetCurSel(),str); pDoc->m_sectiiSet.MoveFirst(); while(!pDoc->m_sectiiSet.IsEOF()){ if(pDoc->m_sectiiSet.m_Nume == str) break; pDoc->m_sectiiSet.MoveNext(); } m_pSet->m_IdSectie=pDoc->m_sectiiSet.m_IdSectie; TRY{ m_pSet->Update(); } CATCH(CDBException,e){ AfxMessageBox(e->m_strError); return FALSE; } END_CATCH; m_bSeAdauga=FALSE; OnSelendokComplist();return TRUE; } else return CRecordView::OnMove(nIDMoveCommand); }

Daca aplicatia este n modul Adaugare, se ncearca adaugarea noii nregistrari la baza de date, nainte de a se trece la alta nregistrare. Daca datele introduse ncalca vreo regula de validare sau de integritate impusa tabelului respectiv n baza de date, atunci se afiseaza mesajul de eroare corespunzator. Variabila membru m_IdSectie ia valoarea corespunzatoare sectiei selectate (n timpul adaugarii, caseta cu numele de sectii este folosita pentru introducere). n final, se iese din modul Adaugare si se apeleaza OnSelendokSectii() care va reinteroga baza de date. Atunci cnd aplicatia este n modul Adaugare si utilizatorul alege o comanda de navigare (de exemplu, MoveNext), ca urmare a reinterogarii sursei de date n metoda OnSelendokSectii() , se ajunge ntotdeauna la prima nregistrare. La pasul doi al aplicatiei, atunci cnd utilizatorul alegea numele unei sectii, functia de tratare OnSelendokSectii()reinterogheaza obiectul set de nregistrari CAngajatiSet pentru a gasi toti angajatii aflati n ntreprindere apartinnd sectiei respective. Pentru functia de adaugare de nregistrari, operatiile legate de reinterogare sunt executate numai daca aplicatia nu este n modul Adaugare, asa cum se vede n functia de tratare OnSelendokSectii(). Implementarea comenzii Sterge. Ca raspuns la comanda Sterge, se sterge nregistrarea curenta, apelndu-se functia membru Delete() a obiectului set de nregistrari asociat:
void CAngajatiView::OnRecordSterge(){ if(m_bSeAdauga){ 61

OnRecordRenunta(); return; } TRY{m_pSet->Delete(); } CATCH(CDBException,e) { AfxMessageBox(e->m_strError); return; } END_CATCH; m_pSet->MoveNext(); if(m_pSet->IsEOF()) m_pSet->MoveFirst(); if(m_pSet->IsBOF()) m_pSet->SetFieldNull(NULL); UpdateData(FALSE); }

Daca programul este n modul Adaugare, se apeleaza OnRecordRenunta(). Altfel, se apeleaza functia membru Delete() si, daca aceasta functie lanseaza o exceptie (de exemplu, nu se poate sterge o nregistrare datorita regulilor de integritate la nivelul tabelului respectiv), erorile sunt raportate utilizatorului. Variabila membru m_strError a clasei CDBException este pregatita de driverul ODBC pentru eroarea respectiva. Apoi, se trece la nregistrarea urmatoare. Daca nregistrarea stearsa era ultima din setul de nregistrari, n ordinea sortarii, atunci ne ntoarcem la nregistrarea care a devenit ultima n urma operatiunii de stergere. Daca nregistrarea stearsa a fost ultima si acum nu mai exista nregistrari, cmpurile setului de nregistrari vor primi valoarea Null. Implementarea comenzii Renunta. Comanda Renunta anuleaza modul Adaugare, n cazul n care utilizatorul a ales anterior comanda Adauga, sau nu ia n considerare modificarile aduse nregistrarii curente, daca utilizatorul a ncercat sa editeze valorile din cmpul formularului.
void CAngajati::OnRecordRenunta() { if(m_bSeAdauga){ m_pSet->Move(AFX_MOVE_REFRESH); m_bSeAdauga=FALSE; OnSelendokComplist(); return; } UpdateData(FALSE); }

Daca utilizatorul se razgndeste si nu mai vrea sa adauge noua nregistrare, aplicatia iese din modul Adaugare apelnd functia Move(), membru al clasei CRecordSet. Cnd se apeleaza functia AddNew() pentru a ncepe operatiunea de adaugare, cadrul-aplicatie pastreaza o copie a valorilor curente ale variabilelor setului de nregistrari, nainte de a permite utilizatorului sa introduca noi valori n controalele formularului. Apelul functiei Move() , ca mai sus, anuleaza operatiunea de adaugare, refacnd nregistrarea care era curenta nainte de a alege comanda Adauga. Dupa iesirea din modul Adaugare se va apela OnSelendokComplist()pentru a reinteroga baza de date, n cazul n care n timpul adaugarii a fost selectat un alt compozitor. Acest exemplu s-a axat pe modul de definire a interogarilor n aplicatiile MFC-ODBC, considernd un singur formular de interfata, care este chiar fereastra vedere a aplicatiei (din clasa derivata din clasa CRecordView). Bibllioteca MFC ofera si alte posibilitati de dezvoltare a aplicatiilor si interfetelor cu bazele de date, prin combinarea mai multor seturi de rezultate (obiecte CResultset) n aceeasi vedere (formular) al unei aplicatii SDI ( Single Document Interface) sau folosind ferestrele descendent n aplicatii MDI (Multiple Document Interface). 4.2.2.4 Crearea formularelor care afiseaza mai multe seturi de rezultate Dezvoltarea unei aplicatii care afiseaza mai multe seturi de rezultate n aceeasi fereasta se poate urmari n exemplul din directorul ..\Aplicatii\Capitol4\MFC\\SA\Step1. Aceasta aplicatie cu numele SA este de tip SDI si foloseste baza de date INTREPRINDERE (dezvoltata ntr-una din lucrarile precedente), pentru care s-a definit o sursa de date ODBC. Clasa
62

interogarii de baza (clasa CSASet derivata din clasa CresultSet) se asociaza, pe de o parte, cu toate tabelele care intervin n interogare (n cazul acesta, tabelele ANGAJATI si SECTII) si, pe de alta parte, cu clasa vedere (formular) CSAView, derivata din clasa CRecordView. n formular (clasa vedere - CSAView) se plaseaza controlale de selectie si afisare a datelor, care corespund variabilelor membre ale clasei CSASet asociate coloanelor (atributelor) tabelelor asociate (Fig. 4.2). Pentru a realiza anumite interogari(de exemplu, afisarea angajatilor unei sectii date prin numele acesteia) se introduc parametri de interogare si, de cele mai multe ori, sunt necesare si alte seturi de rezultate, separate pe tabele din care se extrag valorile parametrilor. n exemplul prezentat, n panoul din stnga se afiseaza rezultatul interogarii:
SELECT IDAngajati, ANGAJATI.Nume,Prenume,Salariu,IdSectie FROM ANGAJATI, SECTII WHERE ANGAJATI.IDSectie = SECTII.IdSectie AND SECTII.Nume = parametru

Pentru realizarea filtrului parametrizat este necesara crearea setului de rezultate de citire a tuturor liniilor tabelului SECTII , pentru care s-a adaugat clasa CSectiiSet, asociata cu tabelul SECTII. n clasa document se nglobeaza, pe lnga setul de nregistrari propriu si un obiect instanta al setului de nregistrari pentru filtrul parametrizat:
class CSADoc : public CDocument { public: CSASet m_sASet; CSectiiSet m_sectiiSet; ............................ };

Fig. 4.2. Afisarea mai multor seturi de rezultate n acelasi formular.

Initializarea casetei combo-box din care se selecteaza parametrul interogarii este realizata n functia OnInitialUpdate() a clasei CSAView astfel:
void CSAView::OnInitialUpdate(){ CSADoc* pDoc = GetDocument(); m_pSet = &pDoc->m_sASet; // Se deschide conexiunea fara filtrare, // pentru a citi valoarea parametrilor IdSectie si NumeAngajat CSectiiSet* m_pSectiiSet = &pDoc->m_sectiiSet; if(!m_pSectiiSet->Open())return; CRecordView::OnInitialUpdate(); // Filtrul interogarii 63

m_pSet->m_strFilter="Angajati.IdSectie=Sectii.IdSectie \ AND Sectii.Nume=?"; m_ctrSectie.ResetContent(); if (m_pSectiiSet->IsOpen()){ m_pSectiiSet->MoveFirst(); m_pSet->m_SectieParam = m_pSectiiSet->m_NumeSectie; while (!m_pSectiiSet->IsEOF()){ m_ctrSectie.AddString(m_pSectiiSet->m_NumeSectie); m_pSectiiSet->MoveNext(); } } m_ctrSectie.SetCurSel(0); m_pSet->m_pDatabase = pDoc->m_sectiiSet.m_pDatabase; m_pSet->Requery(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); }

4.2.2.5 Crearea mai multor formulare ntr-o aplicatie SDI O alta posibilitate de afisare a datelor ntr-o aplicatie SDI este de a crea mai multe clase vedere (formulare) si de a sigura comutarea ntre acestea. Tot n proiectul de mai sus se poate urmari si acest mod de afisare. Pentru acesta se creeaza cte o clasa de vizualizare derivata din clasa CRecordView pentru fiecare interogare (set de rezultate), si fiecarei vederi i se atribuie un identificator. n acest exemplu s-au creat clasele CAngajatiView (asociata cu clasa CAngajatiSet) si o clasa CSectiiView (asociata cu clasa CSectiiSet existenta), pe lnga clasa initiala a proiectului CSAView. Se adauga un articol n meniul principat (articolul Forms) careia i se adauga trei comenzi care permit selectarea formularului: (Angajati, Sectii,SectiiAngajati). Aceste comenzi sunt tratate n clasa CMainFrame si produc schimbarea formularului afisat prin apelul unei functii de comutare (CMainFrame::SwitchToForm), al carui cod poate fi studiat n exemplul propus. 4.2.2.6 Crearea mai multor formulare ntr-o aplicatie MDI Este posibila crearea si afisarea mai multor formulare ntr-o aplicatie MDI, daca se definesc mai multe template-uri de documente si fiecare template este asociat unei vederi (formular) diferit. Un astfel de exemplu este aplicatia MDI cu numele SA, care se gaseste n directorul \Aplicatii\Capitol4\MFC\SA\Step2 si foloseste tabelele SECTII, ANGAJATI, PROIECTE. Pentru o astfel de afisare se creaza mai multe clase vedere (derivate din clasa CRecordView), fiecare clasa asociata cu o clasa set de rezultate derivate din clasa CRecordSet, care, la rndul ei se asociaza cu unul sau mai multe tabele din baza de date. n exemplul descris s-au creat clasele de interogare (set de rezultate) CSASet, CSectiiSet si CProiecteSet , asociate cu tabelele ANGAJATI, SECTII, respectiv PROIECTE, pe de o parte, si cu clasele vedere CSAView, CSectiiView si CProiecteView, pe de alta parte. Dupa aceasta, n functia InitInstance a clasei aplicatiei (CSAApp) se creeza (manual, generatorul Wizard nu ofera acesta functionalitate) mai multe template-uri de document astfel:
BOOL CSAApp::InitInstance() { // Standard initialization ... // ANGAJATI - 0 CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate(IDR_SATYPE, RUNTIME_CLASS(CSADoc),RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CSAView)); AddDocTemplate(pDocTemplate); // SECTII - 1 pDocTemplate = new CMultiDocTemplate(IDR_SATYPE, RUNTIME_CLASS(CSADoc),RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CSectiiView)); AddDocTemplate(pDocTemplate); 64

// PROIECTE - 2 pDocTemplate = new CMultiDocTemplate(IDR_SATYPE, RUNTIME_CLASS(CSADoc),RUNTIME_CLASS(CChildFrame), RUNTIME_CLASS(CProiecteView)); AddDocTemplate(pDocTemplate); // Create main MDI Frame window ... return TRUE; }

Comutarea ntre vederi se realizeaza din meniul File (din meniul principal) n care se nlocuiesc comenzile New, Open , etc. cu comenzile corespunzatoare vederilor care se vor afisa (Angajati,Sectii,Proiecte) si fiecare functie de tratare a unei astfel de comenzi apeleaza o functie de comutare (OpenSpecifiedTemplate) care se poate studia din acest exemplu si folosi ulterior.
#define #define #define #define ANGAJATI SECTII PROIECTE NR_FORMS 0 1 2 3

void CSAApp::OnFileAngajati() { OpenSpecifiedTemplate( ANGAJATI); }

Ferestrele vedere create pot fi afisate si selectate succesiv, si reprezinta formulare multiple ntr-o singura aplicatie de baze de date (Fig. 4.3).

Fig. 4.3. Crearea mai multor formulare ntr-o aplicatie MDI.

La rndul lor, formularele pot contine nu numai interogari simple (ca cele de mai sus), ci si interogari complexe, parametrizate, si comenzi de inserare, stergere, actualizare, obtinndu-se astfel interfete grafice versatile de acces la bazele de date.

65

4.2.3

DEZVOLTAREA APLICATIILOR DE BAZE DE DATE IN TEHNOLOGIA .NET

Pentru dezvoltarea aplicatiilor de baze de date, tehnologia .NET ofera interfata ADO.NET (Access Data Object) ca parte integranta a bibliotecii .NET Framework Class Library (FCL) care permite accesul la baze de date att n aplicatii locale ct si n aplicatii distribuite n Internet. Pentru accesul la bazele de date, ADO.NET foloseste un modul software de interconectare numit driver (data provider). In prezent ADO.NET supporta trei tipuri de drivere (data providers) : SQL Server .NET provider, care permite interfatarea directa cu baze de date SQL Server, ncepnd cu versiunea 7.0 si continuand cu versiunile ulterioare (2000, 2003, 2005). OLE DB .NET provider, care permite interfatarea cu baze de date care suporta interfata OLE DB, cum sunt Access, Oracle. Se poate folosi si pentru SQL Server, n orice versiune. ODBC .NET provider, care permite interfatarea cu baze de date care suporta interfata ODBC Orice interactiune cu o baza de date n ADO.NET necesita crearea unui obiect de conexiune care reprezinta conexiunea fizica la baza de date si este o instanta a clasei SqlConnection (pentru SQL Server .NET provider), a clasei OleDbConnection (pentru OLE DB .NET provider) sau a clasei PdbcConnection (pentru ODBC .NET provider). Instructiunea SQL (SELECT, INSERT, UPDATE sau DELETE) care se transmite bazei de date se defineste printr-un sir de comanda, care este continut ntr-un obiect de comanda. Un obiect de comanda, care este o instanta a uneia din clasele SqlCommand, OleDbCommand , sau OdbcCommand (n functie de providerul folosit) ncapsuleaza o conexiune la baza de date si un sir de comanda. Folosind un obiect de comanda, se pot executa att instructiuni SQL de definire a datelor (INSERT,DELETE,UPDATE) prin apelul metodei ExecuteNonQuery()ct si instructiuni de interogare, prin apelul metodei ExecuteReader(). Rezultatul unei instructiuni de interogare se obtine ntr-un obiect DataReader, instanta a uneia din clasele SqlDataReader, OleDbDataReader sau OdbcDataReader (n functie de providerul folosit), care ncapsuleaza multimea de linii rezultat al interogarii, cu posibilitatea de parcurgere nainte, linie cu linie. Interogarea bazei de date se mai poate face si prin intermediul unui obiect DataSet, care este o instanta a clasei DataSet si reprezinta o copie n memorie a unor date dintr-o baza de date (multimi de linii). Liniile memorate ntr-un obiect DataSet pot fi extrase direct (nu prin parcurgere linie cu linie) iar modificarile efectuate n DataSet sunt propagate n baza de date. n continuare se va studia interogarea unei baze de date folosind un obiect DataReader si interogarea prin intermediul unui obiect DataSet. 4.2.3.1 Interogarea bazei de date folosind un obiect DataReader In exemplul de mai jos (Program4_15.cs) se creaza o conexiune la baza de date pubs din distributia SQL Server (Samples) executata local (localhost), cu mod de autentificare SQL, pentru utilizatorul sa cu parola data. In acest mod este bine sa fie stabilita (sau modificata) parola contului sa si sa fie editata nregistrarea serverului cu comanda Edit SQL Server Registration Properties din meniul de context care se deschide la apasarea butonului dreapta al mouse-ului atunci cnd este selectat numele instantei serverului. n fereastra care se deschide (Registered SQL Server Properties) se selecteaza optiunile Use SQL Server Authentication si Always prompt for login name and password. Pentru aceasta conexiune creeaza mai nti o comanda de inserare de date (obiectul cmd, instanta a clasei SqlCommand) si se executa inserarea prin apelul metodei ExecuteNonQuery()a acestui obiect. Pentru interogare se creeaza o noua comanda (folosind aceeasi referinta cmd) care contine o instructiune SQL de interogare si se apeleaza metoda ExecuteReader() a obiectului de comanda. Rezultatul returnat de o interogare este o multime de linii (record set) memorata ntr-un obiect DataReader instanta a clasei SqlDataReader. Liniile dintr-un astfel de obiect pot fi parcurse folosind metoda Read() care returneaza false atunci cand s-au parcurs toate liniile. Valoarea unui cmp (atribut) dintr-o linie se obtine prin indexarea (cu indexul dat prin numele atributului sau numeric, n ordinea coloanelor, ncepnd cu valoarea 0) obiectului DataReader.
66

Dupa citirea si afisarea liniilor tabelului titles , se sterge linia introdusa folosind o comanda de stergere (instructiunea SQL delete); pentru a executa aceasta noua comanda este necesar sa fie mai nti nchis obiectul DataReader (prin apelul metodei Close()), altfel se obtine o eroare de executie. Acest program poate fi creat cu un editor oarecare si compilat cu compilatorul C# (csc), sau poate fi generat ca proiect C# Console Application n Visual Studio .NET (asa cum este prezentat n Indrumarul de laborator APOO, lucrarile 7 si 8).
//Fisier Program4_15.cs using System; using System.Data.SqlClient; class Class1 { public static void Main(string[] args){ SqlConnection conn = new SqlConnection ("server=localhost;database=pubs;uid=sa;pwd=parola"); try { conn.Open (); // Comanda de inserare String cmdString = ("insert into titles+ "(title_id, title, price, type, pubdate) " + "values ('JP1001', 'Baze de date', 15.2" + "'didactic', 'Feb 2004'); SqlCommand cmd = new SqlCommand(cmdString,conn); cmd.ExecuteNonQuery (); // Comanda de interogare cmd = new SqlCommand("select * from titles", conn); SqlDataReader reader = cmd.ExecuteReader (); while (reader.Read ()) Console.WriteLine ("{0} {1}", reader["title_id"], reader["title"]); reader.Close(); // Comanda de stergere cmd = new SqlCommand( "delete from titles where title_id ='JP1001'", conn); cmd.ExecuteNonQuery (); } catch (SqlException ex){Console.WriteLine (ex.Message);} finally {conn.Close ();} } }

La executia acestui program sunt listate toate titlurile de carti existente n tabelul titles din baza de date pubs n momentul executiei, mpreuna cu cheile lor primare, asa cum se vede mai jos.

Fig. 4.4. Rezultatul executiei aplicatiei Program4_15. 67

4.2.3.2 Interogarea unei baze de date folosind un obiect DataSet Interogarea unui baze de date folosind clasa SqlDataReader este eficienta, dar setul de linii obtinut nu poate fi parcurs dect secvential si este read-only . Nu se poate face, de exemplu, revenirea la linia precedenta, si nu se poate modific a o linie si aceasta sa fie apoi scrisa napoi n baza de date. Pentru astfel de operatii ADO.NET ofera o clasa mai puternica si anume clasa DataSet, care reprezinta o submultime de tabele ale bazei de date la care se conecteaza utilizatorul (Fig. 4.6). Datele din aceste tabele sunt ncarcate n obiectul DataSet , obiectul se deconecteaza de la sursa de date, iar utilizatorul poate manipula datele din acest obiect. Periodic obiectul DataSet se reconecteaza la baza de date pentru actualizarea bazei de date conform modificarilor efectuate de utilizator n obiectul DataSet si a obiectului DataSet cu modificarile efectuate de alti utilizatori n baza de date. Clasa DataSet are proprietatea Tables care returneaza o colectie (DataTableCollection) de obiecte DataTable, fiecare reprezentnd un tabel din baza de date si proprietatea Relations care returneaza o colectie ( DataRelationCollection) de obiecte DataRelation, fiecare reprezentnd o asociere ntre doua tabele ale bazei de date realizata prin intermediul unor obiecte DataColumn. Se reaminteste ca n CLS (Common Language Specification) o proprietate este o interfata de tip public a unei clase care contine metodele de citire-scriere (get si set) ale unui atribut de tip private. n acest mod, utilizatorii clasei acceseaza mai simplu atributul respectiv (ca si cum acesta ar fi public), respecatndu-se incapsularea si ascunderea datelor. Clasa DataTable are urmatoarele proprietati:
Columns: reprezinta un obiect DataColumnCollection, care consta dintr-o colectie de obiecte DataColumn, fiecare reprezentnd o coloana dintr-un tabel. Rows: reprezinta un obiect DataRowCollection, care consta dintr-o colectie de obiecte DataRow, fiecare reprezentnd o linie dintr-un tabel. Constraints: reprezinta un obiect ConstraintCollection, care consta dintr-o colectie de obiecte Constraint, fiecare reprezentnd o constrngere n baza de date.

<<uses>> DataSet 1 1 DataRelationCollection 1 0...* DataRelation 1 1 DataRowCollection 1 0...* DataRow 1 1 DataTableCollection 1 1...* DataTable 1 1 DataCollumnCollection 1 1...* DataCollumn 1 1 ConstraintCollection 1 0...* Constraint DataAdapter 1 1 SqlCommand

Fig. 4.4. Diagrama UML (partiala) a claselor ADO.NET

68

Din pacate, aceste denumiri stabilite de dezvoltatorii Microsoft nu prea corespund terminologiei corecte din domeniul bazelor de date: clasa DataRelation reprezinta o asociere (relationship) nu o relatie asa cum ar sugera numele ei; o relatie (relation) se reprezinta prin clasa DataTable. Binenteles, aceste denumiri nu pot fi schimbate si trebuie sa fie folosite asa cum sunt. Clasa DataAdapter asigura o intermediere ntre baza de date si program. Un obiect DataAdapter permite transferul datelor ntre obiectul DataSet si baza de date, ncapsulnd sirul de comanda (instructiunea) SQL si conexiunea la baza de date ntre clasa DataAdapter si clasa DataSet exista o dependenta de utilizare. Exemplul urmator (Program4_16) realizeaza aceeasi functionare ca si exemplul precedent, folosind un obiect DataSet: se realizeaza o conexiune la baza de date pubs, se nscrie o linie n tabelul titles, apoi se listeaza toate valorile atributelor title_id si title ale tuturor liniilor din tabelul titles; n final se sterge linia nou introdusa.
// Fisier Program4_16.cs using System; using System.Data; using System.Data.SqlClient; namespace Program4_16 { class Class1 { static void Main(string[] args) { // Crearea conexiunii la baza de date pubs SqlConnection conn = new SqlConnection ("server=localhost;database=pubs;uid=sa;pwd=parola"); try { // Deschiderea conexiunii conn.Open (); // Crearea obiectului adapter string cmdString = "select * from titles"; SqlDataAdapter dataAdapter1 = new SqlDataAdapter(cmdString, conn); // Crearea si popularea obiectului DataSet DataSet dataSet1 = new DataSet(); dataAdapter1.Fill(dataSet1,"titles"); // Utilizarea datelor din data set // pentru interogare si modificarea datelor din tabele DataTable table = dataSet1.Tables["titles"]; // Inserarea unei linii noi in tabelul titles DataRow newRow = table.NewRow (); newRow["title_id"] = "JP1001"; newRow["title"] = "Baze de date"; newRow["price"] = "15.20"; newRow["ytd_sales"] = "1000000"; newRow["type"] = "didactic"; table.Rows.Add (newRow); // Listarea tuturor liniilor din tabelul titles foreach (DataRow row in table.Rows) Console.WriteLine ("{0} {1}",row[0],row["title"]); // Stergerea liniei inserate in tabelul titles DataRow[] rows = table.Select ("title_id = 'JP1001' "); foreach (DataRow row in rows) row.Delete (); } catch (SqlException ex) {Console.WriteLine (ex.Message);} finally {conn.Close ();} } } }

69

Acest program poate fi creat, ca si programul precedent, cu un editor oarecare si compilat cu compilatorul C# (csc), sau poate fi generat ca proiect C# Console Application n Visual Studio .NET. La executia acestui program se obtine o lista a titlurilor de carti si a cheilor primare ale acestora din tabelul titles din baza de date pubs din distributia SQL Server, la fel ca si n exemplul precedent. Toate operatiile cu baza de date se desfasoara prin intermediul obiectului DataSet (dataset1) care este popula t cu multimea de linii rezultat al interogarii definite prin sirul de comanda (cmdString); sirul de comanda si conexiunea la baza de date sunt ncapsulate n obiectul DataAdapter, iar popularea obiectului DataSet se face prin metoda Fill() a adaptorului: Datele rezultate din interogare se extrag din obiectul DataSet, folosind proprietatea Tables a acestuia, indexata cu numele tabelului sau cu un index numeric ncepnd cu 0; n mod similar, din tabel se extrag liniile folosind propietatea Rows a tabelului
DataTable table = dataSet1.Tables["titles"]; foreach (DataRow row in table.Rows) Console.WriteLine ("{0} {1}",row[0],row["title"]);

Tot obiectul DataSet poate fi folosit si pentru operatii de actualizare a bazei de date, folosind metoda corespunzatoare ( Add() , Delete(), etc) 4.2.3.3 Afisarea datelor din tabele folosind un obiect DataGrid Modelul ADO.NET ofera suport si pentru dezvoltarea interfetei grafice cu utilizatorul, prin posibilitatea de asociere a unor multimi de linii (DataSet) cu controale de afisare a datelor. n forma cea mai simpla, un obiect DataGrid se asociaza cu un obiect DataSet si afiseaza datele stocate de acesta. Un exemplu simplu n limbajul C# de acces la o baza de date folosind clase ADO si un control DataGrid necesita, ntr-adevar, un numar incredibil de mic de linii de program (Program4_16). n acest exemplu se creeaza un formular Windows (Windows Form) cu un obiect DataGrid (cu referinta notata dataGrid1) care va fi populat cu informatii citite din tabelul Customers al bazei de date Northwind, care face parte din setul de exemple ce se livreaza cu sistemul SQL Server. De asemenea, sistemul de dezvoltare .NET SDK (si VS .NET) contine o versiune independenta de server de baze de date desktop cunoscut sub numele Microsoft SQL Server 2000 Desktop Engine (MDSE 2000) care poate fi folosit pentru testari n locul serverului SQL Server, si contine baza de date Northwind. Listingul de mai jos este putin simplificat (detaliile de implementare a metodelor InitializeComponent() si Dispose()de initializare si de stergere a datelor).
// Fisier Program4_16.cs using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Data.SqlClient; namespace Program4_16 { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.DataGrid dataGrid1; private System.ComponentModel.Container components =

null;

public Form1() { InitializeComponent(); string conn = "server=localhost;uid=sa; + "pwd=parola;database=northwind"; string cmdString = "Select * from Customers"; SqlDataAdapter dataAdapter1 = new SqlDataAdapter(cmdString,conn); DataSet dataSet1 = new DataSet(); dataAdapter1.Fill(dataSet1,"Customers"); dataGrid1.DataSource = dataSet1.Tables["Customers"].DefaultView; }

70

protected override void Dispose(bool disposing){...} private void InitializeComponent(){...} static void Main() { Application.Run(new Form1()); } } }

Pentru crearea aplicatiei de acces la baza de date Northwind, se creeaza mai nti un obiect DataAdapter (cu numele dataAdapter1), caruia i se paseaza ca argumente la constructie un sir de caractere de conectare (conn) si un sir de caractere de comanda (cmdString):
SqlDataAdapter dataAdapter1 = new SqlDataAdapter(cmd, conn);

Sirul conn transmite serverului SQL Server datele de conectare (numele utilizatorului, parola si numele bazei de date), iar sirul comanda transmite bazei de date instructiunea SQL de interogare. Aceste siruri se definesc astfel:
string conn = "server = localhost;uid = sa; + pwd = parola ;database = northwind"; string cmd = "Select * from Customers";

Datele din tabele se ncarca n obiectul DataSet prin apelul metodei Fill() a obiectului
dataAdapter1:
DataSet dataSet1 = new DataSet(); dataAdapter1.Fill(dataSet1,"Customers");

Afisarea liniilor rezultat al interogarii definite se poate face n obiectul DataGrid daca se specifica asocierea acestuia cu obiectul DataSet :
dataGrid1.DataSource = dataSet1.Tables["Customers"].DefaultView;

La executia acestui program se obtine lista liniilor din tabelul Customers al bazei de date
Northwind, asa cum se vede n figura de mai jos.

Fig. 4.5. Rezultatul executiei aplicatiei Program4_17.

Visual Studio .NET ofera suport complet de creare si conectare a obiectelor DataSet si DataGrid, astfel nct majoritatea liniilor de cod prezentate mai sus pot fi generate vizual, prin editarea proprietatilor formularului si a controalelor folosite, modalitati care vor fi prezentate n continuare.

71

4.2.3.4 Crearea vizuala a aplicatiilor de baze de date n Visual Studio .NET n sistemul de dezvoltare Visual Studio .NET se pot crea n mod vizual (prin selectarea din bare de instrumente) att obiectele de comunicatie cu baza de date (conexiuni, comenzi, adapter, set de date), controalele grafice de afisare a datelor: liste de afisare (ListView), tablouri de afisare (DataGrid) ct si conexiunile dintre acestea. n continuare sunt prezentate cteva exemple semnificative de creare a aplicatiilor de baze de date folosind proiectarea vizuala oferita de VS .NET. A. Interogarea bazei de date printr-un obiect DataReader si afisarea rezultatelor folosind un obiect ListView Acest exemplu (Program4_18) este o aplicatie Windows creata n Visual Studio .NET, care creeaza o conexiune la o baza de date Northwind, executa o comanda SQL de interogare, iar rezultatul l afiseaza ntr-un control ListView. Mai nti se creeaza un proiect Visual C# Windows Application, care contine un formular (clasa Form1). Se pot modifica diferite caracteristici ale formularului selectnd formularul (prin click pe formular n modul Design Form1.cs[Design]). La selectia formularului, n fereastra Properties se poate seta titlul formularului editnd proprietatea Text, n care introducem, de exemplu, textul Crearea unui DataReader in VS .NET . Etaple urmatoare de creare a aplicatiei sunt date n continuare. 1. Crearea obiectului de conexiune SqlConnection. Un obiect SqlConnection se creeaza prin selectarea n pagina Data a ferestrei de instrumente Toolbox a obiectului SqlConnection si tragerea lui n fereastra formularului. Dupa aceasta operatie, sub formular este afisat obiectul creat (cu numele implicit sqlConnection1), care este considerat un obiect nevizual (nu apare afisat n formular), dar poate fi totusi proiectat folosing instrumentele Visual Studio. La selectarea obiectului sqlConnection1 proprietatile lui se afiseaza n fereastra Properties. Pentru editarea proprietatii ConnectionString (care specifica detaliile de conectare la baza de date) fie se editeaza direct sirul de conectare (la fel ca n aplicatiile precedente), fie se actioneaza cu click pe acest cmp si se selecteza optiunea New Connection din lista de optiuni afisata, care genereaza fereastra de dialog Data Link Properties. n pagina Provider a acestei ferestre se selecteaza optiunea Microsoft OLE DB Provider for SQL Server si se trece (cu butonul de comanda Next) la pagina urmatoare (Connection). n aceasta pagina (reprezentata n figura de mai jos) se pot seta mai multi parametri: (1) Serverul de baze de date (se poate selecta dintr-o lista sau se poate introduce numele serverului; pentru serverul local se introduce localhost); (2) Modul de conectare (log on) la server; pentru optiunea Use a specific user name and password se poate folosi userul sa; (3) Selectarea bazei de date pe server (s-a selectat baza de date Nortwind).

72

Dupa aceste setari se poate testa conexiunea la baza de date (cu butonul de comanda Test Connection) si, daca acest test reuseste se afiseaza mesajul Test connection succeeded si se poate continua dezvoltarea aplicatiei. Cteva din optiunile setate se pot urmari n figura de mai sus. La nchiderea (cu butonul OK) a ferestrei de dialog Data Link Properties, se lanseaza fereastra de dialog de conectare (login) la baza de date (asa cum se vede n figura de mai sus) si se introduce parola pentru conectare la server. Sirul de comanda creat astfel ca proprietate a obiectului sqlConnect1, se poate vedea n fereastra de proprietati, iar codul generat l gasim n metoda InitializeComponent() a clasei Form1, care este afisat n fereastra de editare daca formularul (clasa Form1) este selectat n modul View Code din meniul de context care se obtine prin click dreapta pe numele fisierului (Form1.cs) n fereastra Solution Explorer. Ceea ce s-a obtinut arata astfel:
this.sqlConnection1.ConnectionString = "workstation id=FELIX;packet size=4096;user id=sa; data source=localhost;persist " + "security info=False;initial catalog=Northwind";

Aceast sir nu este suficient si trebuie completat cu parola (pwd=parola;) altfel nu se va realiza conexiunea dorita. Se poate folosi si un sir de conectare mai simplu, asemanator celui folosit n exemplul precedent: 2. Crearea obiectului de comanda SqlCommand . Obiectul de comanda se creeaza prin selectarea optiunii SqlCommand din pagina Data a ferestrei de instrumente Toolbox, si tragerea acesteia n formular. Obiectul care se creeaza (cu numele implicit sqlCommand1) este afisat sub formular si, daca este selectat, proprietatile lui se afiseaza n fereastra Properties. Pentru acest obiect importante sunt proprietatile Connection si CommandText. Se selecteaza proprietatea Connection si se selecteaza optiunea sqlConnection1. Apoi se selecteaza proprietatea CommandText pentru definirea instructiunii SQL (ca un sir de caractere). Se actioneaza butonul de urmarire () si se obtine un formular de creare a interogarilor (Query Builder) n care se pot introduce unul sau mai multe tabele, vederi (views) sau functii. Se introduce tabelul Customers si apoi se selecteaza ca iesiri (Output) acele atribute ale tabelului care trebuie sa fie citite din baza de date (asa cum se vede n figura de mai jos).

3. Crearea obiectului de citire SqlDataReader si a controlului de afisare ListView. Obiectul ListView se selecteaza in pagina Windows Forms a ferestrei de instrmente ToolBox si se trage n formular. Dimensiunile acestei liste se pot ajusta prin tragere cu mouse-ul a ferestrei respective.

73

Operatia de interogare a bazei de date o vom face la ncarcarea formularului, prin tratarea evenimntului Load a acestuia. Pentru aceasta se actioneaza dublu-click pe formular (n afara listei de afisare ListView) si generatorul de cod creeaza un handler de eveniment (metoda Form1_Load) pe care l nregistreaza pentru evenimentul Load a clasei Form1. Acest handler se completeaza astfel:
private void Form1_Load(object sender, System.EventArgs e){ sqlConnection1.Open(); System.Data.SqlClient.SqlDataReader reader = sqlCommand1.ExecuteReader(); while (reader.Read()){ listView1.Items.Add(reader["CustomerID"].ToString()); listView1.Items.Add(reader["CompanyName"].ToString()); listView1.Items.Add(reader["ContactName"].ToString()); } reader.Close(); sqlConnection1.Close(); }

4. Crearea obiectului DataReader. Obiectul de citire SqlDataReader nu se poate crea vizual, ci se obtine prin executia metodei ExecuteReader() a obiectului sqlCommand1 , asa cum se vede n codul de mai sus. La executia programului se obtine lista clientilor firmei Northwind, afisata ntr-un control ListView, asa cum se vede n imaginea de mai jos.

B. Interogarea bazei de date printr-un obiect DataSet si afisarea rezultatelor folosind un obiect DataGrid Acest exemplu (Program4_19) este o aplicatie Windows asemanatoare exemplului precedent, cu deosebirea ca datele se citesc printr-un obiect DataSet si se afiseaza ntr-un control DatataGrid. Primele etape de creare a proiectului n Visual Studio .NET, inclusiv etapele de creare a obiectului conexiune sqlConnection1 si a obiectului de comana sqlCommand1, sunt identice cu cele de la exemplul precedent. De asemenea, este necesara setarea proprietatii Connexion a obiectului sqlCommand1 cu valoarea sqlConnexion1. Etapele urmatoare sunt date n continuare. 1. Crearea obiectului SqlDataAdapter se realizeaza vizual prin selectarea acestuia n pagina Data a ferestrei Toolbox si tragere n formular. La introducerea adaptorului n formular se initiaza un generator de configurare (Data Adapter Configuration Wizard) cu mai multe ferestre de dialog. n prima fereastra se seteaza conexiunea, selectnd conexiunea la baya de date Nortwind, de pe statia (host) locala si utilizatorul dbo (n exemplul utilizat: FELIX.Nortwind.dbo). n a doua pagina a generatorului se selecteaza optiunea Use SQL statement, din cele trei optiuni posibile (Use SQL statement, Create new stored procedure, Use existing stored procedure). In pagina urmatoare de dialog se introduce direct instructiunea SQL dorita (SELECT * FROM Customers) sau se genereaza aceasta instructiune folosind QueryBuilder, la fel ca n exemplul precedent. Aceste proprietati se pot re-configura daca se selecteaza link-ul Configure Data Adapter, afisat la sfrsitul listei de proprietati a obiectului dataAdapter1.
74

Dupa crearea obiectului sqlDataAdapter1, se selecteaza acest obiect si se seteaza proprietatea SelectCommand selectnd valoarea sqlCommand1 din lista de comenzi afisate. 2. Crearea obiectului DataSet. Obiectul DataSet care va fi folosit pentru interogarea bazei de date se poate crea selectnd link-ul Generate Dataset afisat sub lista de proprietati a obiectului dataAdapter1. La selectarea acestui link se afiseaza o fereastra de dialog intitulata Generate Dataset, n care se selecteaza (cu optiunea New) crearea unui noi clase DataSet1 careia i se adauga tabelul Customers din baza de date. Obiectul dataSet11 creat este o instanta a clasei DataSet1 si este afisat sub formular, alaturi de celelalte obiecte ne-vizuale (sqlConnect1, sqlCommand1 , sqlDataAdapter1). Dupa aceasta etapa fereastra VS arata ca n imaginea de mai jos.

Obiectul dataSet11 creat este instanta a unei clase create de generatorul de cod (Wizard) VS .NET, numita DataSet1, derivata din clasa DataSet, si corespunznd datelor selectate pentru acest obiect (tabelul Customers). Aceasta clasa este definita printr-o schema XML, generata de Wizard si continuta n fisierul DataSet1.xsd care se vede n fereastra Solution Explorer. Schema XML a unui DataSet poate fi afisata n modul Design (selectnd optiunea Open din meniul derulant care se afiseaza cu click dreapta pe fisierul DataSet1.xsd din fereastra Solution Explorer) sau n mod text, daca se selecteaza optiunea View in Browser. n modul Design, se afiseaza schema XML corespunzatoare clasei DataSet1 sub forma unui tabel (al liniilor rezultat al interogarii), iar n modul View in Browser, se afiseaza schema corespunzatoare, ca document XML. Clasa DataSet1 (din care se instantiaza obiectul dataSet11) este generata n fisierul DataSet1.cs, care poate fi deschis daca se selecteaza modul Show All Files (n fereastra Solution Explorer) si apoi se expandeaza grupul de fisiere marcat DataSet1.xsd. Clasa DataSet1 este derivata din clasa DataSet:
public class DataSet1 : DataSet {...}

3. Interogarea bazei de date se face n metoda Form1_Load(), care este un handler de evenimente nregistrat pentru evenimentul Load al formularului Form1. Se completeaza aceasta metoda astfel:

75

private void Form1_Load(object sender, System.EventArgs e){ sqlConnection1.Open(); sqlDataAdapter1.Fill(dataSet11,"Customers"); dataGrid1.DataSource = dataSet11.Tables["Customers"].DefaultView; sqlConnection1.Close(); }

La executia aplicatiei se obtine un formular care contine un tabel ca n imaginea de mai jos:

C. Afisarea rezultatelor unei interogari folosind un obiect DataView Clasa DataView permite afisarea numai a unora dintre liniile unui tabel (DataTable), folosind un filtru, precum si ordonarea acestora dupa un criteriu dat n exemplul urmator ( Program4_20) se executa o interogare a bazei de date Northwind (SELECT * FROM Customers where Country=filtruORDER BY CustomerID) folosind un obiect DataSet. Datele din DataSet se afiseaza ntr-un obiect DataGrid folosind un obiect DataView, care permite filtrarea liniilor conform conditiei de interogare (clauza WHERE a interogarii) si ordonarea lor dupa valorile atributului dat n clauza ORDER BY. Etapele de dezvoltare a proiectului n VS .NET sunt prezentate n continuare. 1. Se creeaza un proiect C# Windows Application cu numele Program4_20 si se selecteaza panoul de afisare Server Explorer din meniul View al sistemului VS .NET. Se realizeaza conexiunea la baza de date introducnd parola pentru userul de conectare dorit (n fereastra de conectare care se deschide atunci cnd selectam sistemul de gestiune SQL Server). Se selecteaza tabelul Customers din baza de date Northwind si se trage n formular. La aceasta operatie generatorul de cod genereaza obiectele sqlConnection1 (instanta a clasei SqlConnection) si sqlDataAdapter1 (instanta a clasei SqlDataAdapter), care sunt plasate sub formular (cnd este editat n modul Design). 2. Se editeaza proprietatea ConnectionString a obiecului sqlConnection1 pentru conectarea la baza de date aleasa. Nu uitati sa adaugati n sirul de conectare parola pentru utilizatorul ales (deoarece generatorul de cod nu face automat acest lucru): pwd=parola; 3. Se selecteaza obiectul sqlDataAdapter1 si se face click pe link-ul Generate Dataset (care este afisat sub lista de proprietati, asa cum se vede n figura de mai jos). Se accepta datele implicite din fereastra de dialog care se deschide, iar la nchiderea acesteia (cu butonul OK) se creeaza obiectul dataSet11, care este afisat sub formular. 4. Se creeaza un obiect DataView prin selectarea acestuia n pagina Data a ferestrei de instrumente ToolBox si tragere n formular; dupa creare obiectul (cu numele implicit dataView1) este afisat sub formular, asa cum se vede n figura de mai jos. 5. Se seteaza proprietatea Tables a obiectului dataView1 (n fereastra Properties) selectnd valoarea dataSet11.Customers din lista derulanta din partea dreapta a paginii Properties. De

76

asemenea, se seteaza proprietatea RowFilter la valoarea: Country = UK si proprietatea Sort la valoarea CustomerID (asa cum se vede n figura de mai jos). 6. Se introduce un control DataGrid , selectndu-l n pagina Windows Forms din fereastra ToolBox si tragndu-l n formular; prin aceasta operatie se creeaza obiectul cu numele implicit dataGrid1 (instanta a clasei DataGrid), care ocupa o anumita suprafata n fereastra formularului. Acesta suprafata se poate ajusta prin tragere cu mouse-ul. 7. Se seteaza proprietatea DataSource a obiectului dataGrid1 la valoarea dataView1, folosind lista derulanta din partea dreapta a proprietatii DataSource. n acest moment n fereastra mediului de dezvoltare VS .NET se afiseaza datele ca cele care se pot vedea n imaginea de mai jos. 8. Interogarea bazei de date se face n metoda Form1_Load() , care este un handler de evenimente nregistrat pentru evenimentul Load al formularului Form1. Se completeaza aceasta metoda astfel:
private void Form1_Load(object sender, System.EventArgs e){ sqlDataAdapter1.Fill(dataSet11, "Customers"); }

Se compileaza si se executa programul. n acest stadiu de dezvoltare al programului se obtine lista tuturor clientilor firmei din tara introdusa ca parametru de filtrare (UK), asa cum se poate vedea n imaginea de mai jos.

77

9. Pentru filtarea liniilor rezultatului cu un parametru oarecare (filtru), introdus n mod dinamic n cursul executiei, se foloseste un control combo-box care sa contina ca lista de elemente (Items) denumirile tuturor tarilor clientilor, din care se selecteaza tara dorita. Controlul combo-box se introduce n formular prin tragerea acestuia din fereastra ToolBox n fereastra formularului (selectat n modul Design) si se seteaza proprietatile n fereastra Properties. Liste elementelor controlului combo-box se poate seta fie ca o colectie de denumiri ale tarilor prin editarea proprietatii Items (Collection) a controlului, fie folosind o sursa de date, introdusa ca proprietate DataSource a controlului. n exemplul prezentat s-a ales cea de-a doua posibiltate, selectnd pentru proprietatea DataSource valoarea dataSet11.Customers iar pentru proprietatea DisplayMember valoarea Country. Prin aceasta setare lista de elemente continuta de combo-box este lista valorilor coloanei (atributului) Country a tabelului Customers din obiectul dataSet11. 10. Se nregistreaza un handler de eveniment de schimbare a selectiei controlului combo-box (prin dublu-click pe controlul combo-box) si se defineste acest handler astfel:
private void comboBox1_SelectedIndexChanged(object sender, System.EventArgs e) { object item = comboBox1.SelectedItem; string selectedText = comboBox1.GetItemText(item); this.dataView1.RowFilter = "Country = \'" + selectedText + "\'"; sqlDataAdapter1.Fill(dataSet11, "Customers"); }

n acest handler de citeste elementul selectat n combo-box (folosind proprietatea


SelectedItem a clasei ComboBox), se converteste ntr-un sir de caractere(obiect string) (apelnd metoda GetItemText) si se modifica filtrul obiectului DataView la valoarea acestui sir de caractere, folosind proprietatea RowFilter a clasei DataView.

11. Se completeaza metoda Form1_Load() (handlerul de eveniment de ncarcare a formulrului) astfel:


private void Form1_Load(object sender, System.EventArgs e) { // Popularea ob. DataSet11 pentru incarcarea listei de elementei // in combo-box sqlDataAdapter1.Fill(dataSet11, "Customers"); // Setarea corespunzatoare a filtrului int index = comboBox1.SelectedIndex; object item = comboBox1.SelectedItem; string selectedText = comboBox1.GetItemText(item); this.dataView1.RowFilter = "Country = \'" + selectedText + "\'"; // Filtrarea liniilor rezultatului sqlDataAdapter1.Fill(dataSet11, "Customers"); }

La executia programului se obtine o imagine ca cea de mai jos:

78

D. Afisarea datelor din doua relatii asociate ntr-un singur formular n programul urmator (Program4_21) se va prezenta modul n care se poate crea o aplicatie Windows care contine un formular de date, folosind generatorul Data Form Wizard din VS .NET. Ca exemplu, se vor folosi relatiile Customers si Orders , din baza de date Northwind, ntre care exista o asociere 1:N prin intermediul cheii straine CustomersID din relatia Orders, care refera cheia primara CustomerID din relatia Customers. n sistemul de dezvoltare .NET se folosesc denumirile de tabel parinte (parent table ) pentru relatia referita (care contine cheia primara referita de cheia straina), tabel fiu (child table ) pentru relatia care refera, iar asocierea 1:N este numita asociere parinte-fiu (parent-child). Aplicatia va afisa ntr-un formular cte o linie din tabelul parinte (Customers) prin intermediul mai multor casete de afisare (TextBox), si pentru fiecare linie din acest tabel, se afiseaza toate liniile corespunzatoare (care au valoarea cheii straine egala cu valoarea cheii primare a liniei respective) din tabelul fiu (Orders) ntr-un tabel de date (DataGrid). Pentru crearea aplicatiei se vor parcurge urmatorii pasi: 1. Selectati: File/New Project. n caseta de dialog New Project selectati Visual C# Empty Project si introduceti numele dorit al proiectului (Program4_21 n aceasta prezentare). Deoarece se va crea ulterior un formular de date, nu este necesar ca VS .NET sa genereze la obisnuitul formular gol (blank) cu care se ncep n general aplicatiile Windows Form, este suficinta crearea n acest stadiu a unui proiect vid. Apasati butonul OK al ferestrei de dialog cu mouse-ul si VS. NET va crea un proiect vid care va fi completat n continuare. 2. Selectati Project/Add New Item. Selectati Data Form Wizard din sectiunea dreapta (Templates) a ferestrei de dialog Add New Item si introduceti numele dorit al formularului (n exemplul dat s-a folosit numele Form1). Se va afisa pagina de start a generatorului Data Form Wizard. Apasati butonul Next pentru a continua. 3. Urmatoarea pagina a generatorului, (intitulata Choose the dataset you want to use), permite definirea unui obiect DataSet; selectati optiunea Create a new dataset named, iar n caseta de text corespunzatoare introduceti numele dorit (dataSet1 n exemplul prezentat). Apasati butonul Next pentru a continua. 4. n pagina urmatoare se alege o conexiune la baza de date, fie selectnd o conexiune existenta, fie creind o conexiune noua. Apasati butonul Next pentru a continua. Urmeaza conectarea la baza de date, care necesita introducerea parolei n cmpul Password al casetei de dialog SQL Server login care se afiseaza n acest moment al generarii proiectului. Apasati butonul OK pentru a continua. 5. n continuare se vor selecta tabelele dorite (Customers si Orders) din tabelele bazei de date Northwind. Selectia se poate face fie prin dublu-click pe tabelul respectiv, fie selectndu-l n sectiunea din stnga a ferstrei (Available Items) si trimitndu-l (cu sageata spre dreapta) n sectiunea din dreapta (Selected Items). n acest stadiu, fereastra Data Form Wizard arata ca n figura de mai jos. Apasati butonul Next pentru a continua.

79

6. n pagina urmatoare se creeaza asocierea (relationship ) ntre cele doua tabele selectate. Se introduce denumirea asocierii (de exemplu customers_orders) n caseta de text Name, se selecteaza tabelul Customers ca tabel parinte (Parent Table), iar tabelul Orders ca tabel fiu (Child Table ). Se selecteaza CustomerID ca si cheie n ambele tabele (n casetele combo-box Keys). Aceasta asociere nu va fi nregistrata dect daca se apasa sageata spre dreapta, care o trimite n ferestra numita Relations (corect ar fi trebuit sa fie denumita Relationships). Apasati butonul Next pentru a continua. 7. n pagina urmatoare se selecteaza coloanele (atributele tabelelor) pe care dorim sa le afisam n formular, asa cum se vede n imaginea de mai jos:

8. n ultima pagina a generatorului se selecteaza stilul de afisare a tabelului parinte. Selectati optiunea Single record in individual control din grupul de radio-butoane How do you want to display your data si toate celelalte optiuni (casete chek-box): Add, Delete, Cancel, Navigation controls, Cancel All, care permit efectuarea operatiunilor de inserare, stergere si parcurgere a liniilor tabelelor, precum si anularea modficarilor efectuate. La apasarea butonului Finish, se ncheie operatiile de creare a formularului, care este afisat n fereastra VS, asa cum se vede n figura de mai jos.

80

Se observa ca generatorul Data Form Wizard foloseste clase OLE DB, desi se conecteaza la o baza de date SQL Server. Aceasta solutie este mai generala, deoarece se aplica pentru orice sistem de baze de date pentru care exista un provider OLE DB si majoritatea sistemelor actuale ofera un astfel de driver, dar, pentru SQL Server, este mai putin eficienta dect daca s-ar folosi clase SQL Server provider. Este pretul platit pentru operatiile de generare a codului, si acest cod nu sunt chiar simplu. Daca ne uitam n fisierul DataForm1.cs (cu optiunea View Code n meniul de context care se deschide la click cu mouse-ul pe numele fisierului n ferestra Solution Explorer), se observa ca s generat -au peste 900 de linii de cod. 9. Pentru conectarea la server este necesara introducerea parolei pentru user-ul folosit (sa). Pentru aceasta se selecteaza obiectul oleDbConnection1 prin selectare n fereastra de proiectare (sub formular) si editarea proprietatii ConnexionString, n care se introduce sub-sirul:
pwd=parola;

10. Ultimul pas necesar pentru crearea aplicatiei este introducerea (manuala) a functiei
Main() n clasa DataForm1 (din fisierul DataForm1.cs):
public static void Main(){ Application.Run(new DataForm1()); }

La executia aplicatiei se afiseaza formularul de date care este populat cu valori din baza de date Northwind la comanda (butonul) Load, asa cum se vede n imaginea de mai jos.

Se observa asocierea dintre cele doua tabele si faptul ca n formular se afiseaza valori corelate: liniei din tabelul parinte (Customers) afisata n partea de sus a formularului i corespund toate liniile care o refera (au aceeasi valoare a cheii) din tabelul fiu (Orders). Liniile din tabelul parinte (Customers) pot fi parcurse incremental n ambele sensuri (cu cele doua butoane cu sageti simple) si se poate ajunge direct la prima sau ultima linie (cu cele doua butoane cu sageti duble). De asemenea se pot actualiza, adauga sau sterge linii din tabelul parinte. E. Interogari pe mai multe relatii asociate n programul urmator (Program4_22) se prezinta modul n care se poate afisa rezultatul unei interogari aplicate pe trei relatii, si anume doua relatii puternice si o relatie de asociere, care asigura o asociere multi-la-multi ntre primele doua relatii. Ca exemplu se vor folosi tabelele Employees, Customers, Orders din baza de date Northwind din distributia SQL Server.

81

ntr-un DataDrid se vor afisa rezultatele interogarii Care sunt numele, prenumele, titlul (functia) angajatilor si tara de rezidenta a clientilor dintr-o anumita tara (data ca parametru) catre care angajatii au efectuat ordine se livrare? Pasii care se trebuie sa fie executati sunt urmatorii: 1. Creati un proiect C# Windows Application cu numele Program4_22. 2. Creati un obiect SqlDataAdapter, tragndu-l din pagina Data a ferestrei ToolBox n formular si n continuare efectuati setarile cerute de generatorul Data Adapter Configuration Wizard. n prima pagina se creaza (daca nu a fost creata mai nainte) sau se selecteaza conexiunea localhost.Nortwind.dbo (n loc de localhost generatorul afiseaza numele hostului pe care lucrati), iar n pagina a doua se selecteaza optiunea Use SQL statements. 3. n pagina urmatoare se creeaza comanda de selectie SQL, fie scriind-o n fereastra de editare, fie invocnd gneratorul Query Builder, care ofera o interfata grafica de generare a interogarii (asa cum se vede n figura de mai jos). Se adauga tabelele Customers, Orders, Employees (ntre care exista asocierile corespunzatoare) si se va genera instructiunea SQL SELECT fara clauza WHERE de filtrare a valorilor dorite, deoarece aceasta clauza se va introduce n mod dinamic n cursul executiei (printr-un control combo-box). Sintaxa folosita este o extensie a limbajului SQL standard, exprimnd jonctiunea ntre doua tabele prin INNER-JOIN

Generatoul Query Builder ne anunta ca s-a generat un DataAdapter care poate efectua numai operatii de interogare (SELECT). Se dau comenzile OK, apoi Finish. La crearea obiectului sqlDataAdapter1 , s-a creat si conexiunea necesara (obiectul sqlConnection1). Nu uitati sa adaugati parola n sirul de conectare (ConnectionString): pwd=parola; al obiectului sqlConnection1 creat. 3. Se creeaza obiectul d ataSet1 , (tragnd obiectul din pagina Data a ferestrei ToolBox n formular) si se selecteaza optiunea Untyped DataSet. Apoi se creeaza un obiect dataView1 ((tragnd obiectul din pagina Data a ferestrei ToolBox n formular). 4. Se insereaza n formular un obiect DataGrid, a carui proprietate DataSource se seteaza cu dataWiew1, iar n proprietatea CaptionText se introduce textul Employees-OrdersCustomers 5. Interogarea bazei de date se face n metoda Form1_Load() , care este un handler de evenimente nregistrat pentru evenimentul Load al formularului Form1. Se completeaza aceasta metoda astfel:
82

private void Form1_Load(object sender, System.EventArgs e){ this.sqlDataAdapter1.Fill(dataSet1); this.dataView1.Table = dataSet1.Tables[0]; }

Dupa compilare si executie se obtine imaginea de mai jos. n aceasta faza n tabel (data-grid) se afiseaza rezultatul interogarii introduse n crearea adaptorului, adica o proiectie a jonctiunii dintre cele trei tabele pe atributele date (FirstName, LastName,Title,etc).

6. Clauza de filtrare cu o conditie data ca parametru (Customers.Country = parametru) se intruduce ca filtru al obiectului dataView1 , iar valoarea parametrului se selecteaza printr-un combo-box. Pentru acesta se insereaza un combo-box (comboBox1) si mai multe obiecte prin care sursa de date a acestuia sa fie coloana Country din tabelul Customers. Se introduce un obiect dataSet2 (cu optiunea Untyped Data Set, la fel ca dataSet1), un obiect dataView2 si un obiect dataAdapter2, care se configureaza cu instructiunea SQL: SELECT DISTINCT Country FROM Customers. Apoi se completeaza metoda Form1_Load() astfel:
private void Form1_Load(object sender, System.EventArgs e) { this.sqlDataAdapter1.Fill(dataSet1); dataView1.Table = dataSet1.Tables[0]; // Populare combobox1 this.sqlDataAdapter2.Fill(dataSet2); dataView2.Table = dataSet2.Tables[0]; this.comboBox1.DisplayMember = "Country"; // Setarea corespunzatoare a filtrului int index = comboBox1.SelectedIndex; object item = comboBox1.SelectedItem; string selectedText = comboBox1.GetItemText(item); this.dataView1.RowFilter = "Country = \'" + selectedText + "\'"; // Popularea dataGrid1 this.sqlDataAdapter1.Fill(dataSet1); dataView1.Table = dataSet1.Tables[0]; }

6. Se nregistreaza un handler de eveniment de schimbare a selectiei controlului combo-box (prin dublu-click pe controlul combo-box) si se defineste acest handler astfel:
private void comboBox1_SelectedIndexChanged(object sender, System.EventArgs e) { object item = comboBox1.SelectedItem; string selectedText = comboBox1.GetItemText(item); this.dataView1.RowFilter = "Country = \'" + selectedText + "\'"; sqlDataAdapter1.Fill(dataSet1, "Customers"); }

83

La executia programului se obtine o imagine ca cea din figura de mai jos.

Aceasta este una dintre posibilitatile de creare a unei interogari parametrizate pe trei tabele corelate si, dupa cum se poate remarca, nu exista un generator (Wizard) care sa genereze automat o astfel de aplicatie si unele obiecte si configurari au fost scrise manual.

84

Exercitii - Capitolul 4
4.1 Scrieti cte un program Transact-SQL si PL/SQL pentru calculul factorialului unui numar dat.

4.2 Scrieti si executati cele doua programe care folosesc cursoare prezentate n sectiunea 4.1.1 (cursor Transact-SQL) si sectiunea 4.1.2 (cursor PL/SQL) din ndrumar si urmariti rezultatele obtinute. n tabelul ANGAJATI stergeti cteva tupluri si introduceti altele noi. La o noua executie a oricaruia din programele de mai sus, veti observa ca numarul liniilor afisate scade. Cum explicati acest lucru?
4.3 Scrieti un program (Transact-SQL sau PL/SQL) care parcurge liniile tabelului ANGAJATI, testeaza valoarea salariului angajatului si acorda marire de salariu acelor angajati care au salariul mai mic dect un salariu minim dat, astfel nct salariul acestora sa fie egal cu salariul minim impus. Sa se afiseze lista angajatilor care au primit marire de salariu (numele, prenumele, vechiul salariu, noul salariu). Sa se calculeze si sa afiseze numarul total de angajati carora li s-a marit salariul si suma totala cu care s-au marit salariile n ntreprinderea respectiva.

4.4

n propria baza de date SQL Server, Oracle sau MySQL, adaugati tabelele STUDENTI, DISCIPLINE, EXAMENE executnd un fisier script. Pentru SQL Server fisierul script este deja creat (Program_4_1.sql din ndrumar); pentru celelalte sisteme se obtine usor scriptul necesar prin adaptarea celui existent. Introduceti mai multe linii n aceste tabele. Testati cursorul Transact-SQL din Program_4_3.sql si scrieti si testati un program similar n PL/SQL.
4.5 Creati sursele ODBC pentru propria baza de date SQL Server, Oracle sau MySQL (n care ati introdus tabelul STUDENTI). n Programul 4.12 modificati numele sursei de date cu numele propriei surse ODBC pentru fiecare sistem de gestiune, numele utilizatorului si parola acestuia si executati programul. Urmariti ce informatii sunt necesare pentru conectarea la surse ODBC pentru baze de date din diferite sisteme de gestiune. 4.6 Definiti o sursa de date ODBC cu numele INTREPRINDERE pentru baza de date MS ACCESS Intreprindere.mdb din directorul de aplicatii. Compilati si executati programul Angajati (care se gaseste n directorul \Aplicatii\Capitol4\MFC\Angajati\Step1 din ndrumar). Identificati elementele de baza ale interfetei MFC-ODBC: clasa conexiunii la baza de date, (CDatabase), clasa setului de nregistrari ( AngajatiSet), clasa vedere ( AngajatiView), asocierile dintre controalele interfetei si C C variabilele membre ale clasei setului de instructiuni.

4.7

Executati pasul urmator de dezvoltare a aplicatiei MFC-ODBC Angajati pentru baza de date (Step 2). La executia aplicatiei trebuie sa obtineti o fereastra de afisare asemanatoare celei din Fig. 4. 2. Realizati pasul urmator al aplicatiei (Step 3), asa cum este descris n ndrumar. Verificati comenzile de introducere, stergere, actualizare a liniilor tabelului ANGAJATI din program.
INTREPRINDERE

4.8 Compilati si executati aplicatia SA din directorul MFC al acestui capitol, n cele doua variante Step 1 si Step 2. Urmariti n fisierele proiectului modul de definire a mai multor formulare n proiectul SDI (Step 1) si n proiectul MDI (Step 2).
4.9* Folosind aceeasi baza de date ca mai sus (INTREPRINDERE), dezvoltati o aplicatie MFC-ODBC de tip SDI pentru interogarea: Care sunt numele, prenumele si adresa furnizorilor care au livrat o componenta a carei denumire este data ca parametru? Pentru aceasta interogare se folosesc tabelele FURNIZORI,ACHIZITII,COMPONENTE, parametrul este atributul Denumire din tabelul COMPONENTE, iar instructiunea SQL care realizeaza aceasta interogare este: SELECT Nume,Prenume,Adresa FROM FURNIZORI,ACHIZITII,COMPONENTE WHERE FURNIZORI.IdFurnizor = ACHIZITII.IdFurnizor AND COMPONENTE.IdComponenta = ACHIZITII.IdComponenta AND Denumire=parametru

85

Pentru afisare folositi clasa vedere (formularul CRecordView) care se creeaza implicit la crearea proiectului. Adaugati nca doua formulare, pentru listarea tuturor furnizorilor si a tuturor componentelor si asigurati selectia ntre formulare prin comenzi de meniu (asa cum este explicat n sectiunea 4.2.2.5) 4.10* Folosind aceeasi baza de date ca mai sus ( INTREPRINDERE), dezvoltati o aplicatie MFC-ODBC pentru interogarea: Care sunt numele, prenumele si adresa clientilor care au cumparat un produs a carui denumire este data ca parametru? Pentru aceasta interogare se folosesc tabelele CLIENTI,VANZARI,PRODUSE, parametrul este atributul Denumire din tabelul PRODUSE, iar instructiunea SQL care realizeaza aceasta interogare este: SELECT Nume,Prenume,Adresa FROM CLIENTI,VANZARI,PRODUSE WHERE CLIENTI.IdClient = VANZARI.IdClient AND PRODUSE.IdProdus = VANZARI.IdProdus AND Denumire=parametru Pentru afisare folositi clasa vedere (formularul CRecordView) care se creeaza implicit la crearea proiectului. Adaugati nca doua formulare, pentru listarea tuturor clientilor si a tuturor produselor si asigurati selectia ntre formulare prin comenzi de meniu (asa cum este explicat n sectiunea 4.2.2.5) 4.11* Folosind aceeasi baza de date ca mai sus (INTREPRINDERE), dezvoltati o aplicatie MFC-ODBC de tip SDI pentru interogarea: Care sunt numele, prenumele si data nasterii angajatilor care participa la un proiect a carui denumire este data ca parametru? Pentru aceasta interogare se folosesc tabelele ANGAJATI,ACTIVITATI,PROIECTE, parametrul este atributul Denumire din tabelul PROIECTE, iar instructiunea SQL care realizeaza acesta interogare este: SELECT Nume,Prenume,DataNasterii FROM ANGAJATI,ACTIVITATI,PROIECTE WHERE ANGAJATI.IdAngajat = ACTIVITATI.IdAngajat AND PROIECTE.IdProiect = ACTIVITATI.IdProiect AND Denumire=parametru; Pentru afisare folositi clasa vedere (formularul CRecordView) care se creeaza implicit la crearea proiectului. Adaugati nca un formular, pentru listarea tuturor proiectelor si asigurati selectia ntre formulare prin comenzi de meniu (asa cum este explicat n sectiunea 4.2.2.5) 4.12* Proiectati baza de date PUBLICATII (descrisa n Exercitiul 2.9) si sursa de date ODBC corespunzatoare. Se considera urmatoarele definitii ale tabelelor: DOMENII(IdDomeniu, Denumire) CARTI(IdCarte,Titlul,DataAparitiei,Editura,Localitatea,Tara,IdDomeniu) AUTORI(IdAutor,Nume,Prenume,AnulNasterii) PUBLICATII(IdPublicatie,IdCarte,IdAutor,Ordinea) 4.13* Dezvoltati o aplicatie MFC-ODBC de tip SDI pentru interogarea Care sunt titlul, data aparitiei si editura cartilor dintr-un domeniu dat ca parametru? asupra bazei de date PUBLICATII. Pentru aceasta interogare se folosesc tabelele DOMENII si CARTI, parametrul este atributul Denumire din tabelul DOMENII, iar intructiunea SQL care realizeaza acesta interogare este: SELECT Titlul,DataAparitiei,Editura FROM CARTI, DOMENII WHERE DOMENII.IdDomeniu = CARTI.IdDomeniu AND Denumire = parametru;

4.14 Scrieti si executati aplicatiile .NET descrise n sectiunea 4.2.3 a ndrumarului. Folositi ca bibliografie Indrumarul APOO (Lucrarile 7 si 9), documentatia MSDN, documentatia SQL Server (Books OnLine) si documentatiile care nsotesc aceasta lucrare, n special editia a treia Jesse Liberty, Programming C#, OReilly 2002. 4.15* Reluati exercitiile 4.9 4.13 de interogari ale bazelor de date si realizati-le n platforma .NET.

86