Biologie | Chimie | Didactica | Fizica | Geografie | Informatica | |
Istorie | Literatura | Matematica | Psihologie |
Primul lucru pe care un programator trebuie sa-l faca atunci cand vrea sa comunice cu o baza de date este sa deschide o conexiune la aceasta. Conexiunea 'spune' celorlalte obiecte cu ce baza de date lucreaza. Conexiunea se ocupa de logica low-level asociata protocolului. Acest lucru usureaza foarte mult munca unui programator, acesta neavand decat sa instantieze obiectul conexiune, sa deschida conexiunea, sa faca operatiile de care are nevoie asupra bazei de date si apoi sa inchida conexiunea. Datorita modului in care celelalte clase ADO.Net sunt implementate uneori este nevoie de chiar mai putin decat atat.
Desi folosirea conexiunilor este mult simplificata in ADO.Net, programatorul trebuie sa le inteleaga foarte bine pentru a lua deciziile corecte. O conexiune este o resursa foarte importanta. Daca aplicatia va fi folosita pe o singura masina, asupra unei singure baze de date, importanta acestei resurse este mai putin clara. Dar daca este vorba de o aplicatie enterprise, folosita simultan de multi utilizatori asupra aceleiasi baze de date importanta conexiunii este mult mai clara. Fiecare conexiune reprezinta overhead pentru server si nu exista nici un server care sa suporte overhead infinit.
Un obiect SqlConnection
este la fel
ca orice alt obiect C#. de cele mai multe ori declararea si instantierea se
face in acelasi timp:
Obiectul SqlConnection
de mai sus
este instantiat folosind un constructor care primeste ca parametru un string.
Acest argument este stringul de conectare.
Data Source- Identifica masina server. Poate sa fie masina locala, numele unui computer din domeniu sau o adresa IP.
Initial Catalog- Numele bazei de date.
Integrated Security- Setat la valoarea SSPI pentru a face conexiunea folosind contul windows al utilizatorului.
User ID- Numele de utilizator configurat pe serverul SQL.
Password- Parola atasata utilizatorului de la User ID.
ConnectionTimeout : de tip int, cu accesor de get, valoare implicita 15; specifica numarul de secunde pentru care un obiect de conexiune ar trebui sa astepte pentru realizarea conectarii la server ınainte de a se genera o exceptie. Se poate specifica o valoare diferita de 15 ın ConnectionString folosind parametrul Connect Timeout :
ConnectionString folosind parametrul Connect Timeout :
Se poate specifica pentru Connect Timeout valoarea 0 cu semnificatia "asteapta oricat", dar se sugereaza sa nu se procedeze ın acest mod
Scopul instantierii unui obiect de tip SqlConnection
este ca alte obiecte ADO.Net sa poata lucra cu baza de date. Alte obiecte, cum
ar fi SqlDataAdapter
si SqlCommand
,
au constructori care primesc obiectul conexiune ca parametru. Atunci cand se
lucreaza cu o baza de date trebuie urmati pasii:
Exemplul urmator prezinta modul de folosire a unei conexiuni, pas cu pas:
using System;Inchid conexiunea la baza de date. Ramura finally se executa indiferent daca a fost o eroare sau nu (s-a intrat pe catch sau nu)
}
Asa cum se vede la linia 19 deschiderea conexiunii se face apeland metoda Open()
a instantei SqlConnection
. Orice
operatii asupra unei conexiuni care nu a fost inca deschisa genereaza o
exceptie.
Inainte de a folosi conexiunea trebuie sa instiintam celelalte obiecte
ADO.Net despre care conexiune este vorba. Facem acest lucru la linia 22 din
figura 4, trimitand ca parametru constructorului SqlCommand
obiectul conn
. Orice operatie pe care o va
face instanta cmd
va folosi aceasta
conexiune.
Obiectul care foloseste conexiunea este cmd
,
de tipul SqlCommand
. Acesta face o
interogare in baza de date adupra tabelului Customers
.
Rezultatul este intors intr-un obiect de tipul SqlDataReader
,
iar in bucla while este afisat
continutul primei coloane din fiecare rand din tabelul obtiunut (noi stim ca
aceasta coloana este coloana CustomerID). Important de retinut este ca
obiectele SqlCommand
si SqlDataReader
folosesc un obiect de tipul SqlConnection
,
si astfel stiu cu care baza de date sa lucreze.
Atunci cand am terminat de folosit obiectul conn
inchidem conexiune (linia 48). Lasarea conexiunii deschise are implicatii grave
in performanta aplicatiei.
Obiectele de tipul SqlCommand
permit specificare tipului de actiune asupra bazei de date. De exemplu se poate
face o interogare, inserare, modificare sau stergere
Instantierea unui obiect de tipul SqlCommand
se face ca la linia 22 din figura 4. Aceste este modul cel mai des intalnit de
instantiere. Ia ca parametru un string, care este comanda ce va fi executata,
si o referinta la un obiect de tipul SqlConnect
.
Atunci cand faci o interogare in baza de date, obtii un tabel rezultat care
trebuie sa poata fi vizualizat. Pentru a obtine acest lucru folosind obiecte SqlCommand
este folosita metoda ExecuteReader
care intoarce
un obiect de tipul SqlDataReader
. Exemplul de
mai jos arata modul de obtinere a unei instante SqlDataReader
.
SqlDataReader rdr = cmd.ExecuteReader();
Pentru a insera valori intr-o baza de date trebuie apelata functia ExecuteNonQuery
pe un obiect SqlCommand
. Exemplul
urmator arata modul de inserare intr-o baza de date.
cmd.ExecuteNonQuery();
Modificarea si stergerea datelor dintr-o baza de date se face la fel ca si
inserarea, dar ca punem primul parametru al constructorului SqlCommand
pe valoarea corespunzatoare.
Uneori avem nevoie dintr-o baza de date doar de o singura valoare, care
poate fi suma, media, etc. inregistrarilor dintr-un tabel. Apeland ExecuteReader
si apoi calculand acea valoare in program nu este cea mai eficienta metoda de a
ajunge la rezultat. Cea mai buna metoda este sa lasam baza de date sa faca ceea
ce este necesar si sa intoarca o singura valoare. Acest lucru il face metoda ExecuteScalar
:
int count = (int) cmd.ExecuteScalar();
Tipul SqlDataReader
este folosit pentru
a citi date in cea mai eficienta metoda posibila. NU poate fi folosit pentru
scriere. O data citita o informatie nu mai poate fi citita inca o data. SqlDataReader
citeste secvential date.
Datorita faptului ca citeste doar inainte (forward-only) permite
acestui tip de date sa fie foarte rapid in citire. Overhead-ul asociat este
foarte mic (overhead generat cu inspectarea rezultatului si a scrierii in baza
de date). Daca intr-o aplicatie este nevoie doar de informatii care vor fi
citite o singura data, sau rezultatul unei interogari este prea mare ca sa fie
retinut in memorie (caching) SqlDataReader
este solutia cea mai buna.
Obtinerea unei instante de tipul SqlDataReader
este putin diferita de instantierea normala - trebuie apelata metoda ExecuteDataReader
.
Daca pentru instantiere este folosit operatorul new veti obtine un obiect cu care nu
puteti face nimic pentru ca nu are o conexiune si o comanda atasate.
SqlDataReader
obtine datele
intr-un stream secvential. Pentru a citi aceste informatii trebuie apelata
metoda Read
; aceasta citeste un singur
rand din tabelul rezultat. Metoda clasica de a citi informatia dintr-un SqlDataReader
este de a itera intr-o bucla while.
Metoda Read
intoarce true cat timp mai este ceva de citit din
stream.
SqlDataReader
implementeaza si
indexatori :
Valoare indexului trebuie sa fie numele coloanei din tabelul rezultat.
Indiferent ca se foloseste un index numeric sau unul de tipul string indexatorii intorc totdeauna un obiect de tipul object fiind necesara conversia.
Dupa ce un reader nu mai este folosit acesta trebuie inchis apeland metoda
Metoda ExecuteReader() mai poate lua un argument optional de tip
enumerare CommandBehavior care descrie rezultatele si efectul asupra bazei de date:
. CommandBehavior.CloseConnection - conexiunea esteınchisa atunci cand obiectul de tip IDataReader este ınchis.
. CommandBehavior.KeyInfo - comanda returneza informatie despre coloane si cheia primara.
. CommandBehavior.SchemaOnly - comanda returneza doar informatie despre coloane.
. CommandBehavior.SequentialAccess - da posibilitatea unui DataReader
sa manipuleze ınregistrari care contin campuri cu valori binare de mare ıntindere. Acest mod permite ıncarcarea sub forma unui flux de date folosind GetChars() sau GetBytes().
. CommandBehavior.SingleResult - se returneaza un singur set de rezultate
. CommandBehavior.SingleRow - se returneaza o singura linie. De exemplu, daca ın codul anterior ınainte de while obtinerea obiectului reader s-ar face cu:
SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SingleRow);
Proprietati
1. IsClosed - proprietate read-only, returneaza true daca obiectul este deschis, false altfel
2. HasRows - proprietate booleana read-only care spune daca readerul contine cel putin o ınregistrare
3. Item - indexator care da acces la campurile unei ınregistrari
4. FieldCount - da numarul de campuri din ınregistrarea curenta
Metode
1. Close() - ınchide obiectul de citire si elibereaza resursele client. Este obligatoriu apelul acestei metode inaintea ınchiderii conexiunii.
2. GetBoolean(), GetByte(), GetChar(), GetDateTime(), GetDecimal(),
GetDouble(), GetFloat(), GetInt16(), GetInt32(), GetInt64(), GetValue(),
GetString() returneaza valorile campurilor din ınergistrarea curenta.
Preiau ca parametru indicele coloanei a carei valoare se cere. GetValue() returneaza un obiect de tip Object, pentru celelalte tipul returnat este descris de numele metodelor.
3. GetBytes(), GetChars() - returneaza numarul de octeti / caractere cititi dintr-un camp ce stocheaza o structura de dimensiuni mari; primeste ca parametri indicele de coloana (int), pozitia din acea coloana de unde se va ıncepe citirea, vectorul ın care se face citirea, pozitia ın buffer de la care se depun datele citite, numarul de octeti/caractere ce urmeaza
a fi cititi.
4. GetDataTypeName() - returneaza tipul coloanei specificat prin indice
5. GetName() - returneaza numele coloanei
6. IsDBNull() - returneaza true daca ın campul specificat prin index este
o valoare de NULL (din baza de date)
7. NextResult() - determina trecerea la urmatorul rezultat, daca aceasta
exista; ın acest caz returneaza true, altfel false (este posibil ca ıntr-un DataReader sa vina mai multe rezultate, provenind din interogari diferite)
8. Read() - determina trecerea la urmatoareaınregistrare, daca aceasta exista ın acest caz ea returneaza true. Metoda trebuie chemata cel putin o data, deoarece initial pozitia curenta este ınaintea primei ınregistrari.
Urmatoarele observatii trebuie luate ın considerare atunci cand se lucreaza cu un obiect DataReader :
. Metoda Read() trebuie sa fieıntotdeauna apelataınaintea oricarui acces la date; pozitia curenta la deschidere este ınaintea primei ınregistrari.
. Intotdeauna apelati metoda Close() pe un DataReader si pe conexiunea asociata cat mai repede posibil; ın caz contrar conexiunea nu poate fi reutilizata
. Procesarea datelor citite trebuie sa se faca dupa ınchiderea conexiunii; ın felul acesta conexiunea se lasa libera pentru a putea fi reutilizata.
Este posibil ca ıntr-un DataReader sa se aduca mai multe seturi de date.
Acest lucru ar micsora numarul de apeluri pentru deschiderea unei conexiuni la stratul de date. Obiectul care permite acest lucru este chiar cel de tip Command:
string select = 'select * from Categories; select * from customers';
SqlCommand command = new SqlCommand ( select, conn );
conn.Open ();
SqlDataReader reader = command.ExecuteReader ();
Trecerea de la un set de date la altul se face cu metoda NextResult() a
obiectului de tip Reader :
do
tt', reader[0], reader[1] );
}
}while ( reader.NextResult () );
Pana acum am vazut cum putem efectua operatii asupra unei baze de date
folosind obiecte de tipul SqlCommand
si SqlDataReader
. Problema cu aceasta
abordare este ca pe parcursul intregii tranzactii conexiunea trebuie sa fie
deschisa.
Voi prezenta in continuare o metoda care nu necesita o conexiune permanenta
la o baza de date - si anume folosind obiecte de tipul DataSet
si SqlDataAdapter
.
Un DataSet
este o reprezentare in
memorie a unui data store (un sistem de stocare si obtinere a datelor). Un DataSet
contine o multime de tabele asupra carora se pot executa diverse operatii. Un DataSet
doar retine informatii si nu interactioneaza cu un data source. SqlDataAdapter
este cel care se ocupa administrarea conexiunilor cu data source si ofera
comportamentul de lucru in mod deconectat. SqlDataAdapter
deschide o conexiune doar atunci cand este nevoie si o inchide imediat ce si-a
terminmat treaba. De exemplu SqlDataAdapter
realizeaza urmatoarele operatiuni atunci cand trebuie sa populeze un DataSet
:
DataSet
-ul;si urmatoarele operatiuni atunci cand trebuie sa faca update in baza de date:
DataSet
in baza de date;Intre operatiunea de populare a DataSet
-ului
si cea de update conexiunile la data source sunt inchise. Intre aceste operatii
in DataSet
se poate scrie sau citi.
Acestea sunt mecanismele de a lucra in mod deconectat. Pentru ca aplicatia tine
deschisa conexiunea la baza de date doar atunci cand este necesar, devine mai
scalabila.
Crearea unui obiect de tipul DataSet
se face folosind operatorul new
DataSet dsCustomers = new DataSet ();
Constructorul unui DataSet
nu necesita
parametri. Exista totusi o supraincarcare a acestuia care primeste ca parametru
un string, si este folosit atunci
cand trebuie sa se faca o serializare a datelor intr-un fisier XML. In acest
moment avem un DataSet
gol si avem nevoie
de un SqlDataAdapter
pentru a-l popula.
Un obiect SqlDataAdapter
contine mai
multe obiecte SqlCommand
si un obiect SqlConnection
pentru a citi si scrie date.
SqlDataAdapter daCustomers = new SqlDataAdapter ('SELECT CustomerID, CompanyName FROM Customers', conn)
Codul de mai sus creaza un obiect de tipul SqlDataAdapter
,
daCustomers
. Comanda SQL specifica
cu ce date va fi populat un DataSet
,
iar conexiunea conn
trebuie sa fi fost
creata anterior, dar nu si deschisa. Responsabilitatea deschiderii conexiunii
revine adapterului la apelul metodelor Fill
si Update
.
SqlDataAdapter
contine mai multe
obiecte comanda: cate unul pentru inserare, update, delete si select. Prin
intermediul constructorului putem instantia doar comanda de interogare.
Instantierea celorlalte se face fie prin intermediul proprietatilor pe care le
expune SqlDataAdapter
, fie folosind
obiecte de tipul SqlCommandBuilder
.
SqlCommandBuilder cmdBldr = new SqlCommandBuilder (daCustomers);
La initializarea unu SqlCommandBuilder
am apelat un constructor care primeste ca parametru un adapter, pentru care vor
fi construite comenzile. SqlCommandBuilder
are limitari: nu poate construi decat comenzi simple si care se aplica unui
singur tabel. Atunci cand trebui ca sa facem comenzi care vor folosi mai multe
tabele este recomandata construirea separata a comnezilor si apoi atasarea lor
adapterului folosind proprietati.
O data ce avem cele doua instante, DataSet
si SqlDataAdapter
, putem sa populam DataSet
-ul.
daCustomers.Fill (dsCustomers, 'Customers');
Metoda Fill
din exemplul anterior
primeste doi parametri: un DataSet
pe care-l va popula si un string care va fi numele tabelului (nu numele
tabelului din baza de date, ci al tabelului rezultat in DataSet
)
care va fi creat. Scopul acestui nume este identificarea ulterioara a
tabelului. In cazul in care nu este specificat nici un nume de tabel, acestea
vor fi adaugate in DataSet
sub numele Table1,
Table2,
Un DataSet
poate fi folosit ca data
source pentru un DataGrid
atat in ASP.Net
cat si pentru cel din Windows Forms. Mai jos este prezentat un exemplu de
legare a unui DataSet
la un DataGrid
:
dgCustomers.DataMembers = 'Customers';
La linia 2 setez un DataSet
ca DataSource
pentru un DataGrid
. Acest lucru ii spune
grid-ului ce informatii sa afiseze. Un grid stie sa afiseze mai multe tabele
dintr-un DataSet
, afisand un semn
'+' permitandu-i utilizatorului sa aleaga care tabel sa fie afisat
din cele disponibile. Pentru a suprima afisarea acelui semn '+' din
GUI am setat si proprietatea DataMembers
pe numele tabelului care va fi afisat. Numele tabelului este acelasi care l-am
folosit ca parametru in apelul metodei Fill
Dupa ce au fost facute modificari intr-un DataSet
acestea trebuie scrise si in baza de date. Actualizarea se face prin apelul
metodei Update
daCustomers.Update (dsCustomers, 'Customers');
Atuci cand lucrati cu bazele de date veti avea nevoie, de cele mai multe ori sa filtrati rezultatul dupa diverse criterii. De obicei acest lucru se face in functii de niste criterii pe care utilizatorul le specifica (ex: vreti sa vedeti doar clientii anume oras).
Dupa cum am vazut, o interogare asociata unui obiect SqlCommand
este un simplu string. Cea mai simpla metoda de filtrare a rezultatelor este sa
construiti acel string in mod dinamic, dar aceasta metoda nu este recomandata.
'SELECT * FROM Customers WHERE city = '' + inputCity + ;
Motivul pentru care o astfel de construire a unei interogari este
nerecomandata este ca nu se poate avea incredere in input-ul utilizatorului. De
obicei inputCity
este introdus de
utilizator intr-un TextBox
. Folosind acel TextBox
un utilizator rau intentionat poate sa introduca cod care poate duce la
coruperea bazei de date, accesarea informatiilor confidentiale, etc.
In loc sa construiti dinamic stringul de interogare folositi interogari cu parametri. Orice valoare pusa intr-un parametru nu va fi tratata drept cod SQL, ci ca valoare a unui camp, facand aplicatia mai sigura. Pentru a folosi interogari cu parametri urmati pasii:
SqlCommand
folosind parametri;SqlParameter
asignand valorile
corespunzatoare;SqlParameter
la obiectul SqlCommand
, folosind proprietatea Parameters
.Deci primul pas este construirea unui string de interogare parametrizat. Pentru a specifica locul unde vor fi inserate valorile parametrilor folositi marcatorul @. Sintaxa este urmatoarea:
// 1. Declarati un obiect SqlCommand care are stringul de interogare parametrizat'SELECT * FROM Customers WHERE city = @City', conn);
In contructorul din exemplul anterior stringul de interogare contine un
parametru @City
. Atunci cand comanda va fi
executata in string @City
va fi inlocuit cu
valoarea aflata in obiectul SqlParameter
atasat. In exemplul de mai sus folosim o interogare cu un singur parametru, dar
pot exista oricati parametri, si pentru fiecare din acestia trebuie sa asociem
un obiect SqlParameter
. In cazul in care
pentru un parametru din stringul de interogare nu avem asociata o instanta de
tipul SqlParameter
vom obtine o eroare
la rulare. Acelasi lucru se intampla si daca avem mai multe instante SqlParameter
pentru un parametru.
Acum trebuie sa declaram o instanta de tipul SqlParameter
:
param.Value = inputCity;
Si acum trebuie sa adaugam acet parametru obiectului comanda:
cmd.Parameters.Add(param);
Tranzactii
O tranzactie este un set de operatii care se efectueaza fie ın ıntregime,fie deloc.
Tranzactiile satisfac niste proprietati
. atomicitate - toate operatiile din tranzactie ar trebui sa aiba success sau sa esueze ımpreuna
. consistenta - tranzactia duce baza de date dintr-o stare stabila ın alta
. izolare - nici o tranzactie nu ar trebui sa afecteze o alta care ruleaza ın acelasi timp
. durabilitate - schimbarile care apar ın tipul tranzactiei sunt permanent stocate pe un mediu.
Sunt trei comenzi care se folosesc ın context de tranzactii:
. BEGIN - ınainte de executarea unei comenzi SQL sub o tranzactie, aceasta trebuie sa fie initializata
. COMMIT - se spune ca o tranzactie este terminata cand toate schimbarile cerute sunt trecute ın baza de date
. ROLLBACK - daca o parte a tranzactiei esueaza, atunci toate operatiile efectuate de la ınceputul tranzactiei vor fi neglijate
Schema de lucru cu tranzactiile
1. deschide conexiunea la baza de date
2. ıncepe tranzactia
3. executa comenzi pentru tranzactie
4. daca tranzactia se poate efectua (nu sunt exceptii sau anumite conditii sunt ındeplinite), efectueaza COMMIT, altfel efectueaza ROLLBACK
5. ınchide conexiunea la baza de date
Copyright © 2024 - Toate drepturile rezervate