Sunteți pe pagina 1din 78

Cap.

4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

CAPITOLUL 4

LIMBAJE ŞI BIBLIOTECI DE PROGRAMARE


A APLICAŢ
APLICAŢIILOR DE BAZE DE DATE

Sistemele de gestiune a bazelor de date relaţionale


prelucrează instrucţiuni (comenzi) SQL.
Limbajul SQL este un limbaj neprocedural, care permite
definirea datelor şi operaţii de manipulare a acestora, dar nu
prevede instrucţiuni de control al ordinii de execuţie a
operaţiilor. De aceea, pentru realizarea aplicaţiilor de baze de
date au fost dezvoltate o multitudine de limbaje şi biblioteci de
programare: limbaje procedurale de extensie a limbajului SQL,
limbajul SQL integrat, biblioteci de funcţii sau de clase pentru
comunicarea cu bazele de date.
Un limbaj procedural (procedural language) este o extensie
a limbajului SQL şi permite combinarea instrucţiunilor SQL cu
instrucţiunile de control al ordinii de execuţie. Astfel de
limbaje sunt folosite în principal în cadrul sistemelor de
gestiune, pentru stocarea unor proceduri de calcul, dar există şi
posibilitatea ca programele de aplicaţii să transmită SGBD
blocuri (loturi) de instrucţiuni într-un limbaj procedural.
Pentru dezvoltarea programelor de aplicaţii de baze de date
se pot aborda două tehnologii diferite, limbajul SQL integrat
într-un limbaj de nivel înalt sau interfeţe de programare a
aplicaţiilor.
În limbajul SQL integrat (Embeded SQL), instrucţiunile
limbajului SQL sunt incluse direct în codul programului sursă
scris într-un limbaj gazdă de nivel înalt (Ada, PL/1, Pascal,
Fortran, Cobol, C). Controlul fluxului de operaţii este realizat
prin instrucţiunile limbajului gazdă, iar operaţiile cu baza de
date sunt realizate prin instrucţiuni SQL.

1
Interfeţele de programare a aplicaţiilor (Application
Programming Interface - API), sunt dezvoltate ca biblioteci de
funcţii sau de clase, iar programele de aplicaţie folosesc apelul
funcţiilor prevăzute de interfaţa respectivă pentru a comunica
cu serverul bazei de date.

4.1. Limbaje procedurale de extensie a limbajului SQL


Limbajele procedurale asigură controlul ordinii de
execuţie (bucle while, instrucţiuni condiţionale if etc.) şi de
asemenea, oferă suport de creare a cursoarelor, a procedurilor
stocate , a funcţiilor definite de utilizator şi a declanşatorilor
(triggere).
Un cursor (cursor) este o structură care permite memorarea
(folosind un buffer în memorie), a unei mulţimi de linii
returnate ca rezultat, de o instrucţiune de interogare.
Programele de aplicaţii nu pot prelucra deodată toate liniile
rezultatului şi folosesc cursoare pentru extragerea individuală a
unei linii (sau a unui grup de linii) din mulţimea de linii
rezultat. În fiecare moment, într-un cursor există o poziţie
curentă (linie curentă) în mulţimea de linii rezultat. La fiecare
operaţie de extragere, se citesc una sau mai multe linii relativ la
poziţia curentă a cursorului, iar această poziţie se actualizează
conform modului de parcurgere (înainte sau înapoi). Cursoarele
se pot crea atât la nivelul limbajului SQL2 sau a extensiilor
procedurale ale acestuia, cât şi prin intermediul limbajului SQL
integrat (Embedded SQL) sau a bibliotecilor şi interfeţelor de
programare a aplicaţiilor de baze de date (de exemplu,
interfeţele ODBC şi JDBC).
O procedură stocată (stored procedure) este o procedură
care implementează o parte din algoritmii de calcul ai
aplicaţiilor şi este memorată în baza de date la fel ca şi alte
obiecte ale bazei de date. Procedurile stocate sunt compilate şi
optimizate de sistemul de gestiune o singură dată, atunci când
Cap.4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

sunt folosite prima oară şi ramân memorate în server pentru


oricâte apeluri ulterioare.
O funcţie definită de utilizator (user-defined function), este
o funcţie memorată în baza de date, la fel ca o procedură
stocată; diferenţa între acestea este că o funcţie returnează
întotdeauna o valoare şi poate fi folosită direct în expresii, pe
câtă vreme o procedură stocată poate să nu returneze nici o
valoare.
Un trigger este o procedură stocată cu o funcţionare
specială, care se declanşează automat atunci când se efectuează
o operaţie de actualizare a unei relaţii. Prin triggere se pot
specifica şi impune procedural constrângerile explicite, cum
sunt dependenţele de date care nu sunt determinate de chei ale
relaţiilor. De asemenea, triggerele mai sunt folosite şi pentru
generarea automată a unor valori care rezultă din valori ale
altor atribute, precum şi pentru jurnalizarea transparentă a
evenimentelor sau culegerea de date statistice în legatură cu
accesarea relaţiilor.
Majoritatea sistemelor de gestiune sunt prevazute cu cel
puţin 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 şi
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
instrucţiuni transmise sistemului prin intermediul unei aplicaţii
sau a unui program utilitar de acces interactiv la baza de date.
Programele utilitare de acces interactiv din distribuţia SQL
Server sunt isql sau osql (la nivel de linie de comandă) şi SQL
Query Analizer (care oferă şi o interfaţă grafică). De asemenea,
numeroase operaţii de definire a tabelelor şi a altor

97
caracteristici ale bazelor de date se pot face din consola de
administrare (Enterprise Manager) a sistemului SQL Server.

4.1.1.1. Elementele de bază ale limbajului


Transact-SQL Loturi de prelucrare. Un lot (batch) constă
dintr-o secvenţă de instrucţiuni Transact-SQL terminată cu
comanda GO. Există mai multe reguli de grupare a
instrucţiunilor în loturi. De exemplu: orice instrucţiune
CREATE trebuie să fie prima în lot; de aceea nu pot fi grupate
în acelaşi lot instrucţiunile 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), în plus, asigură
transferul datelor către şi de la tabelele bazei de date.
Variabilele locale au ca domeniu de definiţie lotul, procedura
sau blocul în care au fost declarate.
O variabilă locală se declară folosind instrucţiunea
DECLARE care specifică un identificator (un nume care
trebuie să înceapă cu caracterul @) şi tipul variabilei. Sintaxa de
declarare arată astfel:
DECLARE @nume_variabila tip_date
Limbajul Transact-SQL suportă toate tipurile de date
prevăzute în standardul SQL2, în plus, permite definirea de
către utilizator a unor noi tipuri de date. Iniţializarea
variabilelor locale se poate face printr-o comandă SELECT, cu
următoarea sintaxă:
SELECT @nume_variabila = expresie
Instrucţiuni SQL. Limbajul Transact-SQL suportă toate
instrucţiunile SQL, cu unele modificări de sintaxă, astfel încât
să poată fi folosite în combinaţie cu variabilele locale ale
programului. De exemplu, forma modificată a instrucţiunii
SELECT, prin care se asignează variabile locale cu valori ale
unor atribute selectate, este:
SELECT @var1 = col1, @var2 = col2, ...
@varn = coln
FROM lista_tabele WHERE conditie
O astfel de instrucţiune este utilă pentru interogările care
returnează o singură linie, deoarece variabilele locale sunt
setate cu valorile coloanelor primei linii a rezultatului, iar
valorile din celelalte linii se pierd (nemaifiind loc unde să fie
memorate). Atunci când o interogare returnează o mulţime de
linii se poate folosi un cursor, aşa cum va fi prezentat în
continuare.
Instrucţiuni de control a ordinii de execuţie. Ordinea de
execuţie a instrucţiunilor unui lot sau a unei proceduri stocate
este controlată prin următoarele instrucţiuni de control:
BEGIN...END IF...ELSE WAITFOR BREAK
GOTO RETURN WHILE CONTINUE
Semnificaţia acestor instrucţiuni este cea generală,
cunoscută din alte limbaje de programare, cu anumite
particularităţi. Instrucţiunile de control nu pot depăşi graniţele
unei lot de execuţie sau a unei proceduri stocate.

4.1.1.2. Cursoare Transact – SQL


În limbajul Transact-SQL se pot defini cursoare cu
următoarea instrucţiune:
DECLARE nume_cursor CURSOR [optiuni] FOR
instructiune_select
Există mai multe opţiuni care se pot seta şi care definesc
tipul cursorului. Semnificaţia acestor opţiuni este descrisă în
manualul sistemului (Books Online), iar în programul care
urmează este dat un exemplu de creare şi utilizare a unui cursor
Transact-SQL.
La deschiderea unui cursor (prin comanda OPEN) se
execută interogarea respectivă (instrucţiunea SELECT) şi
rezultatul (mulţimea de linii) se încarcă în cursor. După
încărcarea cursorului, se pot extrage liniile prin operaţii de
extragere cu instrucţiunea FETCH, a cărei sintaxă simplificată
arată 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 şi
coloanele cursorului este implicit poziţională: valoarea din
prima coloană se înscrie în prima variabilă (@var1), valoarea
din a doua coloană se înscrie în variabila a doua (@var2) etc.
Ordinea coloanelor cursorului este ordinea coloanelor din
instrucţiunea SELECT asociată cursorului. Pentru testarea
stării unui cursor se apelează funcţia globală @@FETCH_STATUS,
care raportează starea ultimei instrucţiuni FETCH executate
pentru conexiunea curentă la server. Valoarea returnată este 0,
dacă extragerea a fost efectuată cu succes, 1, dacă a survenit o
eroare şi 2, dacă nu mai sunt date în mulţimea de linii rezultat.
După terminarea operaţiilor asupra datelor cursorului, acesta se
închide cu comanda CLOSE, iar instrucţiunea DEALLOCATE
şterge structurile de date ale cursorului şi eliberează memoria.
Programul următor foloseşte un cursor pentru a afişa
numele şi prenumele angajaţilor al căror identificator
(IdAngajat) are valori cuprinse între 1 şi numărul maxim de
linii ale tabelului respectiv (tabelul ANGAJATI).
La deschiderea cursorului (cursor_angajati), acesta
se încarcă cu toate liniile tabelului ANGAJATI. Aceste linii
sunt extrase una cîte una, cu câte o comandă FETCH, care
înscrie valorile atributelor din linia curentă a cursorului în
variabilele locale (@id_angajat,@nume,@prenume).
Pentru fiecare linie extrasă se testează valoarea atributului
IdAngajat (reţinută în variabila locală @id_angajat) şi se
tipăresc datele (identificator, nume, prenume) din acele linii
care au acest atribut mai mic sau egal cu numărul 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 oară în versiunea Oracle 6.0 în 1991 şi a fost dezvoltat
continuu şi folosit intens, inclusiv în cele mai recente versiuni,
Oracle9i şi Oracle 10g.

4.1.2.1. Elementele de bază ale limbajului


PL/SQL
Blocuri PL/SQL. Unitatea de programare PL/SQL este
blocul, care oferă posibilitatea de dezvoltare modulară a
programelor. Există patru tipuri de blocuri: blocuri anonime,
proceduri stocate, triggere şi funcţii definite de utilizator.
Blocurile anonime pot fi executate imediat, de obicei dintr-un
program utilitar interactiv, cum este SQL*Plus şi nu sunt
memorate în baza de date. Acestea sunt echivalente loturilor de
prelucrare din Transact-SQL. Celelalte blocuri (procedurile
stocate, triggerele şi funcţiile definite de utilizator) sunt
memorate în baza de date.
Un bloc anonim PL/SQL este compus din mai multe
secţiuni şi are următoarea structură:

În prima secţiune (opţională) se declară variabilele locale


ale blocului folosind instrucţiunea DECLARE. Fiecare variabilă
se declară printr-un nume, urmat de tipul de date şi de
caracterul de încheiere (punct şi virgulă). Opţional, orice
variabilă poate fi iniţializată la declarare folosind operatorul de
asignare (:=) sau poate avea o valoare implicită, stabilită
folosind cuvântul cheie DEFAULT. Tipurile de date care pot fi
folosite în limbajul PL/SQL sunt tipurile Oracle SQL, ca
number, varchar2, char etc. Tot în această secţiune se
pot declara cursoare, cu o sintaxă care va fi prezentată ulterior.
Secţiunea principală a unui bloc PL/SQL este compusă din
instrucţiuni executabile încadrate de instrucţiunile
BEGIN...END. Dintre instrucţiunile executabile, o parte sunt
chiar instrucţiuni SQL de manipulare a datelor, cu o sintaxă
modificată, astfel încât să poată fi folosite în combinaţie cu
variabile locale ale programului. Alte instrucţiuni sunt
instrucţiuni de control a execuţiei programului. În partea finală
a acestei secţiuni se plasează (opţional) şi rutinele de tratare a
excepţiilor.
Instrucţiuni SQL. Limbajul PL/SQL nu admite decât
instructiuni SQL de manipulare a datelor. Dintre acestea,
instrucţiunea SELECT se poate folosi sub următoarea formă
generală:
SELECT lista_coloane INTO
lista_variabile
FROM lista_tabele [WHERE conditie]
[optiuni]
Deşi clauza WHERE este opţională, ea este necesară pentru
a se asigura că rezultatul are o singură linie, iar valorile din
coloanele selectate sunt atribuite (prin corespondenţă
poziţională) variabilelor din lista de variabile introdusă după
cuvântul cheie INTO.
Instrucţiunea SELECT prezentată în continuare asignează
variabilelor m_nume şi m_prenume valorile atributelor
(coloanelor) Nume şi Prenume din linia care are valoarea
cheii primare IdAngajat egală cu variabila locală
id_angajat:
SELECT Nume,Prenume INTO m_nume,
m_prenume
FROM ANGAJATI WHERE IdAngajat =
id_angajat;
Instrucţiunile SQL INSERT şi UPDATE se pot integra cu
uşurinţă în limbajul PL/SQL şi 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;
O precauţie specială trebuie să fie luată de programatori
pentru a evita ca variabilele locale şi coloanele din tabele să
aibă aceleaşi nume, deoarece astfel de situaţii sunt admise ca
legale de către compilatorul PL/SQL, dar produc rezultate
ambigue.
Instrucţiuni de control al execuţiei. Ca şi în alte limbaje
procedurale, ordinea de execuţie a instrucţiunilor unui program
PL/SQL poate fi controlată prin mai multe instrucţiuni de
control:
IF conditie THEN ...[ELSE ...] END IF;
CASE
FOR conditie LOOP
WHILE conditie LOOP
LOOP ... EXIT WHEN ... conditie
Semnificaţia acestor instrucţiuni este asemănătoare cu cea
din limbajele de programare cunoscute, cu unele aspecte mai
deosebite care vor fi prezentate pe scurt în continuare. În toate
aceste instrucţiuni condiţia care se testează este o expresie care
se evaluează la o valoare de tip Boolean (TRUE sau
FALSE).
Instrucţiunea IF...THEN permite execuţia condiţionată a
unor instrucţiuni. Dacă condiţia este TRUE, se execută toate
instrucţiunile dintre cuvintele cheie THEN şi ELSE sau între
THEN şi END IF. Clauza ELSE este opţională şi ea este
urmată de instrucţiunile care trebuie să fie executate dacă
valoarea condiţiei este FALSE:
IF x > 100 THEN x := 100;
ELSE y := x;
END IF;
Instrucţiunile FOR...LOOP, WHILE...LOOP şi
LOOP...WHEN se folosesc pentru execuţia repetată a unui
grup de instrucţiuni.
De exemplu, instrucţiunea FOR...LOOP poate fi folosită
pentru execuţia repetată a grupului de instrucţiuni cuprins între
cuvintele cheie LOOP şi END LOOP atâta timp cât contorul
(care este o variabilă de tip întreg, locală acestei construcţii şi
declarată automat) este cuprins între două valori (minimă şi
maximă); la fiecare iteraţie contorul este incrementat automat
cu 1 (sau decrementat cu 1, dacă este introdusă opţiunea
REVERSE). Această instrucţiune are sintaxa:
FOR contor IN [REVERSE]
valoare_minima .. valoare_maxima LOOP
instructiuni;
END LOOP;
Instructiunea LOOP crează o bucla infinită din care se
poate ieşi cu instrucţiunea EXIT, care poate fi invocată fie într-
un bloc de condiţie IF, fie cu clauza WHEN. Forma generală a
instrucţiunii LOOP este:
LOOP
instructiuni
[IF conditie THEN EXIT | EXIT WHEN
conditie] ;
instructiuni;
END LOOP
Tratarea excepţiilor. Partea finală (opţională) a unui bloc
PL/SQL este secţiunea EXCEPTION care conţine una sau mai
multe rutine de tratare a excepţiilor. Tratarea excepţiilor în
limbajul PL/SQL este asemănătoare cu cea din limbajele C++
şi Java: atunci când apare o condiţie de eroare, execuţia
normală a programului se opreşte şi controlul este pasat rutinei
(handler) de tratare a excepţiei. Ca şi în C++ (şi spre deosebire
de Java), tratarea excepţiilor este opţională în PL/SQL.

4.1.2.2. Cursoare PL/SQL


În limbajul PL/SQL un cursor se declară în secţiunea
DECLARE a unui bloc. Forma cea mai simplă a instrucţiunii
de declarare a unui cursor este:
CURSOR nume_cursor IS instructiune_select;
Extragerea liniilor cursorului se realizează cu
instrucţiunea: FETCH nume_cursor INTO
lista_variabile;
Ca exemplu, în programul următor se defineşte un cursor
PL/SQL cu o funcţionare asemănătoare programului precedent
(afişează numele şi prenumele angajaţilor care au
identificatorul (cuprins între 1 şi numărul total de linii ale
tabelului ANGAJATI).

Pentru aflarea stării unui cursor se testează atributul


acestuia %FOUND, care este setat după fiecare operaţie de
FETCH, la valoarea TRUE, dacă a fost extrasă o linie şi cu
valoarea FALSE dacă nu s-a putut extrage nici o linie.
Alternativ, se poate folosi atributul %NOTFOUND, care are
valoarea TRUE atunci când operaţiunea FETCH a eşuat.

4.2. Interfeţe de programare a aplicaţiilor de baze de


date
Interfeţele de programare a aplicaţiilor (API) reprezintă
cea mai cunoscută tehnică de dezvoltare a aplicaţiilor de baze
de date, fiind mult mai utilizată decât tehnica de programare în
limbajul SQL integrat, deoarece programele rezultate sunt mai
flexibile şi mai uşor de dezvoltat şi de întreţinut. Ca interfeţe de
programare a bazelor de date, există atât interfeţe specifice,
oferite de diferite SGBD, cât şi interfeţe cu un grad mare de
generalitate, care pot fi folosite pentru mai multe tipuri de
SGBD, cum sunt interfeţele ODBC (Open DataBase
Connectivity ) sau JDBC (Java DataBase Connectivity ).

4.2.1. Interfaţa ODBC


Tehnologia ODBC (Open DataBase Connectivity) oferă o
interfaţă de acces la baze de date relaţionale independentă de
SGBD folosit. Această independenţă se obţine prin intermediul
unor drivere, care sunt specifice fiecărui SGBD, în timp ce
funcţiile de conectare şi interogare a bazei de date folosite în
programul aplicaţie sunt aceleaşi, definite de standardul
ODBC. Administratorul de drivere încarcă driverul specific
sursei de date folosite de aplicaţie. O sursă de date stabileşte un
nume al bazei de date, împreună cu alte informaţii (tipul
SGBD, utilizatorul etc.).
Pentru a folosi interfaţa ODBC, trebuie să fie instalat
driverul pentru SGBD necesar.
Unele drivere ODBC se instalează (dacă se selectează
opţiunea respectivă) atunci când se instalează unele sisteme de
programe, iar altele trebuie să fie instalate separat. Pentru orice
tip de SGBD pentru care este instalat un driver ODBC se poate
defini o sursă de date care atribuie un nume (alte opţiuni,
depinzând de driverul instalat) unei baze de date de acel tip.
Pentru dezvoltarea unei aplicaţii de baze de date cu interfaţa
ODBC se parcurg următorii paşi:
 Se crează mai întâi baza de date dorită (sau se foloseşte o
bază de date existentă).
 Se defineşte sursa de date ODBC, corespunzătoare bazei
de date.
 Se dezvoltă aplicaţia client care va accesa baza de date ca
sursă ODBC înregistrată.
4.2.1.1. Definirea surselor ODBC
Pentru înregistrarea unei baze de date ca sursă de date
ODBC (Data Source Name - DSN) se foloseşte administratorul
ODBC în felul urmator:
 În Control Panel, se selectează comanda Data Sources
(ODBC) din grupul de comenzi Administrative Tools.
 În caseta de dialog ODBC Data Source Administrator se
selectează pagina System DSN.
În această pagină se selectează comanda Add, pentru
adăugarea unei noi surse de date.
 În caseta de dialog Add Data Sources, se alege driverul
ODBC corespunzător (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 selectează tipul driverului Oracle in
OraHome 92 şi se stabileşte numele sursei. Pentru serviciul de
nume (TNS Service Name) se introduce denumirea acestuia aşa
cum a fost stabilit la instalarea serverului şi acest serviciu
trebuie să fie specificat în client. Dacă instalarea a fost făcută
corect, atunci în fişierul tnsnames.ora din client
(ora92\tnsnames.ora) trebuie să existe o intrare care conţine
exact datele serviciului de nume afişate de programul Net
Manager din server. Serviciul de nume are denumirea
server...windowsnt.tech.pub.ro şi se conectează la serviciu l
Oracle (server12.tech.pub.ro) pe portul ..... Dacă, la definirea
sursei ODBC pentru Oracle testul de conexiune eşuează, atunci
se poate adăuga manual această intrare în fişierul tnsnames.ora.
La definirea sursei se stabileşte de asemenea şi numele
utilizatorului (user ID).
Pentru sistemul SQL Server la definirea unei surse de date
ODBC se precizează numele serverului, modul de
autentificare, contul utilizator şi parola acestuia.
Pentru Microsoft Access, în câmpul Data Source Name din
caseta de dialog ODBC Setup se scrie numele sursei (de
exemplu IntreprindereAccess), iar în grupul Database,
alegerea opţiunii Select permite localizarea pe disc a fişierului
INTREPRINDERE.mdb creat cu Microsoft Access.
Pentru MySQL, se completează mai multe informaţii de
configurare a sursei de date: Windows DNS Name (numele
sursei, prin care va fi accesată din programul client); numele
bazei de date, al utilizatorului, parola şi portul (dacă nu este
portul implicit al serverului MySQL, 3306).
Numele surselor de date se vor stabili diferit pentru fiecare
utilizator şi pentru fiecare sistem de gestiune în parte. De
exemplu, dacă utilizatorii au conturile user1, user2 etc,
atunci se pot crea sursele de date ODBC cu numele:
User1Oracle, User1SQLServer, User1MySQL,
User2Oracle, User2SQLServer, User2MySQL etc.
Dacă utilizatorul sistemului de operare Windows are
drepturi restricţionate atunci sursele ODBC trebuie să 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.
Există tipuri de date pentru numere (de exemplu
SQLSMALLINT, care este un număr întreg pe 16 biţi), pentru
tipul returnat de funcţii (SQLRETURN) pentru tipuri de date
pentru identificatori (handle) (SQLHANDLE). La rândul lui,
identificatorii SQLHANDLE pot avea mai multe forme:
 HENV - identificator al mediului în care se desfăşoară
operaţiile cu sursa de date ODBC.
 HDBC - identificator al unei conexiuni cu o sursă de
date.
 HINST - identificator al unei instructiuni SQL.
Într-un program pentru interfaţa ODBC trebuie să fie
definiţi astfel de identificatori (de mediu, de conectare şi de
instrucţiune) şi fiecare identificator trebuie să fie alocat,
operaţie care se efectuează prin apelul funcţiei
SQLAllocHandle():
SQLRETURN SQLAllocHandle( SQLSMALLINT
HandleType,
SQLHANDLE InputHandle,SQLHANDLE
*OutputHandlePtr);
Primul argument al acestei funcţii (HandleType) este o
valoare care indică tipul identificatorului care se va aloca şi
poate lua una din valorile SQL_HANDLE_ENV,
SQL_HANDLE_DBC sau SQL_HANDLE_STMT, pentru
alocarea unui identificator de mediu, de conexiune sau de
instrucţiune. Cel de-al doilea argument al funcţiei este
identificatorul de intrare (InputHandle) care trebuie să 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 instrucţiune).
Cel de al treilea argument (OutputHandlePtr) este un
pointer care indică locul unde funcţia va depune identificatorul
alocat. Pentru un identificator (handle ) alocat corect, funcţia
returnează unul din codurile SQL_SUCCESS sau
SQL_SUCCESS_WITH_INFO. În caz de eroare, funcţia
returnează unul din codurile SQL_INVALID_HANDLE sau
SQL_ERROR.
Mai întâi se alocă un identificator (handle ) de mediu
(henv) şi apoi se setează atributele acestuia cu comanda
SQLSetEnvAttr(). După aceasta, în mediul ODBC definit,
se alocă un identificator de conexiune (hdbc) şi se încearcă
conectarea la sursa de date prin apelul funcţiei
SQLConnect(). Dacă conectarea reuşeşte, pentru
conexiunea respectivă se alocă un identificator de instrucţiune
(hstmt), după care pot fi executate instrucţiuni (comenzi)
SQL.
O instrucţiune SQL se construieşte ca un şir de caractere
care conţine o comandă SQL şi parametrii acesteia. Interfaţa
ODBC oferă două moduri de construire a instrucţiunilor SQL:
instrucţiuni SQL statice, care sunt cunoscute la compilare, sunt
codate în programul executabil şi execută întotdeauna aceleaşi
operaţii şi instrucţiuni SQL dinamice, care se construiesc în
timpul execuţiei programului şi conţin date care nu sunt
cunoscute la compilare.
După ce au fost construite într-una din aceste două forme,
instrucţiunile SQL sunt transmise ca argumente unor funcţii
ODBC care le execută şi returnează rezultatul (dacă există).
Instrucţiunile SQL statice pot fi executate direct, folosind
funcţia SQLExecDirect(), sau pot fi mai întâi pregatite cu
funcţia SQLPrepare() şi apoi executate, posibil de mai
multe ori, cu funcţia SQLExecute(). În general, este mai
eficient ca o instrucţiune SQL să fie mai întâi pregătită şi apoi
executată, dacă instrucţiunea respectivă se execută de mai
multe ori. Prototipurile funcţiilor de execuţie a instrucţiunilor
SQL statice sunt:
SQLRETURN SQLExecDirect(SQLHSTMT hstmt,
SQLCHAR *StatementText,SQLINTEGER
TextLength);
SQLRETURN SQLPrepare(SQLHSTMT hstmt,
SQLCHAR *StatementText,SQLINTEGER
TextLength);
SQLRETURN SQLExecute(SQLHSTMT hstmt);
Funcţiile SQLExecDirect() şi SQLPrepare() au
ca parametri identificatorul instrucţiunii ODBC (hstmt),
variabila (şir de caractere) care conţine instrucţiunea SQL ce
trebuie să fie executată sau pregătită (StatementText) şi
lungimea acestui şir de caractere (TextLength), care poate
lua şi valoarea SQL_NTS, pentru şirurile de caractere terminate
cu NULL. Funcţia SQLExecute() are un singur parametru,
un handle al instrucţiunii ODBC (hstmt).
În Programul 4.12 se pot remarca cele două moduri de
execuţie a instrucţiunilor SQL statice. Prima instrucţiune
(UPDATE) este executată folosind funcţia SQLExecDirect();
cea de -a doua instrucţiune (SELECT) este pregătită cu funcţia
SQLPrepare() şi apoi executată cu funcţia SQLExecute().

Acest program este un proiect MSVC 6.0, de tipul Win32


Console Application şi permite conectarea la o sursă de date
ODBC cu un nume dat. Pentru conectarea la o sursă de date
MS Access este suficient numele sursei; pentru conectarea la
surse Oracle, SQL Server sau MySQL mai este necesar numele
unui cont şi parola de conectare. De asemenea, pentru SQL
Server trebuie ca baza de date a sursei ODBC să fie baza de
date implicită a acelui cont.
Programul asigură executarea diferitelor instrucţiuni SQL,
iar pentru o instrucţiune SELECT care returnează o mulţime
de linii, se extrag liniile cu funcţia SQLFetch() şi se
afişează valoarea fiecărui atribut al fiecărei linii folosind
funcţia SQLGetData(). În această situaţie, mulţimea de linii
rezultat returnate reprezintă un cursor, care este creat implicit
de driverul ODBC. De aceea, în aplicaţiile ODBC, foarte rar se
definesc în mod explicit cursoare (deşi este posibil acest lucru)
şi se folosesc cursoarele implicite create de driver.

4.2.2. Dezvoltarea aplicaţiilor 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 bibliotecă C++ de dezvoltare a aplicaţiilor în sistemele
de operare Windows. Interfaţa ODBC este o interfaţă de funcţii
C (deci nu este orientată pe obiecte), iar biblioteca MFC oferă
o extensie obiect-orientată a interfeţei ODBC. Biblioteca MFC
încapsulează funcţiile ODBC, grupându-le în mai multe clase
prin care se realizează operaţiile de comunicaţie cu baza de
date într-un mod mai structurat şi mai simplu.
Folosind interfaţa ODBC şi clasele MFC ODBC, poate fi
accesată orice sursă de date, locală sau la distanţă, pentru care
s-a definit o sursă ODBC. În această lucrare se va dezvolta o
aplicaţie de acces la o bază de date folosind interfaţa ODBC
prin intermediul bibliotecii MFC.
Aplicaţia se referă la baza de date INTREPRINDERE care
a fost înregistrată ca sursă ODBC cu numele
Intreprindere. Aplicaţia MFC de baze de date se va crea
în trei paşi de dezvoltare:
 Crearea unei aplicaţii minimale cu suport pentru baze de
date.
 Adăugarea unei interogări parametrizate.
 Extinderea intefeţei pentru adăugarea, actualizarea şi
ştergerea înregistrărilor.

4.2.2.1. Crearea unei aplicaţii minimale cu suport


pentru baze de date
Clasele MFC care se folosesc în aplicaţiile de baze de date
prin interfaţa ODBC sunt:
CDatabase, CRecordset, CRecordView. Clasa
CDatabase specifică conexiunea la o bază de date prin
intermediul numelui DSN al acesteia, înregistrat în ODBC.
Un obiect de tip CRecordset reprezintă un set de
înregistrări selectate dintr-o sursă de date.
Obiectul de tipul CRecordset conţine o selecţie a
înregistrărilor dintr-unul sau mai multe tabele ale bazei de date,
proiectate pe una sau mai multe coloane.
Obiectul vedere asociat acestor înregistrări (de tip
CRecordView) corespunde unei vederi care este un formular
şi permite înglobarea controalelor de editare sau de afişare. Un
astfel de obiect are atasată o resursă de tip dialog, care permite
afişarea / editarea câmpurilor unui set de înregistrări.
Un obiect de tip CRecordView este asociat atât cu un
obiect de tip CRecordset cât şi cu resursa de dialog
asociată. Clasa CRecordset oferă suport pentru navigarea
prin seturile de înregistrări, folosind comenzile Move First,
Move Next, Move Previous si Move Last, utilizând
un cursor, care este actualizat în mod corespunzător de aceste
comenzi. Când este modificată valoarea cursorului şi se trece la
altă înregistrare, valoarea din câmpul corespunzator este
actualizată automat.
La crearea unei aplicaţii se va specifica o sursă de date
ODBC şi un tabel din baza de date corespunzatoare.
AppWizard va crea o pereche de clase: o clasă pentru setul de
înregistrări şi o clasă vedere pentru vizualizarea acestora, în
următorii paşi:
 Se crează un proiect SDI numit Angajati.
 În caseta de dialog AppWizard-Step2 se selectează
opţiunea Database View Without File Suport; se apelează Data
Source; în caseta de dialog Database Options se selectează
ODBC, iar din listă se selectează Intreprindere; în caseta
de dialog Select Database Tables se selectează tabelul
ANGAJATI.
După ce AppWizard a creat proiectul Angajati, se poate
folosi ClassView pentru a vedea o reprezentare grafică a
claselor şi a componentelor acestora, create de AppWizard în
mod implicit. De asemenea, cu ClassWizard se pot vedea
legăturile create de AppWizard între variabilele membru şi
câmpurile tabelului ANGAJATI.
Clasa pentru setul de înregistrări CAangajatiSet.
AppWizard a legat toate coloanele tabelului Angajaţi de
variabilele membru ale clasei CAngajatiSet. Acestea se
numesc "variabile membru corespunzătoare câmpurilor" (field
data members). AppWizard le denumeşte în mod automat, pe
baza numelor coloanelor din tabelul ANGAJATI. De asemenea,
AppWizard atribuie tipul corect de date C++ sau MFC pentru
aceste variabile membru, în funcţie de tipul coloanei.
Clasa vedere CAngajatiView. Funcţia membru a clasei
de baza CRecordView:OnInitialUpdate()deschide
baza de date dacă aceasta nu era deja deschisă, apoi deschide
setul de înregistrări şi iniţializează formularul, apelând funcţia
CFormView::OnInitialUpdate().
Clasa document CAngajatiDoc. În alte tipuri de
aplicaţii, documentul păstrează datele şi le stochează într-un
fişier pe disc prin serializare. Într-o aplicaţie pentru baze de
date, datele sunt stocate în baza de date, iar utilizatorul le vede
ca înregistrări. O astfel de aplicaţie nu are nevoie de un fişier
de stocare. Prin urmare, un document al unei aplicaţii pentru
baze de date nu este folosit ca suport pentru serializare. Rolul
clasei document în aplicaţia SA este acela de a păstra setul de
înregistrări:
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
înregistrări este construit şi eliminat automat odată cu obiectul
document. Clasa document poate păstra un număr oricât de
mare de obiecte de tip set de înregistrări. Alegerea opţiunii
Database without file support (baza de date fără suport pentru
fişiere) face ca în meniul File să nu mai existe comenzile New,
Open, Save sau Save as.
Pe lânga aceste clase, AppWizard a creat şi o resursă de tip
dialog, numită IDD_ANGAJATI_FORM, pe care clasa
CAngajatiView, derivată din CRecordView, o foloseşte
pentru a afişa controalele de editare sau de afişare a datelor.
Deoarece CRecordView este derivată din CFormView,
zona client a unei vederi din clasa CRecordView este
ocupată de o resursă de dialog.
Se configurează această resursă de dialog adaugând şapte
controale de editare corespunzatoare coloanelor tabelului
ANGAJATI:
IdAngajat,Nume,Prenume,..Salariu,IdSectie
şi controalele statice corespunzătoare. O regulă des folosită la
proiectarea interfeţelor cu utilizatorul a aplicaţiilor pentru baze
de date este aceea de a nu i se permite utilizatorului să modifice
câmpurile care fac parte din cheia primară sau dintr-o cheie
străină, pentru a nu viola constrângerile implicite ale bazei de
date şi de aceea se protejează la scriere controalele de editare
Id şi IdComp, selectându-se caseta de validare Read Only
din pagina Styles a ferestrei de proprietăţi.
În mod normal, se foloseşte ClassWizard pentru a lega
controalele dintr-o casetă de dialog (sau dintr-un formular) de
variabilele membru ale clasei derivate din CDialog (sau
CFormView).
Totuşi, în cazul clasei CRecordView, nu se leagă
controalele formularului de datele membru ale acestei clase, ci
de datele membru ale clasei pentru setul de înregistrări. Clasa
derivată din CRecordView, în acest caz CAngajatiView,
are o variabilă membru numită m_pSet care este un pointer
la CAngajatiSet, clasa pentru setul de înregistrări a
aplicaţiei. Legăturile ce pornesc de la controalele formularului
ajung, prin intermediul variabilei m_pSet, la variabilele
membru corespunzătoare ale clasei CAngajatiSet. În
fereastra editorului de resurse dialog, ţinând tasta Ctrl apăsată,
executarea unui dublu-clic pe un controlul de editare conduce
la apariţia casetei de dialog AddMember Variable din
ClassWizard, propunându-vă un nume în câmpul Member
Variable Name. Din caseta combobox se poate alege numele
coloanei dorite. Se repetă această operaţie pentru fiecare din
celelalte controale de editare ale formularului.
La execuţia aplicaţiei se deschide setul de înregistrări de tip
CAngajatiSet, care va selecta înregistrările din tabelul
ANGAJATI al bazei de date INTREPRINDERE. Prima
înregistrare devine cea curentă. Formularul aplicaţiei afişează
în controale valorile din înregistrarea curentă. Meniul Record
conţine comenzile First Record, Previous
Record, Next Record si Last Record, cărora le
corespund butoane din bara cu instrumente, care pot fi folosite
pentru parcurgerea liniilor tabelului ANGAJATI (conţinute în
obiectul din obiectul CAngajatiSet).

4.2.2.2. Crearea unei interogari parametrizate


Deşi cu AppWizard se crează iniţial o aplicaţie care are
numai o clasă CRecordset şi o clasă vedere
CRecordView, ulterior se poate folosi ClassWizard pentru a
adăuga mai multe astfel de clase.
Diferite clase vedere pot "vedea" acelaşi set de înregistrări
şi invers, mai multe seturi de înregistrări pot fi "văzute" de
aceeaşi clasă vedere, dintre care unul singur este setul primar
de înregistrări.
Pasul al doilea al aplicaţiei (Step 2) ilustrează folosirea a
două seturi de înregistrări într-o singură vedere, pentru
realizarea unei interogări parametrizate. Se va adaugă un obiect
set de înregistrări pentru tabelul SECTII, care va fi folosit
pentru completarea unei liste din care să se poată selecta o
anumită secţie. Astfel, clasa CAngajatiView va avea o
asociere cu obiectul CAngajatiSet (vederea va afişa o
înregistrare din CAngajatiSet), în timp ce lista secţiilor
este asociată celui de-al doilea set de înregistrări,
CSectiiSet. În clasa CAngajatiSet vor fi selectaţi
numai acei angajaţi care aparţin unei anumite secţii, în loc să
fie selectaţi toţi angajaţii, ai tuturor secţiilor. Interogarea
corespunzătoare acestei funcţionări este:
SELECT IdSectie,Nume,Prenume,Salariul
FROM SECTII, ANGAJATI
WHERE SECTII.IdSectie=ANGAJATI.IdAngajat
AND SECTII.Nume = nume_selectat;
Pentru a obţine această funcţionare se renunţă la controlul
de editare IdSectie (şi la controlul static corespunzător) şi
se adaugă o casetă combo-box cu numele Sectii, care va fi
completată cu toate numele secţiilor existente în intreprinderea
respectivă. Când utilizatorul selectează un nume de secţie în
casetă, se caută identificatorul acestuia (IdSectie) în tabelul
SECTII şi va fi reinterogat tabelul ANGAJATI pentru a
selecta numai angajaţii din secţia aleasă. Operaţiile care se vor
efectua pentru a realiza această funcţionare sunt următoarele:
a) Crearea unei clase pentru setul de înregistrări din
tabelul SECTII. Aplicaţia are deja o clasă pentru setul de
înregistrări din tabelul ANGAJATI, care completează
controalele de editare ale vederii CAngajatiView cu
informaţii despre un angajat. Se va adăuga o nouă clasă pentru
setul de înregistrări din tabelul SECTII, care va fi utilizată
pentru a completa lista tuturor secţiilor intreprinderii. Cu
ClassWizard se crează o nouă clasă CSectiiSet derivată
din clasa de bază CRecordSet. Se deselectează caseta de
validare Add to Component Gallery. În caseta de dialog
Database Options, din lista ODBC se selectează
INTREPRINDERE. În caseta de dialog SelectDatabase Tables
se selectează tabelul SECTII. Astfel s-a conectat numele
tabelului SECTII la clasa CSectiiSet.
b) Înglobarea obiectului set de înregistrări în obiectul
document. În fişierul AngajatiDoc.h se adaugă un obiect
de tip CSectiiSet (public: CsectiiSet
m_sectiiSet;) De asemenea, se adaugă directiva
#include "SectiiSet.h" în fişierele
AngajatiDoc.cpp, Angajati.cpp şi î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 şterge controlul de editare
IdSectie şi se adaugă în locul lui o casetă combo-box,
pentru care se alege în lista Type din pagina de proprietăţi
Styles opţiunea DropList. În clasa CAngajatiView se
adaugă variabila membru m_ctrlSectie de categorie
Control care se asociază cu caseta combo-box.
d) Înscrierea în caseta combo-box a listei numelor
secţiilor. Locul cel mai potrivit pentru înscrierea în caseta
combo-box a listei numelor secţiilor este funcţia virtuală
redefinită OnInitialUpdate() din clasa
CangajatiView. Vederea completează lista din casetă în
cadrul procesului său de iniţializare. Pentru aceasta, în funcţia
OnInitialUpdate() se construieşte şi se deschide
obiectul set de înregistrări CSectiiSet, se şterge conţinutul
curent al casetei, se introduce fiecare nume de compozitor în
listă, se fixează selecţia curentă la primul nume din listă (în
ordinea sortării). Codul care se adaugă prin procedura
următoare completează caseta şi totodată, filtrează,
parametrizează şi sortează setul de înregistrări:

f) Introducerea filtrului parametrizat. Filtrele unui set de


înregistrări determină submulţimea de înregistrări selectate
dintr-un tabel sau dintr-o interogare. Acestea folosesc
parametri, reprezentaţi prin semnul "?", în loc de valori literale
atribuite la compilare. Aplicaţia reselectează sau
"reinteroghează" înregistrările din tabelul ANGAJATI, ori de
câte ori utilizatorul selectează un alt nume de secţie din caseta
combinată. O modalitate de reinterogare a setului de
înregistrări este "parametrizarea" filtrului, adică apelarea
funcţiei Requery() cu o nouă valoare a parametrului
filtrului.
Pentru aceasta se adaugă în clasa CAngjatiSet o
variabilă membru pentru parametrul (CString
m_IDSectieParam;) şi se iniţializează această variabilă şi
numărul de parametri în constructorul clasei CAngajatiSet:
m_nParams=1; m_IDSectiiParam=0;
De asemenea, se completează funcţia
DoFieldExchange()cu următoarele linii de cod pentru a
identifica parametrul m_strIDCompParam:
pFX->SetFieldType(CFieldExchange::param);
RFX_Long(pFX,"IDSectiiParam",m_IDSectiiPar
am);
Funcţia DoFieldExchange()recunoaşte două tipuri de
câmpuri: coloane şi parametri.
Apelând funcţia SetFieldType()membru al clasei
CFieldExchange, ea afla ce tip de câmp (câmpuri) urmează
în apelul (apelurile) funcţiei RFX. Semnul "?" din specificaţia
filtrului parametrizat, indică locul unde va fi substituită
valoarea parametrului la rulare.
Pentru a furniza valoarea parametrului la rulare se atribuie
această valoare variabilei membru parametru a setului de
înregistrări î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
câmpului IdSectii găsită în setul de înregistrări
CSectiiSet.
Atât AppWizard, cât şi ClassWizard implementează clasele
derivate din CRecordSet astfel încât obiectul set de
înregistrări să aibă ca variabilă membru un pointer la obiectul
baza de date, de tip CDatabase, prin intermediul căruia să fie
conectat la sursa de date. Implementarea implicită a funcţiei
CRecordView:: OnInitialUpdate()apelează indirect
funcţia:
CString CAngajatiSet::GetDefaultConnect(){
return _T("ODBC;DSN=Intreprindere");
}
Cadrul-aplicaţie transmite acest "şir de caractere de
conectare" funcţiei CDatabase::Open()pentru obiectul de
tip CDatabase creat de cadrul-aplicaţie la implementarea
funcţiei CRecordSet::Open(). Dacă aplicaţia are două
sau mai multe seturi de înregistrări, fiecare dintre acestea va
crea în mod implicit şi va deschide propriul obiect de tip
CDatabase. Dacă mai multe seturi de înregistrări au acces la
aceeaşi sursă de date, atunci este mai eficient ca ele să
folosească în comun obiectul de tip CDatabase. O modalitate
de a permite mai multor seturi de înregistrări să folosească în
comun acelaşi obiect CDatabase este atribuirea ca
parametru funcţiei Open(), pentru celelalte seturi de
înregistrări, valoarea variabilei membru m_pDatabase a
primului obiect:
m_pSet->m_pDatabase=pDoc-
>m_sectiiSet.m_pDatabase;
CRecordView::OnInitialUpdate();
Dacă funcţia CRecordset::Open() află că variabila
membru m_pDatabase este deja alocată, refoloseşte
obiectul de tip CDatabase care este deschis.
Înregistrările din tabelul SECTII au fost sortate după
coloana Nume: pDoc->m_sectiiSet.m_strSort =
"Nume";
Pentru o anumită secţie, înregistrările din CAngajatiSet
sunt sortate după atributul Nume: m_pSet->m_strSort =
"Nume";
g) Reinterogarea bazei de date. De fiecare dată când
utilizatorul selectează un alt nume de compozitor din caseta
combinată, aplicaţia trebuie să reinterogheze obiectul set de
înregistrări CAngajatiSet pentru a-i împrospăta
înregistrările. Selectând un nume de secţie, utilizatorul va
vedea numai înregistrările reprezentând angajaţii secţiei
respective. CAngajaiSet conţine înregistrările pentru secţia
selectată anterior. Reinterogarea îi actualizează înregistrările, în
conformitate cu noul nume de secţie ales, folosind valorile
curente ale şirurilor de caractere pentru filtrare şi sortare. Când
utilizatorul selectează un nume de secţie din caseta combinată,
vederea de tip CangajatiView primeşte mesajul de
notificare CBN_SELENDOK. Apoi, vederea foloseşte funcţia de
tratare a acestui mesaj pentru a reselecta înregistrările
corespunzătoare numelui de secţie selectat, transmiţând ca
parametru identificatorul secţiei respective. Implementarea
funcţiei de tratare a mesajului CBN_SELENDOK este:
Codul adăugat reselectează înregistrările din baza de date în
obiectul set de înregistrări, în funcţie de valoarea parametrului
m_IdSectiiParam. Această valoare reprezintă
identificatorul compozitorului al cărui nume a fost selectat în
caseta combinată. Funcţia GetLBText(), membru al clasei
CComboBox, încarcă în cel de-al doilea parametru textul
curent selectat în control. Astfel, variabila va primi ca valoare
numele secţiei curent selectate. Apoi se caută în setul de
înregistrări CAngajatiSet identificatorul compozitorului
cu numele respectiv, pentru a-i atribui valoarea parametrului
m_IdSectiiParam. Dacă după reinterogarea obiectului
CAngajatiSet reiese că pentru secţia curent selectată nu
există nici un angajat, setul de înregistrari este iniţializat cu
valoarea Null, cu excepţia câmpului IdSectie, care va fi
iniţializat cu identificatorul secţiei respective.
După compilarea şi rularea aplicaţiei, se poate folosi
interfaţa cu utilizatorul pentru a naviga printre angajaţii din
secţia al cărei nume a fost selectat în caseta combinată. Dacă se
aleg alte nume de secţii, se vor obţine informaţiile stocate în
baza de date despre angajaţii lor. La execuţia aplicaţiei în
această versiune (Step 2) se va afişa o fereastră ca în Fig. 4.1.

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


4.2.2.3. Extinderea interfeţei pentru adăugarea,
actualizarea şi ştergerea înregistrărilor
În acest ultim pas al aplicaţiei (Step 3), se va extinde
interfaţa cu utilizatorul prin adăugarea a trei noi comenzi în
meniul Record:
 Comanda Adauga, pregăteşte o înregistrare vidă în care
utilizatorul va introduce datele. Acestea sunt salvate când
utilizatorul trece la altă înregistrare, folosind o comandă de
meniu sau un buton din bara cu instrumente pentru navigarea
printre înregistrări. În acest fel, se puteau salva şi până acum
modificările aduse unei înregistrări. În plus, utilizatorul poate
să salveze noua înregistrare, alegând din nou comanda Adauga.
 Comanda Renunta, abandonează o operaţiune de
adăugare sau de editare a unei înregistrări aflate în curs de
desfăşurare. Totodată, readuce înregistrarea modificată la
starea ei iniţială sau revine la înregistrarea afişată înainte de
alegerea comenzii Adauga.
 Comanda Sterge, şterge o înregistrare.
Cele trei articole se adaugă în meniul Record. Fiecare
dintre articolele nou adaugate are nevoie de o funcţie de tratare
în clasa CAngajatiView. Deoarece resursa meniu a
aplicaţiei este asociată cu clasa CMaimFrame, trebuie stabilită
o asociere între identificatorii articolelor de meniu şi clasa
CAngajatiView.
În operaţiunile de adăugare, editare şi ştergere a
înregistrărilor, clasa CrecordView actualizează în mod
automat înregistrarea curentă atunci când utilizatorul trece la
altă înregistrare.
Clasa CRecordView modifică în trei paşi o înregistrare
în obiectul set de înregistrări asociat, atunci când utilizatorul
trece la altă înregistrare:
 Pregăteşte înregistrarea curentă pentru a fi actualizată,
apelând funcţia membru Edit() a clasei pentru setul de
înregistrări.
 Apelează funcţia membru UpdateData() pentru
clasa vedere a aplicaţiei, derivată din CFormView, funcţie
care modifică valorile variabilelor membru ale obiectului set de
înregistrări, de obicei prin obţinerea noilor valori de la
controalele formularului.
 Apelează funcţia membru Update() a clasei pentru
seturi de înregistrări pentru a actualiza efectiv sursa de date cu
valorile modificate.
Implementarea comenzii Adauga. În implementarea
descrisă, utilizatorul va putea adăuga numai angajaţi ai secţiilor
existente în tabelul SECTII. Selectând comanda Adauga, se
intră în modul Adaugare, atribuindu-se variabilei
m_bSeAdauga valoarea TRUE; când utilizatorul trece la altă
înregistrare se iese din modul Adaugare.
Pentru implementarea funcţiei de tratare a comenzii
Adauga, în fişierul AngajatiView.h se declară
o variabilă protected:BOOL
m_bSeAdauga carese
iniţializează în constructorul clasei CAngajatiView cu
valoarea FALSE. Se construieşte funcţia 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 actualizează, prin
adăugarea noii înregistrări, atunci când utilizatorul trece la o
altă înregistrare. Tot atunci se iese şi din modul Adaugare.
Această funcţionalitate se realizează prin redefinirea funcţiei
membru OnMove() a clasei CRecordView:

Dacă aplicaţia este în modul Adaugare, se încearcă


adăugarea noii înregistrări la baza de date, înainte de a se trece
la altă înregistrare. Dacă datele introduse încalcă vreo regulă de
validare sau de integritate impusă tabelului respectiv în baza de
date, atunci se afişează mesajul de eroare corespunzator.
Variabila membru m_IdSectie ia valoarea corespunzătoare
secţiei selectate (în timpul adăugării, caseta cu numele de secţii
este folosită pentru introducere). În final, se iese din modul
Adaugare şi se apelează OnSelendokSectii() care va
reinteroga baza de date. Atunci când aplicaţia este în modul
Adaugare şi utilizatorul alege o comandă de navigare (de
exemplu, MoveNext), ca urmare a reinterogării sursei de date
în metoda OnSelendokSectii(), se ajunge întotdeauna la
prima înregistrare.
La pasul doi al aplicaţiei, atunci când utilizatorul alegea
numele unei secţii, funcţia de tratare
OnSelendokSectii()reinteroghează obiectul set de
înregistrări CAngajatiSet pentru a găsi toţi angajaţii aflaţi
în întreprindere aparţinând secţiei respective. Pentru funcţia de
adăugare de înregistrări, operaţiile legate de reinterogare sunt
executate numai dacă aplicaţia nu este în modul Adaugare, aşa
cum se vede în funcţia de tratare OnSelendokSectii().
Implementarea comenzii Sterge. Ca raspuns la comanda
Sterge, se şterge înregistrarea curentă, apelându-se funcţia
membru Delete() a obiectului set de înregistrări asociat:
void CAngajatiView::OnRecordSterge()
{ if(m_bSeAdauga){
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);
}
Dacă programul este în modul Adaugare, se apelează
OnRecordRenunta(). Altfel, se apelează funcţia membru
Delete() şi dacă această funcţie lansează o excepţie (de
exemplu, nu se poate şterge o înregistrare datorită regulilor de
integritate la nivelul tabelului respectiv), erorile sunt raportate
utilizatorului. Variabila membru m_strError a clasei
CDBException este pregatită de driverul ODBC pentru
eroarea respectivă. Apoi, se trece la înregistrarea următoare.
Dacă înregistrarea ştearsă era ultima din setul de înregistrări, în
ordinea sortării, atunci ne întoarcem la înregistrarea care a
Cap.4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

devenit ultima în urma operaţiunii de ştergere. Dacă


înregistrarea ştearsă a fost ultima şi acum nu mai există
înregistrări, câmpurile setului de înregistrări vor primi valoarea
Null.
Implementarea comenzii Renunta. Comanda Renunta
anulează modul Adaugare, în cazul în care utilizatorul a ales
anterior comanda Adauga sau nu ia în considerare modificările
aduse înregistrării curente, dacă utilizatorul a încercat să
editeze valorile din câmpul formularului:
void CAngajati::OnRecordRenunta() {
if(m_bSeAdauga){
m_pSet->Move(AFX_MOVE_REFRESH);
m_bSeAdauga=FALSE;
OnSelendokComplist();
return;
}
UpdateData(FALSE);
}
Dacă utilizatorul se răzgândeşte şi nu mai vrea să adauge
noua înregistrare, aplicaţia iese din modul Adaugare apelând
funcţia Move(), membru al clasei CRecordSet. Când se
apelează funcţia AddNew() pentru a începe operaţiunea de
adăugare, cadrul-aplicaţie păstrează o copie a valorilor curente
ale variabilelor setului de înregistrări, înainte de a permite
utilizatorului să introducă noi valori în controalele
formularului. Apelul funcţiei Move(), ca mai sus, anulează
operaţiunea de adăugare, refăcând înregistrarea care era curentă
înainte de a alege comanda Adauga. După ieşirea din modul
Adaugare se va apela OnSelendokComplist()pentru a
reinteroga baza de date, în cazul în care în timpul adăugării a
fost selectat un alt compozitor. Acest exemplu s-a axat pe
modul de definire a interogărilor în aplicaţiile MFC-ODBC,
considerând un singur formular de interfaţă, care este chiar
fereastra vedere a aplicaţiei (din clasa derivată din
CRecordView). Bibllioteca MFC oferă şi alte posibilităţi de

129
Cap.4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

dezvoltare a aplicaţiilor şi interfeţelor cu bazele de date, prin


combinarea mai multor seturi de rezultate (obiecte
CResultset) în aceeaşi vedere (formular) al unei aplicaţii
SDI (Single Document Interface) sau folosind ferestrele
descendent în aplicaţii MDI (Multiple Document Interface).

4.2.2.4. Crearea formularelor care afişează mai multe


seturi de rezultate
Dezvoltarea unei aplicaţii care afişează mai multe seturi de
rezultate în aceeaşi fereastră se poate urmări în exemplul din
directorul ..\Aplicatii\Capitol4\MFC\\SA\Step1.
Această aplicaţie cu numele SA este de tip SDI şi foloseşte
baza de date INTREPRINDERE (dezvoltată într-una din
lucrările precedente), pentru care s-a definit o sursă de date
ODBC. Clasa interogării de bază (clasa CSASet derivată din
clasa CresultSet) se asociază, pe de o parte, cu toate
tabelele care intervin în interogare (în cazul acesta, tabelele
ANGAJATI şi SECTII) iar pe de altă parte, cu clasa vedere
(formular) CSAView, derivată din clasa CRecordView.
În formular (clasa vedere - CSAView) se plasează
controalele de selecţie şi afişare a datelor, care corespund
variabilelor membre ale clasei CSASet asociate coloanelor
(atributelor) tabelelor asociate (Fig. 4.2). Pentru a realiza
anumite interogări(de exemplu, afişarea angajaţilor unei secţii
date prin numele acesteia) se introduc parametri de interogare
şi de cele mai multe ori, sunt necesare şi alte seturi de rezultate,
separate pe tabele, din care se extrag valorile parametrilor.

130
Fig. 4.2. Afişarea mai multor seturi de rezultate în acelaşi formular.

În exemplul prezentat, în panoul din stânga se afişează


rezultatul interogării:
SELECT IDAngajati,
ANGAJATI.Nume,Prenume,Salariu,IdSectie
FROM ANGAJATI, SECTII
WHERE ANGAJATI.IDSectie = SECTII.IdSectie
AND SECTII.Nume = parametru
Pentru realizarea filtrului parametrizat este necesară crearea
setului de rezultate de citire a tuturor liniilor tabelului
SECTII, pentru care s-a adăugat clasa CSectiiSet,
asociată cu tabelul SECTII. În clasa document se înglobează,
pe lângă setul de înregistrări propriu şi un obiect instanţă al
setului de înregistrări pentru filtrul parametrizat:
class CSADoc : public CDocument {
public:
CSASet m_sASet;
CSectiiSet m_sectiiSet;
............................
};
Iniţializarea casetei combo-box din care se selectează
parametrul interogării este realizată în funcţia
OnInitialUpdate() a clasei CSAView astfel:

4.2.2.5. Crearea mai multor formulare într-o aplicaţie


SDI
O altă posibilitate de afişare a datelor într-o aplicaţie SDI
este de a crea mai multe clase vedere (formulare) şi de a sigura
comutarea între acestea.
Tot în proiectul de mai sus se poate urmări şi acest mod de
afişare. Pentru aceasta se creează câte o clasă de vizualizare
derivată din clasa CRecordView pentru fiecare interogare
(set de rezultate) şi fiecărei vederi i se atribuie un identificator.
În acest exemplu s-au creat clasele CangajatiView
(asociată cu clasa CAngajatiSet) şi o clasă
CSectiiView (asociată cu clasa CsectiiSet existentă),
pe lângă clasa iniţială a proiectului CSAView. Se adaugă un
articol în meniul principal (articolul Forms) căreia i se adaugă
trei comenzi care permit selectarea formularului:
(Angajati, Sectii,SectiiAngajati). Aceste
comenzi sunt tratate în clasa CMainFrame şi produc
schimbarea formularului afişat prin apelul unei funcţii de
comutare (CMainFrame::SwitchToForm), al cărui cod
poate fi studiat în exemplul propus.

4.2.2.6. Crearea mai multor formulare într-o aplicaţie


MDI
Este posibilă crearea şi afişarea mai multor formulare într-
o aplicaţie MDI, dacă se definesc mai multe template-uri de
documente şi fiecare template este asociat unei vederi
(formular) diferit.
Un astfel de exemplu este aplicaţia MDI cu numele SA, din
directorul \Aplicatii\Capitol4\MFC\SA\Step2 şi
foloseşte tabelele SECTII, ANGAJATI, PROIECTE.
Pentru o astfel de afişare se crează mai multe clase vedere
(derivate din clasa CRecordView), fiecare clasă asociată cu o
clasa set de rezultate derivate din clasa CRecordSet, care, la
rândul ei se asociază cu unul sau mai multe tabele din baza de
date. În exemplul descris s-au creat clasele de interogare (set de
rezultate) CSASet, CSectiiSet şi CProiecteSet,
asociate cu tabelele ANGAJATI, SECTII, respectiv
PROIECTE, pe de o parte şi cu clasele vedere CSAView,
CSectiiView şi CProiecteView, pe de altă parte.
După aceasta, în funcţia InitInstance a clasei
aplicaţiei (CSAApp) se creeză (manual, generatorul Wizard nu
oferă această funcţionalitate) mai multe template-uri de
document astfel:
Comutarea între vederi se realizează din meniul File (din
meniul principal) în care se înlocuiesc comenzile New, Open
etc. cu comenzile corespunzătoare vederilor care se vor afişa
(Angajati,Sectii,Proiecte) şi fiecare funcţie de
tratare a unei astfel de comenzi apelează o funcţie de comutare
(OpenSpecifiedTemplate) care se poate studia din acest
exemplu şi folosi ulterior.
#define ANGAJATI 0
#define SECTII 1
#define PROIECTE 2
#define NR_FORMS 3
void CSAApp::OnFileAngajati()
{ OpenSpecifiedTemplate( ANGAJATI
);
}
Ferestrele vedere create pot fi afişate şi selectate succesiv,
şi reprezintă formulare multiple într-o singură aplicaţie de baze
de date (Fig. 4.3).
Fig. 4.3. Crearea mai multor formulare într-o aplicaţie MDI.
La rândul lor, formularele pot conţine nu numai interogări
simple (ca cele de mai sus) ci şi interogări complexe,
parametrizate şi comenzi de inserare, ştergere, actualizare,
obţinându-se astfel interfeţe grafice versatile de acces la bazele
de date.

4.2.3. Dezvoltarea aplicaţiilor de baze de date în


tehnologia .NET
Pentru dezvoltarea aplicaţiilor de baze de date, tehnologia
.NET oferă interfaţa ADO.NET (Access Data Object) ca parte
integrantă a bibliotecii .NET Framework Class Library (FCL)
care permite accesul la baze de date atât în aplicaţii locale cât şi
în aplicaţii distribuite în Internet. Pentru accesul la bazele de
date, ADO.NET foloseşte un modul software de interconectare
numit driver (data provider). In prezent ADO.NET suportă trei
tipuri de drivere (data providers):
 SQL Server .NET provider, care permite interfaţarea
directă cu baze de date SQL Server, începând cu versiunea 7.0
şi continuând cu versiunile ulterioare (2000, 2003, 2005).
 OLE DB .NET provider, care permite interfaţarea cu baze
de date care suportă interfaţa OLE DB, cum sunt Access,
Oracle. Se poate folosi şi pentru SQL Server, în orice versiune.
 ODBC .NET provider, care permite interfaţarea cu baze
de date care suportă interfaţa ODBC.
Orice interacţiune cu o bază de date în ADO.NET necesită
crearea unui obiect de conexiune care reprezintă conexiunea
fizică la baza de date şi este o instanţă a clasei
SqlConnection (pentru SQL Server .NET provider), a
clasei OleDbConnection (pentru OLE DB .NET provider)
sau a clasei PdbcConnection (pentru ODBC .NET provider).
Instrucţiunea SQL (SELECT, INSERT, UPDATE sau
DELETE) care se transmite bazei de date se defineşte printr-un
şir de comandă, care este conţinut într-un obiect de comandă.
Un obiect de comandă, care este o instanţă a uneia din
clasele SqlCommand, OleDbCommand sau OdbcCommand
(în funcţie de providerul folosit) încapsulează o conexiune la
baza de date şi un şir de comandă. Folosind un obiect de
comandă, se pot executa atât instrucţiuni SQL de definire a
datelor (INSERT,DELETE,UPDATE) prin apelul metodei
ExecuteNonQuery()cât şi instrucţiuni de interogare, prin
apelul metodei ExecuteReader(). Rezultatul unei
instrucţiuni de interogare se obţine într-un obiect
DataReader, instanţă a uneia din clasele
SqlDataReader, OleDbData-Reader sau
OdbcDataReader (în funcţie de providerul folosit), care
încapsulează mulţimea de linii rezultat al interogării, cu
posibilitatea de parcurgere înainte, linie cu linie.
Interogarea bazei de date se mai poate face şi prin
intermediul unui obiect DataSet, care este o instanţă a clasei
DataSet şi reprezintă o copie în memorie a unor date dintr-o
bază de date (mulţimi de linii). Liniile memorate într-un obiect
DataSet pot fi extrase direct (nu prin parcurgere linie cu
linie) iar modificările efectuate în DataSet sunt propagate în
baza de date.
În continuare se va studia interogarea unei baze de date
folosind un obiect DataReader şi 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 crează o
conexiune la baza de date pubs din distribuţia SQL Server
(Samples) executată local (localhost), cu mod de
autentificare SQL, pentru utilizatorul sa cu parola data.
În acest mod este bine să fie stabilită (sau modificată)
parola contului sa şi să fie editată înregistrarea serverului cu
comanda Edit SQL Server Registration Properties din meniul
contextual care se deschide la apăsarea butonului dreapta al
mouse-ului atunci când este selectat numele instanţei
serverului. În fereastra care se deschide (Registered SQL
Server Properties) se selectează opţiunile Use SQL
Server Authentication şi Always prompt for login
name and password. Pentru această conexiune creează mai
întâi o comandă de inserare de date (obiectul cmd,
instanţă a clasei SqlCommand) şi se execută
inserarea prin apelul metodei ExecuteNonQuery()a
acestui obiect. Pentru interogare se creează o nouă comandă
(folosind aceeaşi referinţă cmd) care conţine o instrucţiune
SQL de interogare şi se apelează metoda
ExecuteReader() a obiectului de comandă. Rezultatul
returnat de o interogare este o mulţime de linii (record set)
memorată într-un obiect DataReader instanţă a clasei
SqlDataReader. Liniile dintr-un astfel de obiect pot fi
parcurse folosind metoda Read() care returnează false
atunci când s-au parcurs toate liniile. Valoarea unui câmp
(atribut) dintr-o linie se obţine prin indexarea (cu indexul dat
prin numele atributului sau numeric, în ordinea coloanelor,
începând cu valoarea 0) obiectului DataReader.
După citirea şi afişarea liniilor tabelului titles, se şterge
linia introdusă folosind o comandă de ştergere (instrucţiunea
SQL delete); pentru a executa această nouă comandă este
necesar să fie mai întâi închis obiectul DataReader (prin
apelul metodei Close()), altfel se obţine o eroare de
execuţie.Acest program poate fi creat cu un editor oarecare şi
compilat cu compilatorul C# (csc) sau poate fi generat ca
proiect C# Console Application în Visual Studio .NET.

La execuţia acestui program sunt listate toate titlurile de


cărţi existente în tabelul titles din baza de date pubs în
momentul execuţiei, împreună cu cheile lor primare, aşa cum
se vede mai jos.
Fig. 4.4. Rezultatul execuţiei aplicaţiei Program4_15.

4.2.3.2. Interogarea unei baze de date folosind un obiect


DataSet
Interogarea unui baze de date folosind clasa
SqlDataReader este eficientă, dar setul de linii obţinut nu
poate fi parcurs decât secvenţial şi este read-only . Nu se poate
face, de exemplu, revenirea la linia precedentă şi nu se poate
modifica o linie şi aceasta să fie apoi scrisă înapoi în baza de
date.
Pentru astfel de operaţii ADO.NET oferă o clasă mai
puternică şi anume clasa DataSet, care reprezintă o
submulţime de tabele ale bazei de date la care se conectează
utilizatorul (Fig. 4.5). Datele din aceste tabele sunt încărcate în
obiectul DataSet, obiectul se deconectează de la sursa de
date, iar utilizatorul poate manipula datele din acest obiect.
Periodic obiectul DataSet se reconectează la baza de date,
pentru actualizarea bazei de date conform modificărilor
efectuate de utilizator în obiectul DataSet şi a obiectului
DataSet cu modificările efectuate de alţi utilizatori în baza
de date.
Clasa DataSet are proprietatea Tables care returnează
o colecţie (DataTable-Collection) de obiecte
DataTable, fiecare reprezentând un tabel din baza de date şi
Cap.4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

proprietatea Relations care returnează o colecţie


(DataRelationCollection) de obiecte
DataRelation, fiecare reprezentând o asociere între două
tabele ale bazei de date realizată prin intermediul unor obiecte
DataColumn.
Se reaminteşte că în CLS (Common Language
Specification) o proprietate este o interfaţă de tip public a
unei clase care conţine metodele de citire-scriere (get şi
set) ale unui atribut de tip private. În acest mod,
utilizatorii clasei accesează mai simplu atributul respectiv (ca şi
cum acesta ar fi public), respectându-se încapsularea şi
ascunderea datelor.
Clasa DataTable are următoarele proprietăţi:
 Columns: reprezintă un obiect
DataColumnCollection, care constă dintr-o colecţie de
obiecte DataColumn, fiecare reprezentând o coloană dintr-un
tabel.
 Rows: reprezintă un obiect DataRowCollection,
care constă dintr-o colecţie de obiecte DataRow, fiecare
reprezentând o linie dintr-un tabel.
 Constraints: reprezintă un obiect
ConstraintCollection, care constă dintr-o colecţie de
obiecte Constraint, fiecare reprezentând o constrângere în
baza de date.
Din păcate, aceste denumiri stabilite de dezvoltatorii
Microsoft nu prea corespund terminologiei corecte din
domeniul bazelor de date: clasa DataRelation reprezintă o
asociere (relationship) nu o relaţie aşa cum ar sugera numele
ei; o relaţie (relation) se reprezintă prin clasa DataTable.
Bineînţeles, aceste denumiri nu pot fi schimbate şi trebuie să
fie folosite aşa cum sunt.

140
Cap.4 Limbaje şi biblioteci de programare a aplicaţiilor de baze de date

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

Clasa DataAdapter asigură o intermediere între baza de


date şi program. Un obiect DataAdapter permite transferul
datelor între obiectul DataSet şi baza de date, încapsulând
şirul de comandă (instrucţiunea) SQL şi conexiunea la baza de
date Între clasa DataAdapter şi clasa DataSet există o
dependenţă de utilizare.
Exemplul următor realizează aceeaşi funcţionare ca şi
exemplul precedent, folosind un obiect DataSet: se
realizează o conexiune la baza de date pubs, se înscrie o linie
în tabelul titles, apoi se listează toate valorile atributelor
title_id şi title ale tuturor liniilor din tabelul
titles; în final se şterge linia nou introdusă.

141
Acest program poate fi creat, ca şi programul precedent, cu
un editor oarecare şi compilat cu compilatorul C# (csc) sau
poate fi generat ca proiect C# Console Application în Visual
Studio .NET.
La execuţia acestui program se obţine o listă a titlurilor de
cărţi şi a cheilor primare ale acestora din tabelul titles din
baza de date pubs din distribuţia SQL Server, la fel ca şi în
exemplul precedent.
Toate operaţiile cu baza de date se desfăşoară prin
intermediul obiectului DataSet(dataset1) care este
populat cu mulţimea de linii rezultat al interogării definite prin
şirul de comandă (cmdString); şirul de comandă şi
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, indexată
cu numele tabelului sau cu un index numeric începând cu 0; în
mod similar, din tabel se extrag liniile folosind proprietatea
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 şi pentru operaţii de
actualizare a bazei de date, folosind metoda corespunzătoare (
Add() , Delete() etc.)

4.2.3.3. Afişarea datelor din tabele folosind un obiect


DataGrid
Modelul ADO.NET oferă suport şi pentru dezvoltarea
interfeţei grafice cu utilizatorul, prin posibilitatea de asociere a
unor mulţimi de linii (DataSet) cu controale de afişare a
datelor. În forma cea mai simplă, un obiect DataGrid se
asociază cu un obiect DataSet şi afişează datele stocate de
acesta.
Un exemplu simplu în limbajul C# de acces la o bază de
date folosind clase ADO şi un control DataGrid necesită,
într-adevăr, un număr incredibil de mic de linii de program
(Program4_16).
În acest exemplu se creează un formular Windows
(Windows Form) cu un obiect DataGrid (cu referinţa notată
dataGrid1) care va fi populat cu informaţii citite din tabelul
Customers al bazei de date Northwind, care face parte
din setul de exemple ce se livrează cu sistemul SQL Server. De
asemenea, sistemul de dezvoltare .NET SDK (si VS .NET)
conţine o versiune independentă de server de baze de date
desktop cunoscut sub numele Microsoft SQL Server 2000
Desktop Engine (MDSE 2000) care poate fi folosit pentru
testări în locul serverului SQL Server şi conţine baza de date
Northwind. Listingul de mai jos este puţin simplificat
(detaliile de implementare a metodelor
InitializeComponent() şi Dispose()de iniţializare
şi de ştergere a datelor).
Pentru crearea aplicaţiei de acces la baza de date
Northwind, se creează mai întâi un obiect DataAdapter
(cu numele dataAdapter1), căruia i se pasează ca
argumente la construcţie un şir de caractere de conectare
(conn) şi un şir de caractere de comandă (cmdString):
SqlDataAdapter dataAdapter1 = new
SqlDataAdapter(cmd, conn);
Şirul conn transmite serverului SQL Server datele de
conectare (numele utilizatorului, parola şi numele bazei de
date), iar şirul comanda transmite bazei de date instrucţiunea
SQL de interogare.
Aceste şiruri se definesc astfel:
string conn = "server = localhost;uid
= sa;” +
”pwd = parola ;database = northwind";
string cmd = "Select * from Customers";
Datele din tabele se încarcă în obiectul DataSet prin
apelul metodei Fill() a obiectului:
dataAdapter1:
DataSet dataSet1 = new DataSet();
dataAdapter1.Fill(dataSet1,"Customers");
Afişarea liniilor rezultat al interogării definite se poate face
în obiectul DataGrid dacă se specifică asocierea acestuia cu
obiectul
DataSet:
dataGrid1.DataSource =
dataSet1.Tables["Customers"].DefaultView;
La execuţia acestui program se obţine lista liniilor din
tabelul Customers al bazei de date Northwind, aşa cum
se vede în figura de mai jos.
Visual Studio .NET oferă suport complet de creare şi
conectare a obiectelor DataSet şi DataGrid, astfel încât
majoritatea liniilor de cod prezentate mai sus pot fi generate
vizual, prin editarea proprietăţilor formularului şi a controalelor
folosite, modalităţi care vor fi prezentate în continuare.

Fig. 4.6. Rezultatul executiei aplicatiei Program4_17.

4.2.3.4. Crearea vizuală a aplicaţiilor 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) atât
obiectele de comunicaţie cu baza de date (conexiuni, comenzi,
adapter, set de date), controalele grafice de afişare a datelor:
liste de afisare (ListView), tablouri de afişare (DataGrid)
cât şi conexiunile dintre acestea. În continuare sunt prezentate
câteva exemple semnificative de creare a aplicaţiilor de baze de
date folosind proiectarea vizuală oferită de VS .NET.
A. Interogarea bazei de date printr-un obiect
DataReader şi afişarea rezultatelor folosind un obiect
ListView
Acest exemplu este o aplicaţie Windows creată în Visual
Studio .NET, care creează o conexiune la o bază de date
Northwind, execută o comandă SQL de interogare, iar
rezultatul îl afişează într-un control ListView.
Mai întâi se creează un proiect Visual C# Windows
Application, care conţine un formular (clasa Form1). Se pot
modifica diferite caracteristici ale formularului selectând
formularul (prin click pe formular în modul Design –
Form1.cs[Design]). La selecţia formularului, în fereastra
Properties se poate seta titlul formularului editând proprietatea
Text, în care introducem, de exemplu, textul Crearea unui
DataReader in VS .NET . Etapele următoare de creare a
aplicaţiei sunt:
1. Crearea obiectului de conexiune SqlConnection. Un
obiect SqlConnection se creează prin selectarea în pagina
Data a ferestrei de instrumente Toolbox a obiectului
SqlConnection şi tragerea lui în fereastra formularului. După
această operaţie, sub formular este afişat obiectul creat (cu
numele implicit sqlConnection1), care este considerat un
obiect nevizual (nu apare afişat în formular), dar poate fi totuşi
proiectat folosind instrumentele Visual Studio. La selectarea
obiectului sqlConnection1 proprietăţile lui se afisează în
fereastra Properties. Pentru editarea proprietăţii
ConnectionString (care specifică detaliile de conectare
la baza de date) fie se editează direct şirul de conectare (la fel
ca în aplicaţiile precedente), fie se acţionează cu click pe acest
câmp şi se selecteză opţiunea New Connection din lista de
opţiuni afişată, care generează fereastra de dialog Data Link
Properties. În pagina Provider a acestei ferestre se selectează
opţiunea Microsoft OLE DB Provider for SQL Server şi se
trece (cu butonul de comandă Next) la pagina următoare
(Connection). În această pagină (reprezentată în figura de mai
jos) se pot seta mai mulţi parametri:
(1) Serverul de baze de date (se poate selecta dintr-o listă
sau se poate introduce numele serverului; pentru serverul local
se introduce localhost);
(2) Modul de conectare (log on) la server; pentru opţiunea
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).

După aceste setări se poate testa conexiunea la baza de date


(cu butonul de comandă Test Connection) şi dacă acest test
reuşeşte se afişează mesajul Test connection succeeded şi se
poate continua dezvoltarea aplicaţiei. Câteva din opţiunile
setate se pot urmări în figura de mai sus.
La închiderea (cu butonul OK) a ferestrei de dialog Data
Link Properties, se lansează fereastra de dialog de conectare
(login) la baza de date (aşa cum se vede în figura de mai sus) şi
se introduce parola pentru conectare la server.
Şirul de comandă creat astfel ca proprietate a obiectului
sqlConnect1, se poate vedea în fereastra de proprietăţi, iar
codul generat îl găsim în metoda
InitializeComponent() a clasei Form1, care este
afişat în fereastra de editare dacă formularul (clasa Form1)
este selectat în modul View Code din meniul contextual care se
obţine prin click dreapta pe numele fişierului (Form1.cs) în
fereastra Solution Explorer. Ceea ce s-a obţinut arată astfel:
this.sqlConnection1.ConnectionString
= "workstation id=FELIX;packet
size=4096;user id=sa; data
source=localhost;persist " +
"security info=False;initial
catalog=Northwind";
Acest şir nu este suficient şi trebuie completat cu parola
(pwd=parola;) altfel nu se va realiza conexiunea dorită. Se
poate folosi şi un şir de conectare mai simplu, asemănător celui
folosit în exemplul precedent:
2. Crearea obiectului de comandă SqlCommand . Obiectul
de comandă se creează prin selectarea oţiunii SqlCommand din
pagina Data a ferestrei de instrumente Toolbox şi tragerea
acesteia în formular. Obiectul care se creează (cu numele
implicit sqlCommand1) este afişat sub formular şi dacă este
selectat, proprietăţile lui se afişează în fereastra Properties.
Pentru acest obiect, importante sunt proprietăţile Connection şi
CommandText. Se selectează proprietatea Connection şi se
selectează opţiunea sqlConnection1. Apoi se selectează
proprietatea CommandText pentru definirea instrucţiunii SQL
(ca un şir de caractere). Se acţionează butonul de urmărire (…)
şi se obţine un formular de creare a interogărilor (Query
Builder) în care se pot introduce unul sau mai multe tabele,
vederi (views) sau funcţii. Se introduce tabelul Customers şi
apoi se selectează ca ieşiri (Output) acele atribute ale tabelului
care trebuie să fie citite din baza de date (aşa cum se vede în
figura de mai jos).

3. Crearea obiectului de citire SqlDataReader şi a


controlului de afişare ListView.
Obiectul ListView se selectează în pagina Windows
Forms a ferestrei de instrumente ToolBox şi se trage în
formular. Dimensiunile acestei liste se pot ajusta prin tragerea
cu mouse-ul a ferestrei respective.
Operatia de interogare a bazei de date o vom face la
încărcarea formularului, prin tratarea evenimentului Load a
acestuia. Pentru aceasta se efectuează dublu-click pe formular
(în afara listei de afişare ListView) şi generatorul de cod
creează un handler de eveniment (metoda Form1_Load) pe
care îl înregistrează pentru evenimentul Load a clasei
Form1. Acest handler se completează 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"].ToS
tr ing());
listView1.Items.Add(reader["CompanyName"].ToSt
ring());
listView1.Items.Add(reader["ContactName"].ToSt
ring());
}
reader.Close();
sqlConnection1.Close();
}
4. Crearea obiectului DataReader. Obiectul de citire
SqlDataReader nu se poate crea vizual, ci se obţine prin
execuţia metodei ExecuteReader() a obiectului
sqlCommand1, aşa cum se vede în codul de mai sus.
La execuţia programului se obţine lista clienţilor firmei
Northwind, afişată într-un control ListView, aşa cum se vede
în imaginea de mai jos.
B.Interogarea bazei de date printr-un obiect DataSet
şi afişarea rezultatelor folosind un obiect DataGrid
Acest exemplu este o aplicaţie Windows asemănătoare
exemplului precedent, cu deosebirea că datele se citesc printr-
un obiect DataSet şi se afişează într-un control
DatataGrid.
Primele etape de creare a proiectului în Visual Studio .NET,
inclusiv etapele de creare a obiectului conexiune
sqlConnection1 şi a obiectului de comandă
sqlCommand1, sunt identice cu cele de la exemplul
precedent. De asemenea, este necesară setarea proprietăţii
Connexion a obiectului sqlCommand1 cu valoarea
sqlConnexion1. Etapele următoare sunt date în continuare.
1. Crearea obiectului SqlDataAdapter se realizează
vizual prin selectarea acestuia în pagina Data a ferestrei
Toolbox şi tragere în formular. La introducerea adaptorului în
formular se iniţiază un generator de configurare (Data Adapter
Configuration Wizard) cu mai multe ferestre de dialog. În
prima fereastră se setează conexiunea, selectând conexiunea la
baza de date Nortwind, de pe staţia (host) locală şi utilizatorul
dbo (în exemplul utilizat: FELIX.Nortwind.dbo). În a
doua pagină a generatorului se selectează opţiunea Use SQL
statement, din cele trei opţiuni posibile (Use SQL statement,
Create new stored procedure, Use existing stored procedure).
În pagina următoare de dialog se introduce direct instrucţiunea
SQL dorită (SELECT * FROM Customers) sau se
generează această instrucţiune folosind QueryBuilder, la fel ca
în exemplul precedent. Aceste proprietăţi se pot reconfigura
dacă se selectează link-ul Configure Data Adapter, afişat la
sfârşitul listei de proprietăţi a obiectului dataAdapter1.
După crearea obiectului sqlDataAdapter1, se
selectează acest obiect şi se setează proprietatea
SelectCommand selectând valoarea sqlCommand1 din lista
de comenzi afişate.
2. Crearea obiectului DataSet. Obiectul DataSet care
va fi folosit pentru interogarea bazei de date se poate crea
selectând link-ul Generate Dataset afişat sub lista de
proprietăţi a obiectului dataAdapter1. La selectarea acestui
link se afişează o fereastră de dialog intitulată Generate
Dataset, în care se selectează (cu opţiunea New) crearea unui
noi clase DataSet1 căreia i se adaugă tabelul Customers din
baza de date. Obiectul dataSet11 creat este o instanţă a
clasei DataSet1 şi este afişat sub formular, alături de
celelalte obiecte nevizuale (sqlConnect1, sqlCommand1,
sqlDataAdapter1). După această etapă fereastra VS arată
ca în imaginea de mai jos.

Obiectul dataSet11 creat, este instanţă a unei clase


create de generatorul de cod (Wizard) VS.NET, numită
DataSet1, derivată din clasa DataSet şi corespunzând
datelor selectate pentru acest obiect (tabelul Customers).
Această clasă este definită printr-o schema XML, generată de
Wizard şi conţinută în fişierul DataSet1.xsd care se vede în
fereastra Solution Explorer. Schema XML a unui DataSet
poate fi afişată în modul Design (selectând opţiunea Open din
meniul derulant care se afişează cu click dreapta pe fişierul
DataSet1.xsd din fereastra Solution Explorer) sau în mod text,
dacă se selectează opţiunea View in Browser. În modul Design,
se afişează schema XML corespunzătoare clasei DataSet1
sub forma unui tabel (al liniilor rezultat al interogării), iar în
modul View in Browser, se afişează schema corespunzătoare,
ca document XML.
Clasa DataSet1 (din care se instanţiază obiectul
dataSet11) este generată în fişierul DataSet1.cs, care poate
fi deschis dacă se selectează modul Show All Files (în fereastra
Solution Explorer) şi apoi se expandează grupul de fişiere
marcat DataSet1.xsd. Clasa DataSet1 este derivată 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 completează această metodă astfel:
private void Form1_Load(object sender,
System.EventArgs e)
{ sqlConnection1.Open();
sqlDataAdapter1.Fill(dataSet11,"Customers"
);
dataGrid1.DataSource =
dataSet11.Tables["Customers"].DefaultView;
sqlConnection1.Close();
}
La execuţia aplicaţiei se obţine un formular care conţine un
tabel ca în imaginea de mai jos:
C. Afişarea rezultatelor unei interogări folosind un
obiect DataView
Clasa DataView permite afişarea numai a unora dintre
liniile unui tabel (DataTable), folosind un filtru, precum şi
ordonarea acestora după un criteriu dat În exemplul următor se
execută o interogare a bazei de date Northwind
(SELECT * FROM Customers where
Country=‘filtru’ORDER BY CustomerID) folosind
un obiect DataSet. Datele din DataSet se afişează într-un
obiect DataGrid folosind un obiect DataView, care
permite filtrarea liniilor conform condiţiei de interogare (clauza
WHERE a interogării) şi ordonarea lor după valorile atributului
dat în clauza ORDER BY. Etapele de dezvoltare a proiectului
în VS .NET sunt prezentate în continuare.
1. Se creează un proiect C# Windows Application cu
numele Program4_20 şi se selectează panoul de afişare Server
Explorer din meniul View al sistemului VS .NET. Se realizează
conexiunea la baza de date introducând parola pentru userul de
conectare dorit (în fereastra de conectare care se deschide
atunci când selectăm sistemul de gestiune SQL Server). Se
selectează tabelul Customers din baza de date Northwind şi
se trage în formular. La această operaţie generatorul de cod
generează obiectele sqlConnection1 (instanţă a clasei
SqlConnection) şi sqlDataAdapter1 (instanţă a clasei
SqlDataAdapter), care sunt plasate sub formular (când este
editat în modul Design).
2. Se editează proprietatea ConnectionString a
obiectului sqlConnection1 pentru conectarea la baza de
date aleasă. Nu uitaţi să adăugăţi în şirul de conectare parola
pentru utilizatorul ales (deoarece generatorul de cod nu face
automat acest lucru): pwd=parola;
3. Se selectează obiectul sqlDataAdapter1 şi se face
click pe link-ul Generate Datase (care este afişat sub lista de
proprietăţi, aşa cum se vede în figura de mai jos). Se acceptă
datele implicite din fereastra de dialog care se deschide, iar la
închiderea acesteia (cu butonul OK) se creează obiectul
dataSet11, care este afişat sub formular.
4. Se creează un obiect DataView prin selectarea
acestuia în pagina Data a ferestrei de instrumente ToolBox şi
tragere în formular; după creare obiectul (cu numele implicit
dataView1) este afişat sub formular, aşa cum se vede în
figura de mai jos.
5. Se setează proprietatea Tables a obiectului
dataView1 (în fereastra Properties) selectând valoarea
dataSet11.Customers din lista derulantă din partea
dreapta a paginii Properties. De asemenea, se setează
proprietatea RowFilter la valoarea: Country = ‘UK’
şi proprietatea Sort la valoarea CustomerID (aşa cum se
vede în figura de mai jos).
6. Se introduce un control DataGrid, selectându-l în
pagina Windows Forms din fereastra ToolBox şi tragându-l în
formular; prin această operaţie se creează obiectul cu numele
implicit dataGrid1 (instanţă a clasei DataGrid), care
ocupă o anumită suprafaţă în fereastra formularului.
Acestă suprafaţă se poate ajusta prin tragere cu mouse-ul.
7. Se setează proprietatea DataSource a obiectului
dataGrid1 la valoarea dataView1, folosind lista
derulantă din partea dreapta a proprietăţii DataSource. În
acest moment în fereastra mediului de dezvoltare VS .NET se
afişează datele 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 completează această metodă astfel:
private void Form1_Load(object sender,
System.EventArgs e)
{ sqlDataAdapter1.Fill(dataSet11,
"Customers");
}
Se compilează şi se execută programul. În acest stadiu de
dezvoltare al programului se obţine lista tuturor clienţilor
firmei din ţara introdusă ca parametru de filtrare (UK), aşa cum
se poate vedea în imaginea de mai jos.
9. Pentru filtarea liniilor rezultatului cu un parametru
oarecare (filtru), introdus în mod dinamic în cursul execuţiei,
se foloseşte un control combo-box care să conţină ca listă de
elemente (Items) denumirile tuturor ţărilor clienţilor, din care
se selectează ţara dorită. Controlul combo-box se introduce în
formular prin tragerea acestuia din fereastra ToolBox în
fereastra formularului (selectat în modul Design) şi se setează
proprietăţile în fereastra Properties.
Liste elementelor controlului combo-box se poate seta fie
ca o colecţie de denumiri ale ţărilor prin editarea proprietăţii
Items (Collection) a controlului, fie folosind o sursă de date,
introdusă ca proprietate DataSource a controlului. În exemplul
prezentat s-a ales cea de-a doua posibiltate, selectând pentru
proprietatea DataSource valoarea dataSet11.Customers iar
pentru proprietatea DisplayMember valoarea Country. Prin
această setare lista de elemente conţinută de combo-box este
lista valorilor coloanei (atributului) Country a tabelului
Customers din obiectul dataSet11.
10. Se înregistrează un handler de eveniment de schimbare
a selecţiei controlului combo-box (prin dublu-click pe controlul
combo-box) şi se defineşte 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 citeşte elementul selectat în combo-box
(folosind proprietatea SelectedItem a clasei ComboBox),
se converteşte într-un şir de caractere (obiect string)
(apelând metoda GetItemText) şi se modifică filtrul
obiectului DataView la valoarea acestui şir de caractere,
folosind proprietatea RowFilter a clasei DataView.
11. Se completează metoda Form1_Load() (handlerul
de eveniment de încărcare 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 execuţia programului se obţine o imagine ca cea de mai
jos:
D. Afişarea datelor din două relaţii asociate într-un
singur formular
În programul următor se va prezenta modul în care se poate
crea o aplicaţie Windows care conţine un formular de date,
folosind generatorul Data Form Wizard din VS .NET. Pentru
exemplificare, se vor folosi relaţiile Customers şi Orders,
din baza de date Northwind, între care există o asociere 1:N
prin intermediul cheii străine CustomersID din relaţia
Orders, care referă cheia primară CustomerID din relaţia
Customers. În sistemul de dezvoltare .NET se folosesc
denumirile de tabel părinte (parent table ) pentru relaţia referită
(care conţine cheia primară referită de cheia străină), tabel fiu
(child table ) pentru relaţia care referă, iar asocierea 1:N este
numită asociere părinte-fiu (parent-child). Aplicaţia va afişa
într-un formular câte o linie din tabelul părinte (Customers)
prin intermediul mai multor casete de afişare (TextBox) şi
pentru fiecare linie din acest tabel, se afişează toate liniile
corespunzătoare (care au valoarea cheii străine egală cu
valoarea cheii primare a liniei respective) din tabelul fiu
(Orders) într-un tabel de date (DataGrid).
Pentru crearea aplicaţiei se vor parcurge următorii paşi:
1. Selectaţi: File/New Project. În caseta de dialog New
Project selectaţi Visual C# Empty Project şi introduceţi numele
dorit al proiectului (Program4_21 în această prezentare).
Deoarece se va crea ulterior un formular de date, nu este
necesar ca VS .NET să genereze obişnuitul formular gol
(blank) cu care se încep în general aplicaţiile Windows Form,
este suficintă crearea în acest stadiu a unui proiect vid. Apăsaţi
butonul OK al ferestrei de dialog cu mouse-ul şi VS. NET va
crea un proiect vid care va fi completat în continuare.
2. Selectaţi Project/Add New Item. Selectaţi Data Form
Wizard din secţiunea dreapta (Templates) a ferestrei de dialog
Add New Item şi introduceţi numele dorit al formularului (în
exemplul dat s-a folosit numele Form1). Se va afişa pagina de
start a generatorului Data Form Wizard.
Apasaţi butonul Next pentru a continua.
3.Următoarea pagină a generatorului, (intitulată Choose
the dataset you want to use), permite definirea unui obiect
DataSet; selectaţi opţiunea Create a new dataset named, iar
în caseta de text corespunzătoare introduceţi numele dorit
(dataSet1 în exemplul prezentat). Apăsaţi butonul Next
pentru a continua.
4.În pagina următoare se alege o conexiune la baza de
date, fie selectând o conexiune existentă, fie creând o
conexiune nouă. Apăsaţi butonul Next pentru a continua.
Urmează conectarea la baza de date, care necesită introducerea
parolei în câmpul Password al casetei de dialog SQL Server
login care se afişează în acest moment al generării proiectului.
Apăsaţi butonul OK pentru a continua.
5.În continuare se vor selecta tabelele dorite (Customers
şi Orders) din tabelele bazei de date Northwind. Selecţia se
poate face fie prin dublu-click pe tabelul respectiv, fie
selectându-l în secţiunea din stânga a ferestrei (Available
Items) şi trimiţându-l (cu săgeata spre dreapta) în secţiunea din
dreapta (Selected Items). În acest stadiu, fereastra Data Form
Wizard arată ca în figura de mai jos.
Apăsaţi butonul Next pentru a continua.
6.În pagina următoare se creează asocierea (relationship )
între cele două tabele selectate. Se introduce denumirea
asocierii (de exemplu customers_orders) în caseta de text
Name, se selectează tabelul Customers ca tabel părinte
(Parent Table), iar tabelul Orders ca tabel fiu (Child Table).
Se selectează CustomerID, cheie în ambele tabele (în casetele
combo-box Keys). Această asociere nu va fi înregistrată decât
dacă se apasă săgeata spre dreapta, care o trimite în fereastra
numită Relations (corect ar fi trebuit să fie denumită
Relationships). Apăsaţi butonul Next pentru a continua.
7. În pagina următoare se selectează coloanele (atributele
tabelelor) pe care dorim să le afişăm în formular, aşa cum se
vede în imaginea de mai jos:
8. În ultima pagină a generatorului se selectează stilul de
afişare a tabelului părinte. Selectaţi opţiunea Single record in
individual control din grupul de radio-butoane How do you
want to display your data şi toate celelalte opţiuni (casete chek-
box): Add, Delete, Cancel, Navigation controls, Cancel All,
care permit efectuarea operaţiunilor de inserare, ştergere şi
parcurgere a liniilor tabelelor, precum şi anularea modficărilor
efectuate. La apăsarea butonului Finish, se încheie operaţiile de
creare a formularului, care este afişat în fereastra VS, aşa cum
se vede în figura de mai jos.
Se observă că generatorul Data Form Wizard foloseşte
clase OLE DB, deşi se conectează la o bază de date SQL
Server. Această soluţie este mai generală, deoarece se aplică
pentru orice sistem de baze de date pentru care există un
provider OLE DB şi majoritatea sistemelor actuale oferă un
astfel de driver, dar, pentru SQL Server, este mai puţin
eficientă decât dacă s-ar folosi clase SQL Server provider. Este
preţul plătit pentru operaţiile de generare a codului şi acest cod
nu este chiar simplu.
Dacă ne uităm în fişierul DataForm1.cs (cu opţiunea View
Code în meniul contextual care se deschide la click cu mouse-
ul pe numele fişierului în fereastra Solution Explorer), se
observă că s-au generat peste 900 de linii de cod.
9. Pentru conectarea la server este necesară introducerea
parolei pentru user-ul folosit (sa).
Pentru aceasta se selectează obiectul
oleDbConnection1 prin selectare în fereastra de
proiectare (sub formular) şi editarea proprietăţii
ConnexionString, în care se introduce sub-şirul:
pwd=parola;
10. Ultimul pas necesar pentru crearea aplicaţiei este
introducerea (manuală) a funcţiei Main() în clasa
DataForm1 (din fişierul DataForm1.cs):
public static void Main()
{ Application.Run(new
DataForm1());
}
La execuţia aplicaţiei se afişează formularul de date care
este populat cu valori din baza de date Northwind la comanda
(butonul) Load, aşa cum se vede în imaginea de mai jos.

Se observă asocierea dintre cele două tabele şi faptul că în


formular se afişează valori corelate: liniei din tabelul părinte
(Customers) afişată în partea de sus a formularului îi
corespund toate liniile care o referă (au aceeaşi valoare a cheii)
din tabelul fiu (Orders). Liniile din tabelul părinte
(Customers) pot fi parcurse incremental în ambele sensuri
(cu cele două butoane cu săgeţi simple) şi se poate ajunge
direct la prima sau ultima linie (cu cele două butoane cu săgeţi
duble). De asemenea se pot actualiza, adăuga sau şterge linii
din tabelul părinte.
E. Interogări pe mai multe relaţii asociate
În programul următor se prezintă modul în care se poate
afişa rezultatul unei interogări aplicate pe trei relaţii şi anume
două relaţii puternice şi o relaţie de asociere, care asigură o
asociere “mulţi-la-mulţi” între primele două relaţii. Pentru
exemplificare se vor folosi tabelele Employees, Customers,
Orders din baza de date Northwind din distribuţia SQL Server.
Într-un DataDrid se vor afişa rezultatele interogării
“Care sunt numele, prenumele, titlul (funcţia) angajaţilor şi ţara
de rezidenţă a clienţilor dintr-o anumită ţară (dată ca
parametru) către care angajaţii au efectuat ordine de livrare?”
Paşii care trebuie să fie executaţi sunt următorii:
1. Creaţi un proiect C# Windows Application cu numele
Program4_22.
2. Creaţi un obiect SqlDataAdapter, trăgându-l din
pagina Data a ferestrei ToolBox în formular şi în continuare
efectuaţi setările cerute de generatorul Data Adapter
Configuration Wizard.
În prima pagină se crează (dacă nu a fost creată mai înainte)
sau se selectează conexiunea localhost.Nortwind.dbo (în loc de
localhost generatorul afişează numele hostului pe care lucraţi),
iar în pagina a doua se selectează opţiunea Use SQL
statements.
3.În pagina următoare se creează comanda de selecţie
SQL, fie scriind-o în fereastra de editare, fie invocând
gneratorul Query Builder, care oferă o interfaţă grafică de
generare a interogării (aşa cum se vede în figura de mai jos). Se
adaugă tabelele Customers, Orders, Employees (între care
există asocierile corespunzătoare) şi se va genera instrucţiunea
SQL SELECT fără clauza WHERE de filtrare a valorilor
dorite, deoarece această clauză se va introduce în mod dinamic
în cursul execuţiei (printr-un control combo-box). Sintaxa
folosită este o extensie a limbajului SQL standard, exprimând
joncţiunea între două tabele prin INNER-JOIN
Generatoul Query Builder ne anunţă că s-a generat un
DataAdapter care poate efectua numai operaţii de
interogare (SELECT). Se dau comenzile OK, apoi Finish.
La crearea obiectului sqlDataAdapter1, s-a creat şi
conexiunea necesară (obiectul sqlConnection1). Nu uitaţi
să adăugaţi parola în şirul de conectare
(ConnectionString): pwd=parola; al obiectului
sqlConnection1 creat.

3. Se creează obiectul dataSet1, (tragând obiectul din


pagina Data a ferestrei ToolBox în formular) şi se selectează
opţiunea Untyped DataSet. Apoi se creează un obiect
dataView1 (tragând obiectul din pagina Data a ferestrei
ToolBox în formular).
4.Se inserează în formular un obiect DataGrid, a cărui
proprietate DataSource se setează cu dataWiew1, iar în
proprietatea CaptionText se introduce textul “Employees-
Orders-
Customers”
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 completează această metodă astfel:
private void Form1_Load(object sender,
System.EventArgs e)
{ this.sqlDataAdapter1.Fill(dataSet1);
this.dataView1.Table = dataSet1.Tables[0];
}
După compilare şi execuţie se obţine imaginea de mai jos.
În aceasta fază în tabel (data-grid) se afişează rezultatul
interogării introduse în crearea adaptorului, adică o proiecţie a
joncţiunii dintre cele trei tabele pe atributele date (FirstName,
LastName,Title etc).

6. Clauza de filtrare cu o condiţie dată ca parametru


(Customers.Country = parametru) se intruduce ca
filtru al obiectului dataView1, iar valoarea parametrului se
selectează printr-un combo-box.
Pentru aceasta se inserează un combo-box (comboBox1)
şi mai multe obiecte prin care sursa de date a acestuia să fie
coloana Country din tabelul Customers. Se introduce un
obiect dataSet2 (cu opţiunea Untyped Data Set, la fel ca
dataSet1), un obiect dataView2 şi un obiect
dataAdapter2, care se configurează cu instrucţiunea SQL:
SELECT DISTINCT Country FROM Customers.
Apoi se completează metoda Form1_Load() astfel:
7.Se înregistrează un handler de eveniment de schimbare a
selecţiei controlului combo-box (prin dublu-click pe controlul
combo-box) şi se defineşte 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");
}
La execuţia programului se obţine o imagine ca cea din
figura de mai jos.

Aceasta este una dintre posibilităţile de creare a unei


interogări parametrizate pe trei tabele corelate şi după cum se
poate remarca, nu există un generator (Wizard) care să
genereze automat o astfel de aplicaţie, unele obiecte şi
configurări au fost scrise manual.

S-ar putea să vă placă și