Technologie .NET - laboratorium 2
Post

Technologie .NET - laboratorium 2

Wstęp i opis

Podczas laboratoriów wykonany zostanie projekt encji wraz z klasami bazowymi, oraz zaprezentowane zostanie wykonanie połączenia do bazy danych wraz z wstępną konfiguracją platformy mapowania obiektowo-relacyjnego EF Core.

Projekt Encji

Desktop View

Dodanie encji do projektu jako klasy danych

Mając zaprojektowane encje, możemy przejść do ich implementacji. Zacznijmy od stworzenia nowego podkatalogu Entities w projekcie ApartmentRental.Infrastructure. Posłuży on do przechowywania wspomnianych modeli. W katalogu zaczniemy od wyodrębnienia klasy bazowej BaseEntity , która będzie dziedziczona przez wszystkie pozostałe encje. Klasa ta składać się będzie z trzech właściwości:

  • Id - identyfikator - typu int
  • DateOfCreation - data stworzenia - typu DateTime
  • DateOfUpdate - data aktualizacja - typu DateTime

Klasa DateTime , będzie typem naszych dwóch właściwości DateOfCreation oraz DateOfUpdate. Reprezentuje ona moment w czasie, wyrażony domyślnie jako data wraz z godziną, minutą i sekundą.

Aby stworzyć nową klasę w IDE JetBrains Rider, kliknij prawym przyciskiem myszy w folder Entities oraz wybierz Add->Class/Interface. Dodajmy teraz właściwości klasy. W C# domyślne pola do zapisywania oraz odczytywania danych w obiekcie wraz zachowaniem enkapsulacji (settery i gettery) mają uproszczoną składnie. Podczas dodawania nowych właściwości, podstawowe pola zapisu oraz odczytu dodajemy za pomocą dopisania wyrażenia { get; set;} po prawej stronie od nazwy danej właściwości. Przy pomocy środowiska programistycznego całą operację można wykonać szybciej przy pomocy wpisania skrótu prop, który wygeneruje nam odpowiednią linijkę, którą należy uzupełnić o wybrany typ właściwości oraz nazwę. Polecam również przeczytanie dokumentacji, która opisuje więcej przypadków oraz możliwości edycji pól odczytu oraz zapisu.

Desktop View Desktop View

Do stworzonej klasy BaseEntity oraz właściwości definiującej identyfikator encji, należy dodać jeszcze dwa atrybuty:

  • Key - służąca do oznaczania jednej lub więcej właściwości encji, które zapewniają jej unikalną tożsamość

  • DatabaseGeneratedAttribute - określający sposób, w jaki baza danych generuje wartości dla właściwości - wraz z opcją DatabaseGeneratedOption.Identity , przy pomocy której generowana jest nowa wartość identyfikatora, kiedy do bazy danych dodawany jest nowy wiersz

Gotowa klasa BaseEntity powinna prezentować się następująco:

Desktop View

Zadanie 1

  1. Dodaj brakujące klasy encji w folderze Entities: a. Address dziedzicząca po BaseEntity , o właściwościach: i. Ulica (string) ii. Numer mieszkania (nullable string) iii. Numer budynku (nullable string) iv. Miasto (string) v. Kod pocztowy (string) vi. Kraj (string) ii. Account dziedzicząca po BaseEntity , o właściwościach: i. Imię (string) ii. Nazwisko (string) iii. Email (string) iv. Numer telefonu (string) v. Flaga “Czy konto jest aktywne” (bool) iii. Apartment dziedzicząca po BaseEntity , o właściwościach: i. Wysokość czynszu (decimal) ii. Liczba pokoi (integer) iii. Wielkość mieszkania w metrach kwadratowych (integer) iv. Piętro (integer) v. Flaga „Czy jest winda” (bool) iv. Image dziedzicząca po BaseEntity , o właściwościach: i. Dane (tablica bajtów) v. Landlord dziedzicząca po BaseEntity , o właściwościach: i. Apartamenty (lista klasy Apartment) vi. Tenant dziedzicząca po klasie BaseEntity , o właściwościach: i. Apartament (klasa Apartment)

NuGet

Niezbędnym narzędziem każdej nowoczesnej platformy programistycznej jest mechanizm, dzięki któremu programiści mogą tworzyć, udostępniać i wykorzystywać użyteczny kod. Często taki kod jest łączony w “pakiety”, które zawierają skompilowany kod (jako biblioteki DLL) wraz z inną zawartością potrzebną w projektach korzystających z tych pakietów. W przypadku .NET (w tym .NET Core) wspieranym przez Microsoft mechanizmem udostępniania kodu jest NuGet. Definiuje on w jaki sposób pakiety dla .NET są tworzone, hostowane i konsumowane. Pakiet NuGet to pojedynczy plik ZIP z rozszerzeniem .nupkg który zawiera skompilowany kod (DLL), inne pliki związane z tym kodem oraz manifest zawierający takie informacje, jak np. numer wersji pakietu. Deweloperze, którzy posiadają kod którym chcą opublikować, tworzą pakiety i publikują je na (publicznym lub prywatnym) serwerze. Konsumenci pakietów otrzymują te pakiety z odpowiednich hostów, dodają je do swoich projektów, a następnie wywołują funkcjonalność pakietu w kodzie swojego projektu. NuGet sam zajmuje się wszystkimi pośrednimi szczegółami.

EF Core

Entity Framework (EF) Core jest lekką, rozszerzalną, otwarto-źródłową i wielo-platformową biblioteką dostępu do danych. EF Core służy jako mapper obiektowo-relacyjny (ORM), który:

  • umożliwia deweloperom .NET pracę z bazą danych przy użyciu obiektów
  • eliminuje konieczność pisania kodu dostępu do danych
  • wspiera większość silników bazodanowych

Dodanie EF Core i Sqlite do projektu

Wykorzystując NuGet, dodamy pakiety potrzebne do generacji mapowania obiektowo- relacyjnego, połączenia do bazy danych oraz do tworzenia tzw. plików migracyjnych. Prezentowane rozwiązania podczas laboratoriów opierać się będzie na lokalnej bazie danych Sqlite. W celu dodania pakietów, należy wybrać zakładkę NuGet znajdującą się na dole środowiska programistycznego JetBrains Rider.

Desktop View

W zakładce powinna wyświetlić się wyszukiwarka z listą pakietów, które można dodać do stworzonych na poprzednich laboratoriach projektów (by dodać pakiet do projektu wystarczy kliknąć przycisk „plusika”).

Zadanie 2

  1. Wyszukaj pakiet Microsoft.EntityFrameworkCore w wersji 6.0.3 i dodaj go do projektów ApartmentRental.API oraz ApartmentRental.Infrastructure
  2. Wyszukaj pakiet Microsoft.EntityFrameworkCore.Design w wersji 6.0.3 i dodaj go do projektu ApartmentRental.API
  3. Wyszukaj pakiet Microsoft.EntityFrameworkCore.Sqlite w wersji 6.0.3 i dodaj go do projektów ApartmentRental.API oraz ApartmentRental.Infrastructure

Konfiguracja EF Core i implementacja mapowania obiektowo-relacyjnego

Kontekst

Na sam początek zaczniemy od stworzenia logiki, która będzie obsługiwało kontekst bazodanowy w aplikacji. W projekcie ApartmentRental.Infrastructure należy stworzyć folder Context a w nim klasę MainContext. Utworzoną klasę należy rozszerzyć o klasę DbContext z przestrzeni Microsoft.EntityFrameworkCore oraz należy dodać pusty konstruktor oraz konstruktor przyjmujący klasę DbContextOptions i przekazać parametr options do rodzica.

Desktop View

Klasa DbContext jest połączeniem wzorców projektowych Repository oraz Unit of Work. Jej instancja reprezentuje sesję z bazą danych i jest używana do odpytywania oraz zapisywania instancji encji.

Connection string

Następnym krokiem który wykonamy jest skonfigurowania adresu do bazy danych. W tym celu musimy przeciążyć metodę OnConfiguring z argumentem DbContextOptionsBuilder. Po wcześniejszym zainstalowaniu pakietu Microsoft.EntityFrameworkCore.Sqlite wystarczy użyć metody UseSqlite ze wspomnianego powyżej argumentu i przekazać do niej connection string: DataSource=dbo.ApartmentRental.db. Jako że Sqlite jest naszą lokalną bazą danych, ta konfiguracja pozwoli nam na automatyczne wygenerowanie bazy w dalszej części laboratorium.

Desktop View

DbSet

Kolejnym krokiem konfiguracji oraz implementacji jest dodanie do klasy właściwości DbSet dla każdej utworzonej poprzednio encji. DbSet reprezentuje zbiór wszystkich encji danego typu, które znajdują się w kontekście lub które mogą być odpytane w bazie danych, poprzez operacje CRUD (create, read, update, delete). Zaimplementowany DbSet dla każdej enci prezentuje się następująco:

Desktop View

Powiązania pomiędzy encjami

Najczęstszym wzorcem relacji jest posiadanie właściwości nawigacyjnych zdefiniowanych na obu końcach relacji oraz stworzenie klucza obcego, zdefiniowanego w klasie encji zależnej. Jeśli para właściwości nawigacyjnych zostanie znaleziona pomiędzy dwoma typami, to zostaną one skonfigurowane jako odwrotne właściwości nawigacyjne tej samej relacji. Jeśli encja zależna zawiera właściwość o nazwie pasującej do jednego z tych wzorców, to zostanie ona skonfigurowana jako klucz obcy.

Całość została opisana w dokumentacji.

Przejdźmy teraz do implementacji powiązań pomiędzy encjami. Analizując projekt struktury bazodanowej, należy wysunąć poniższe powiązania:

  • Tabela Account posiada jeden klucz obcy AddressId do tabeli Address
  • Tabela Image posada jeden klucz obcy ApartmentId do tabeli Apartment
  • Tabela Landlord posiada jeden klucz obcy AccountId do tabeli Account
  • Tabela Tenant posiada jeden klucz obcy AccountId do tabeli Account
  • Tabela Apartment posiada trzy klucze obce AddressId , LandlordId oraz TenantId do tabel Address , Landlord , Tenant

W tym celu, w każdej klasie trzeba dodać właściwość opisującą klucz obcy oraz obiekt. Dla wyżej wymienionych tabel, wszystkie właściwości klas po implementacji prezentują się następująco:

Desktop View Desktop View Desktop View Desktop View Desktop View

Ostatnim etapem konfiguracji relacji pomiędzy encjami, jest zdefiniowanie jak encje powinny się zachować podczas usuwania rekordów. Przyjmiemy że jeśli w sytuacji której będzie usuwany wpis o danym apartamencie, to wraz z nim zostaną usunięte wszystkie zdjęcia. Podobną strategie przyjmiemy podczas kasowania danych dla właściciela obiektu. Podczas kasowania rekordu zostaną usunięte wszystkie powiązane z nim apartamenty. Aby osiągnąć opisane wyżej założenia, należy przeciążyć metodę OnModelCreating, która przyjmuje argument ModelBuilder. W ciele metedy używając funkcji argumentu, zdefiniujmy powiązanie pomiędzy encjami oraz kaskadową metodę kasowania danych.

Desktop View

Proces migracji - wygenerowania mapowań obiektowo-relacyjnych oraz stworzenie lokalnej bazy danych

W rzeczywistych projektach modele danych zmieniają się w miarę wdrażania nowych funkcjonalności. Podczas dodawania/usuwania encji lub ich właściwości musimy pamiętać o zmienianiu równiach schematu bazy danych, tak aby zachować odpowiednią synchronizację. Funkcja migracji w EF Core umożliwia przyrostową aktualizację schematu bazy danych w celu utrzymania go w synchronizacji z modelem danych aplikacji przy jednoczesnym zachowaniu istniejących danych w bazie. W ogólnym zarysie migracje działają w następujący sposób:

  • Gdy wprowadzana jest zmiana modelu danych, deweloper używa narzędzi EF Core, aby dodać odpowiednią migrację opisującą aktualizacje niezbędne do utrzymania synchronizacji schematu bazy danych. EF Core porównuje bieżący model z snapshotem starego modelu, aby określić różnice, po czym generuje pliki źródłowe migracji. Wygenerowane pliki można po operacji obejrzeć w kodzie źródłowym aplikacji.
  • Po wygenerowaniu nowej migracji można ją zastosować do bazy danych na różne sposoby. EF Core zapisuje wszystkie zastosowane migracje w specjalnej tabeli historii, dzięki czemu wie, które migracje zostały zastosowane, a które nie.

Przed rozpoczęciem procesu migracji, wymagane jest zainstalowanie odpowiedniego rozszerzenia do .Net SDK. W konsoli/terminalu wpisz poniższa komendę:

1
dotnet tool install —global dotnet-ef

Następnie w konsoli przejdź do głównego katalogu solucji ApartmentRental.API i wykonaj komendę:

1
2
dotnet ef migrations add initial —project ApartmentRental.Infrastructure —startup-project
ApartmentRental.API

Powyższa komenda wygeneruje pliki migracji. Możemy je zobaczyć w folderze Migrations w projekcie ApartmentRental.Infrastructure. Mają one formę klas c#.

Desktop View

Wygenerowane pliki migracji posłużą teraz do stworzenia (a w późniejszym czasie do aktualizacji) bazy danych. W tym celu w folderze w którym mieści się solucja projektu, wykonaj poniższą komendę:

1
2
dotnet ef database update —project ApartmentRentral.Infrastructure —startup-project
ApartmentRental.API

W okienku konsoli/terminala możemy zobaczyć wykonane komendy na bazie danych z pliku migracyjnego:

Desktop View

Plik z bazą powinien się wygenerować w folderze AparmentRental.API. Stworzoną bazę danych możemy obejrzeć chociażby za pomocą bezpłatnego narzędzia DbBrowser for Sqlite lub w środowisku programistycznym JetBrains Rider.

Desktop View

Zadania 3

  1. Usuń ze wszystkich projektów puste klasy Class1.cs
  2. Usuń klasę WeatherForecast.cs z projektu ApartmentRental.API
  3. Wykonaną pracę zacommituj i pushnij na stworzone na poprzednich zajęciach repozytorium i wyślij informację o zakończeniu pracy do prowadzącego