ORM
Zacznijmy od tego, czym jest ORM. ORM z angielskiego Object-Relational Mapping, czyli mapowanie obiektowo relacyjne. Jest to sposób odwzorowania architektury obiektowej na relacyjną bazę danych.
ORM umożliwia nam tworzenie zapytań na bazie danych za pomocą obiektowego paradygmatu programowania, to znaczy możemy operować na zwykłych obiektach np. w języku C#, a pod spodem wywołania różnych metod czy właściwości będą zamieniane na zapytania SQL.
Załóżmy, że chcemy dodać informacje o nowej książce do bazy danych. Dzięki ORM'owi, zamiast pisać całe zapytanie, to znaczy
INSERT INTO Books (column1, column2)
VALUES (value1, value2);
Wystarczy wywołać metodę:
Books.Add(book);
I przekazać obiekt, który chcemy dodać.
Z punktu widzenia programisty, jest to wręcz idealne rozwiązanie, dzięki któremu programista, nawet bez wiedzy typowo bazodanowej może bez problemu współpracować z bazą danych. Używanie obiektowego języka jest dla nas programistów bardziej naturalne. Oczywiście warto znać, chociaż podstawowe składnie SQL'a, dzięki czemu będziesz mógł optymalizować te zapytania, ale nie jest to wiedza obowiązkowa na początek.
Poza tym oczywiście pisanie zapytań za pomocą języka obiektowego jest dużo szybsze, czasem wystarczy wywołać jedną metodę, czy właściwość, zamiast pisać wiele linii w SQL'u. Co więcej, na pewno nie zrobisz też żadnej literówki, co w SQL czasem się zdarza. Ponadto ORM'y mają sporo zaawansowanych funkcji takich jak wsparcie transakcji, migracje, czy obsługa puli połączeń.
Jeżeli chodzi o wady ORM, to pierwsze co przychodzi na myśl, to fakt, że stworzone zapytania mogą nie być szybkie i zbyt optymalne, jednak gwarantuje Ci, że przy odpowiedniej wiedzy, Twoje zapytania właśnie takie będą. Wystarczy tylko pamiętać o kilku podstawowych zasadach.
Entity Framework
Sam Entity Framework jest to open source'owy framework ORM dostępny na platformie .NET Framework. Obecnie jest jedną z najczęściej pobieranych bibliotek ze strony nuget. Umożliwia on programistom C# dostęp do bazy danych bez konieczności pisania czystych SQL'i, tylko za pomocą samych obiektów.
Wywołania metod, czy właściwości kontekstu zamienia na SQL'e, także bez znajomości zapytań SQL z łatwością możesz tworzyć aplikacje bazodanowe. Oczywiście, jeżeli będziesz miał taką potrzebę, to również oprócz obiektów możesz pisać też własne SQL'e.
Samo entity framework umożliwia nam tworzenie bazy danych na 4 sposoby, to znaczy mamy podejście code first, code first from database, database first oraz model first. Umożliwia nam zmapowanie modeli na tabele w bazie danych.
Możesz tworzyć modele za pomocą różnych konwencji, a także każdą konwencję możesz nadpisać dzięki odpowiedniej konfiguracji, dzięki temu mamy pełną kontrolę na bazą danych, którą tworzymy. Konfiguracje możemy tworzyć między innym za pomocą data annotations, a także fluent api.
Jego głównym celem jest zwiększenie produktywności programistów przy pracy z bazami danych. Możemy generować zapytania i komendy do odczytu i zapisu na bazie danych. Jeżeli chcesz wykonywać zapytania na bazie danych, to możesz posłużyć się składnią LINQ. Entity Framework wykona odpowiednie zapytanie i zmapuje wynik na obiekty Twojego modelu.
Wspiera wszystkie relacje, takie jak 1 do 1, 1 do wiele, czy wiele do wiele. Oprócz tego umożliwia tworzenie sparametryzowanych zapytań, śledzi zmiany na obiektach w pamięci, pozwala na odczyt, dodawanie, aktualizowanie i usuwanie danych. Umożliwia tworzenie indexów, migracji, transakcji. Wspiera także prace z procedurami, widokami, czy funkcjami.
Najwyższa wersja Entity Framework to wersja 6, która pierwotnie została opublikowana w 2013 roku. Od tego czasu są rozwijane tylko drobne poprawki do tej wersji, ponieważ od roku 2016 Entity Framework został zastąpiony przez Entity Framework Core. Wprowadziło to wiele zamieszania, ale to właśnie Entity Framework Core jest obecnie rozwijany i zalecane jest korzystanie z tej wersji frameworka. Musisz zwrócić uwagę na to, że Entity Framework i Entity Framework Core to dwie oddzielne biblioteki.
Entity Framework Core
Entity Framework Core co prawda miał być kolejną wersją Entity Framework, początkowo nawet miał nosić nazwę Entity Framework 7, ale tak naprawdę Entity Framework Core został napisany od zera.
Jest to lekki, rozszerzalny, open source'owy wspierający wiele platform framework, który jest częścią Microsoft .NET Core. Został stworzony, by był łatwiejszy w użyciu i miał lepsza wydajność niż jego poprzednik Entity Framework i jak pokazują testy, zostało to osiągnięte. Entity Framework Core jest sporo szybszy niż sam Entity Framework. Jest dostępny jako osobny nuget, co czasem powoduje małe zamieszanie.
Entity Framework Core wspiera między innymi takie bazy jak SQL Server, SQLite, PostgreSQL, MySQL, Oracle czy Firebird. Co prawda przede wszystkim jest przeznaczony do pracy z aplikacjami opartymi o .NET Core'a, jednak wersje 2.1 i 3.1 również mogą być używany z aplikacjami, które są rozwijany na platformie .NET 4.7.2. i wyższymi. Wersja Entity Framework Core 5.0 nie wspiera już aplikacji napisanych w .NET Framework.
Dla nowych projektów jest zalecane używanie najnowszej wersji Entity Framework Core. Możesz ją również używać w każdej aplikacji, czy to webowej, desktopowej, konsolowej, a nawet mobilnej.
W Entity Framework Core masz 2 podejścia do tworzenia bazy danych, to znaczy code first oraz database first. Także możesz tworzyć bazę danych od zera za pomocą Twoich klas w kodzie w C#, ale też możesz zacząć pracować na bazie danych, która już została wcześniej stworzona.
Mimo wszystko, jeżeli chodzi o główną ideę tych 2 frameworków, to jest ona bardzo podobna, nie jest tak, że to są dwa zupełnie inne światy, jedynie co, to różnią się w niektórych miejscach troszkę składnią, ale mniej więcej oba frameworki bazują na tym samym.
Praktyka – omówienie projektu
Przejdźmy na chwilę do praktyki. Pokaże Ci teraz, jak wyglądają podstawowe zapytania i komendy na bazie danych w Entity Framework Core.
Będziemy pracować na projekcie o nazwie Bookstore, w którym zainstalowałem pakiety dla Entity Framework Core. Jest to prosta aplikacja konsolowa, która składa się z kilku klas.
Tak wygląda klasa kontekstu:
namespace Bookstore
{
class ApplicationDbContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Category> Categories { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var builder = new ConfigurationBuilder()
.AddJsonFile($"appsettings.json", true, true);
var config = builder.Build();
optionsBuilder
.UseSqlServer(config["ConnectionString"]);
}
}
}
Zawiera ona 2 DbSety Books i Categories. Są to odwzorowania 2 tabel w bazie danych o tych samych nazwach. Tak wyglądają nasze encje:
namespace Bookstore
{
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int? CategoryId { get; set; }
public Category Category { get; set; }
}
}
namespace Bookstore
{
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Book> Books { get; set; } = new HashSet<Book>();
}
}
Encja Books ma takie właściwości jak Id, Name, Price oraz odwołanie do Category. Z kolei encja Category ma właściwości Id, Name oraz właściwość nawigacyjną kolekcję Books. Także książka może mieć 1 kategorię, ale 1 kategoria może zostać przypisana do wielu książek.
Na koniec connection string w pliku appsettings.json:
{
"ConnectionString": "Server=.\\SQLEXPRESS;Database=Bookstore;User Id=x;Password=y;"
}
Możemy przejść do klasy Program i głównej metody Main, gdzie napiszemy kilka podstawowych kwerend i komend za pomocą Entity Framework Core.
namespace Bookstore
{
class Program
{
static async Task Main(string[] args)
{
}
}
}
Dodawanie nowego rekordu do bazy danych
Aby dodać nowy rekord do bazy danych za pomocą Entity Framework Core, wystarczy na początek stworzyć nowy obiekt, w naszym przykładzie instancję klasy Book.
var book = new Book { Name = "Książka 1", Price = 10 };
Właściwość Category nie musimy wypełniać, jest ona niewymagalna, także dla prostoty przykładu pominiemy na tę chwilę ustawianie samej kategorii (przyda nam się do kolejnych bardziej zaawansowanych przykładów). Natomiast Id zostanie wygenerowane automatycznie przez bazę danych. Aby dodać taką książkę, potrzebujemy nową instancję klasy implementującej klasę DbContext, czyli w naszym przypadku ApplicationDbContext.
using (var context = new ApplicationDbContext())
{
}
Następnie wewnątrz tego using'a na kontekście i DbSet'cie Books wywołujemy metodę Add i przekazujemy nasz wcześniej wygenerowany obiekt.
context.Books.Add(book);
Zapisujemy zmiany na bazie danych.
await context.SaveChangesAsync();
W tym momencie rekord zostanie dodany już faktycznie do bazy danych.
Tak będzie wyglądać cały kod:
namespace Bookstore
{
class Program
{
static async Task Main(string[] args)
{
var book = new Book { Name = "Książka 1", Price = 10 };
using (var context = new ApplicationDbContext())
{
context.Books.Add(book);
await context.SaveChangesAsync();
}
}
}
}
Pobieranie danych z bazy danych
Żeby pokazać Ci, że te dane faktycznie zostaną dodane do bazy danych, możemy od razu najpierw je pobrać za pomocą EF Core, a następnie wyświetlić w konsoli naszej aplikacji. Najpierw przygotujemy sobie nową listę obiektów Book:
var books = new List<Book>();
A następnie, zaraz po zapisie pierwszej książki, pobierzemy sobie informacje o wszystkich książkach z bazy danych. W tym celu znowu odwołamy się do kontekstu, następnie DbSet'a Books i wywołamy metodę ToListAsync().
books = await context.Books.ToListAsync();
W tym miejscu zostaną najpierw pobrane wszystkie rekordy z tabeli Books, a następnie przypisane do zmiennej books. Poniżej najlepiej za pomocą pętli foreach, możemy wyświetlić te rekordy na konsoli.
foreach (var item in books)
{
Console.WriteLine($"Id: {item.Id}. Książka: '{item.Name}' - {item.Price:0.00} PLN.");
}
Wyświetlamy za pomocą interpolacji stringów najpierw id danej książki, a następnie jej nazwę oraz cenę.
Teraz uruchomimy aplikację i zweryfikujemy czy faktycznie dane zostały poprawnie dodane do bazy danych.
Jak widzisz mamy w bazie 1 rekord. Wygląda na to, że wszystko działa poprawnie. Wszystkie właściwości zostały uzupełnione. Jeżeli uruchomimy teraz aplikacje ponownie:
To również zostanie dodany kolejny rekord. Dla każdego z nich zostało wygenerowane unikalne Id. Także, tak działa dodawanie i wyświetlanie rekordów. Cały kod:
namespace Bookstore
{
class Program
{
static async Task Main(string[] args)
{
var book = new Book { Name = "Książka 1", Price = 10 };
var books = new List<Book>();
using (var context = new ApplicationDbContext())
{
context.Books.Add(book);
await context.SaveChangesAsync();
books = await context.Books.ToListAsync();
}
foreach (var item in books)
{
Console.WriteLine($"Id: {item.Id}. Książka: '{item.Name}' - {item.Price:0.00} PLN.");
}
}
}
}
Aktualizowanie danych w bazie danych
W celu zaktualizowania danych za pomocą EF Core pobierzmy sobie na początek jeden rekord, który już istnieje i który za chwilę będziemy aktualizować.
var bookToUpdate = await context.Books.FindAsync(1);
Za pomocą metody FindAsync pobierzemy z bazy danych rekord o Id 1. Następnie zmienimy wartość tej książki na 99.
bookToUpdate.Price = 99;
I zapiszemy wprowadzone zmiany w bazie danych.
await context.SaveChangesAsync();
Możemy uruchomić aplikację i zweryfikować zmiany.
Jak widzisz, teraz książka o Id 1, ma wartość 99 PLN, czyli ten rekord został poprawnie zaktualizowany. Tak samo możemy pobrać książkę o Id 2 i zaktualizować jej nazwę.
var bookToUpdate = await context.Books.FindAsync(2);
bookToUpdate.Name = "Ksiażka 2";
Jak widzisz nazwa również została zaktualizowana. Tak wygląda cały kod:
namespace Bookstore
{
class Program
{
static async Task Main(string[] args)
{
var books = new List<Book>();
using (var context = new ApplicationDbContext())
{
var bookToUpdate = await context.Books.FindAsync(2);
bookToUpdate.Name = "Ksiażka 2";
await context.SaveChangesAsync();
books = await context.Books.ToListAsync();
}
foreach (var item in books)
{
Console.WriteLine($"Id: {item.Id}. Książka: '{item.Name}' - {item.Price:0.00} PLN.");
}
}
}
}
Usuwanie danych z bazy danych
Na koniec pokaże Ci jeszcze jak usuwać dane z bazy danych za pomocą Entity Framework Core. W tym celu ponownie na kontekście i konkretnym DbSet'cie wywołujemy metodę Remove, przekazując obiekt, który chcemy usunąć. W tym miejscu wystarczy uzupełnić samo Id obiektu.
context.Books.Remove(new Book { Id = 1 });
Następnie zapisujemy te zmiany w bazie danych.
await context.SaveChangesAsync();
Uruchamiam teraz aplikację.
Zauważ, że w bazie danych nie ma już książki o Id 1. Jest tam tylko książka o Id 2. Cały kod:
namespace Bookstore
{
class Program
{
static async Task Main(string[] args)
{
var books = new List<Book>();
using (var context = new ApplicationDbContext())
{
context.Books.Remove(new Book { Id = 1 });
await context.SaveChangesAsync();
books = await context.Books.ToListAsync();
}
foreach (var item in books)
{
Console.WriteLine($"Id: {item.Id}. Książka: '{item.Name}' - {item.Price:0.00} PLN.");
}
}
}
}
PODSUMOWANIE
Tak wyglądaj 4 najprostsze kwerendy i komendy w Entity Framework Core. Nie napisaliśmy ani 1 linii w SQL'u, a nasze dane w bazie danych zostały zmienione. Jeżeli jeszcze nie miałeś styczności z tym frameworkiem, to koniecznie musisz nadrobić zaległości. W kolejnym artykułach będę jeszcze pokazywał kilka dobrych praktyk stosowania tego frameworka. Ma on duże możliwości, ale pisząc zapytania w EF Core musisz pamiętać o kilku podstawowych zasadach, dzięki którym Twoja zapytania będę szybkie, ale to już jest temat na kolejny artykuł.
Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności. Zapisz się na darmowy newsletter, gdzie co tydzień dzielę się wartościowymi materiałami w szczególności dotyczącymi C# i platformy .NET (darmowy zapis – newsletter).
Poprzedni artykuł - Programowanie a Studia. Czy Programista .NET Potrzebuje Studiów?.
Następny artykuł - 5 Najlepszych Praktyk z Entity Framework Core.