Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pw:laboratoare:02 [2023/02/23 11:19]
ciprian.dobre
pw:laboratoare:02 [2023/03/13 18:00] (current)
ciprian.dobre [Dependency injection]
Line 4: Line 4:
  
 Scopul acestui laborator este de a va familiariza cu un concept foarte important in dezvoltarea aplicatiilor in general, anume **Dependency injection**,​ dar de asemenea sa va familiazati si interactiunea cu baza de date folosind un **ORM**. Scopul acestui laborator este de a va familiariza cu un concept foarte important in dezvoltarea aplicatiilor in general, anume **Dependency injection**,​ dar de asemenea sa va familiazati si interactiunea cu baza de date folosind un **ORM**.
 +
 +Pentru acest laborator o sa aveti nevoie sa descarcati si sa urmariti indicatiile din codul de pe [[https://​gitlab.com/​mobylabwebprogramming/​dotnetbackend|Gitlab]].
  
 ===== Dependency injection ===== ===== Dependency injection =====
Line 9: Line 11:
 Un motiv pentru care limbaje cum sunt Java si C# sunt populare pentru dezvoltarea de aplicatii este faptul ca suporta reflexie la runtime, adica programul la runtime poate face introspectie si poate, de exemplu, sa creeze instante de obiecte fara sa fie explicit programat in acest sens.  Un motiv pentru care limbaje cum sunt Java si C# sunt populare pentru dezvoltarea de aplicatii este faptul ca suporta reflexie la runtime, adica programul la runtime poate face introspectie si poate, de exemplu, sa creeze instante de obiecte fara sa fie explicit programat in acest sens. 
  
-Acest lucru a ajutat la implementarea de dependency injection in aceste limbaje ce se rezuma doar la faptul ca la runtime componentele sunt instantiate rand pe rand de la cele mai simple la cele mai complexe iar instantele ​componenteleor ​sunt pasate ca paramentri pentru instantierea altor componente.+Acest lucru a ajutat la implementarea de dependency injection in aceste limbaje ce se rezuma doar la faptul ca la runtime componentele sunt instantiate rand pe rand de la cele mai simple la cele mai complexe iar instantele ​componentelor ​sunt pasate ca paramentri pentru instantierea altor componente.
  
 In exemplul de mai jos se poate vedea cum este declaranta o componeta, parametri dati la constructor sunt pasati de framework atunci cand se cere aceasta componenta, observati ca parametri sunt interfete. De obicei se injecteaza interfete, nu implementari efective motivul fiind ca pot exista mai multe implementari pentru o interfata care pot fi schimbate in functie de necesitati cum ar fi pentru testare, de exemplu se poate inlocui implementarea de productie cu una de test pentru interceptarea apelurilor de metode ale acelui serviciu. In exemplul de mai jos se poate vedea cum este declaranta o componeta, parametri dati la constructor sunt pasati de framework atunci cand se cere aceasta componenta, observati ca parametri sunt interfete. De obicei se injecteaza interfete, nu implementari efective motivul fiind ca pot exista mai multe implementari pentru o interfata care pot fi schimbate in functie de necesitati cum ar fi pentru testare, de exemplu se poate inlocui implementarea de productie cu una de test pentru interceptarea apelurilor de metode ale acelui serviciu.
Line 67: Line 69:
 De mentionat, exista 3 tipuri de lifetime pentru instantele componentelor,​ anume: De mentionat, exista 3 tipuri de lifetime pentru instantele componentelor,​ anume:
   * **Singleton** – pe durata programului doar o singura instanta a acelei componente este instantiata,​ de fiecare data cand se cere componenta aceiasi instanta este returnata. Un exmplu este ILogger care este instantiat o singura data pentru fiecare parametru generic.   * **Singleton** – pe durata programului doar o singura instanta a acelei componente este instantiata,​ de fiecare data cand se cere componenta aceiasi instanta este returnata. Un exmplu este ILogger care este instantiat o singura data pentru fiecare parametru generic.
-  * **Transient** – de fiecare data cand se cere o componenta se returneaza o noua instanta. Exemple de componente transient sunt controllerele,​ la fiecare cerere HTTP o noua instanta de controller este create ​pentru tratarea cererii.+  * **Transient** – de fiecare data cand se cere o componenta se returneaza o noua instanta. Exemple de componente transient sunt controllerele,​ la fiecare cerere HTTP o noua instanta de controller este creata ​pentru tratarea cererii.
   * **Scoped** – instantele returnate sunt unice pe fiecare scope. Un exemplu este contextul de baza de date.   * **Scoped** – instantele returnate sunt unice pe fiecare scope. Un exemplu este contextul de baza de date.
  
Line 84: Line 86:
 ===== Baza de date si ORM ===== ===== Baza de date si ORM =====
  
-Pe langa logica aplicatiei, si vom discuta la laboratoarele urmatoare cum se implementeaza,​ trebuie sa existe persistenta datelor peste care se efectueaza logica efectiva. In acest sens majoritatea aplicatiilor folosesc baze de date, pentru a simplifica interactiunea programelor cu baza de date au fost implementate **ORM**-uri (**Object-relational mapping**), acestea sunt framework-uri care fac o corespondenta a tabelelor si a tipurilor de date din baza de date cu obiectele, numite entitati, si tipurile declarate in codul aplicatiei. ​+Pe langa logica aplicatiei, si vom discuta la laboratoarele urmatoare cum se implementeaza,​ trebuie sa existe persistenta datelor peste care se efectueaza logica efectiva. In acest sens majoritatea aplicatiilor folosesc baze de date, pentru a simplifica interactiunea programelor cu baza de date au fost implementate **ORM**-uri (**Object-relational mapping**), acestea sunt framework-uri care fac o corespondenta a tabelelor si a tipurilor de date din baza de date cu obiectele, numite ​**entitati**, si tipurile declarate in codul aplicatiei.  
 + 
 +ORM-urile expun in general o interfata generica care poate fi folosita pentru mai multe baze de date (PostgreSQL,​ MariaDB sau SQLServer) folosind acelasi cod chiar daca pot exista particularizari pentru fiecare, aceste implementari specifice se gasesc in divese biblioteci de pe [[https://​www.nuget.org/​|NuGet]]. Interfata generica este expusa printr-un **context** de baza de date, contextul nu este altceva decat un client pentru baza de date care serializaza/​deserializeaza cereri si obiecte si mai serveste ca cache pentru entitati, in .NET contextul o sa fie o clasa derivata de la **DBContext**
  
-ORM-urile expun in general o interfata generica care poate fi folosita pentru mai multe baze de date (PostgreSQL,​ MariaDB sau SQLServer) folosind acelasi cod chiar daca pot exista particularizari pentru fiecare. 
 <note warning> <note warning>
 Va recomandam sa folositi baze de date SQL, majoritatea aplicatiilor nu o sa aiba nevoie de baze de date NoSQL iar bazele de date clasice va vor satisfice cel mai probabil nevoile. ​ Va recomandam sa folositi baze de date SQL, majoritatea aplicatiilor nu o sa aiba nevoie de baze de date NoSQL iar bazele de date clasice va vor satisfice cel mai probabil nevoile. ​
Line 185: Line 188:
  
 Cand se implementeaza o schema de baza de date aceasta poate suferii diverse modificari in timpul dezvoltarii si maturizarii aplicatiei. Din acesta cauza, modificarile pe schema de baza de date trebuie sa fie efectuate incremental,​ adica orice modificare se aplica peste modificarile precedente, si de aceia exista conceptul de migrare. O migrare este o transformare,​ adesea reversibila,​ a schemei bazei de date care sa reflecte schimbarile din cod. Cand se implementeaza o schema de baza de date aceasta poate suferii diverse modificari in timpul dezvoltarii si maturizarii aplicatiei. Din acesta cauza, modificarile pe schema de baza de date trebuie sa fie efectuate incremental,​ adica orice modificare se aplica peste modificarile precedente, si de aceia exista conceptul de migrare. O migrare este o transformare,​ adesea reversibila,​ a schemei bazei de date care sa reflecte schimbarile din cod.
-In Entity Framework puteti folosi migrari instaland dotnet-ef : ''​dotnet tool install --global dotnet-ef --version 6.*''​.+In Entity Framework puteti folosi migrari instaland ​**dotnet-ef** 
 +<​code>​dotnet tool install --global dotnet-ef --version 6.*</​code>​ 
 + 
 +O data ce au fost create entitatile si configurate corespunzator (vedeti configurarile pentru entitati din din proiectul laboratorului) se poate rula comanda de generare a migrarilor avand baza de date deschisa:  
 +<​code>​dotnet ef migrations add <​nume_migrare>​ --context <​nume_clasa_context>​ --project <​proiect_cu_migrarile>​ --startup-project <​proiect_cu_startup></​code>​ 
 + 
 +In codul din laborator migrarile o data create for fi aplicate automat la prima cerere facuta catre baza de date. Pentru mai multe informatii despre migrari si uneltele in linie de comanda puteti consulta documentatia de [[https://​learn.microsoft.com/​en-us/​ef/​core/​managing-schemas/​migrations/?​tabs=dotnet-core-cli|aici]]. 
 + 
 +===== Informatii suplimentare pentru cine doreste ===== 
 + 
 +Pentru .NET ORM-ul se numeste Entity Framework si ca o particularitate acesta nu se foloseste de cereri SQL scrise de programator ci se pot specifica functional prin **LINQ** (**L**anguage **In**tegrated **Q**uery), framework-ul abstractizeaza prin interfata functionala cererile iar acestea sunt traduse in cereri specifice pentru fiecare tip de baza de date. 
 +Mai jos e un exemplu de cum se traduce codul din LINQ in SQL de Postgres. 
 +<​code>​ 
 +        var search = "​test";​ 
 +        await DbContext.Set<​UserFile>​() 
 +        .Where(e => EF.Functions.Like(e.Name,​ $"​%{search}%"​)) 
 +        .OrderByDescending(e => e.CreatedAt) 
 +        .Select(e => new UserFileDTO 
 +        { 
 +            Id = e.Id, 
 +            Name = e.Name, 
 +            Description = e.Description,​ 
 +            CreatedAt = e.CreatedAt,​ 
 +            UpdatedAt = e.UpdatedAt,​ 
 +            User = new() 
 +            { 
 +                Id = e.User.Id,​ 
 +                Email = e.User.Email,​ 
 +                Name = e.User.Name,​ 
 +                Role = e.User.Role 
 +            } 
 +        }).ToListAsync();​  
 +</​code>​ 
 +Acest cod se traduce in urmatorul SQL. 
 +<​code>​ 
 +select uf."​Id",​ uf."​Name",​ uf."​Description",​ uf."​CreatedAt",​ uf."​UpdatedAt",​ u."​Id",​ u."​Email",​ u."​Name",​ u."​Role"​ from "​UserFile"​ uf  
 +left join "​User"​ u on u."​Id"​ = uf."​UserId"​  
 +where uf."​Name"​ like '%test%' 
 +order by uf."​CreatedAt"​ desc 
 +</​code>​ 
 + 
 +In LINQ metodele pe setul de baza de date sunt in coresondenta unu la unu cu cele din SQL, **proiectia/​select** corespunde la .**Select**,​ **filtrarea/​where** corespunde la **.Where** si **sortarea/​order** la **.OrderBy**. 
 + 
 +Astfel, folosind un ORM se pot implementa componente de tip **repository** care fie sa interactioneaze cu ORM-ul pe entitati specifice cum ar fi entitatea pentru utilizatori sau sa fie generice iar cererile sa fie grupate in design pattern-ul de **specificatii**. O specificatie in contextul de design pattern este un obiect care contine cererea catre baza de date pentru a fi refolosita in mai multe parti ale codului. Puteti vedea in codul din laborator cum sunt implementate specificatiile si repository-ul generic. 
 + 
 +Un lucru foarte important de stiut aici e ca entitatiile odata extrase din baza de date sunt legate implicit la contextul bazei de date si sunt urmarite de framework, aceste enitati sunt numite **“tracked”**,​ si nu vor fi consumate de garbage collector decat dupa ce contextul bazei de date este consumat mai intai.  
 + 
 +Este nerecomandat sa fie expuse entitatile bazei de date in controller, de aceia cel mai bine este ca entitatile sa fie transformate/​mapate in **DTO**-uri (**Data Transfer Objects**), adica obiecte simple care doar transfera informatiile din entitati si care pot fi consumate de garbage colletor independent de contextul bazei de date. De asemenea, nu toate informatiile din entitate pot fi necesare sau se doresc a fi expuse in fara serviciilor si este mai bine sa fie folosite DTO-uri si  pentru securitatea aplicatiei. 
 + 
 +===== Sarcini laborator ===== 
 + 
 +Descarcati codul din laborator de pe [[https://​gitlab.com/​mobylabwebprogramming/​dotnetbackend|Gitlab]] si urmatiti urmatoarele tipuri de clase: 
 +  * Entitati 
 +  * Configurari de entitati 
 +  * Specificatii 
 +  * Repository 
 + 
 +Creati prima migrare numita **InitialCreate** cu comanda de **dotnet-ef** si rulati proiectul cu baza de date pornita. Conectati-va la baza de date si urmariti schema bazei de date.
  
-O data ce au fost create entitatile si configurate corespunzator (vedeti configurarile pentru ​entitati ​din din proiectul laboratorului) se poate rula comanda ​de generare a migrarilor avand baza de date deschisa: ''​dotnet ef migrations add <​nume_migrare>​ --context <​nume_clasa_context>​ --project <​proiect_cu_migrarile>​ --startup-project <​proiect_cu_startup>''​+Incercati sa adaugati propriile ​entitati ​si creati noi migrari. Puteti ​de acum sa va creati schema bazei de date pentru proiect.
  
  
pw/laboratoare/02.1677143956.txt.gz · Last modified: 2023/02/23 11:19 by ciprian.dobre
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0