Objektově relační mapování a Entity Framework
15.05.2021ORM nám umožňuje pracovat s databází objektovým přístupem. Článek se věnuje základní konfiguraci a použití.
Slovníček
ORM | Objektově-relační mapování (ORM) je technika, která převádí hodnoty uložené v relační databázi na objekty v aplikaci a zajišťuje synchronizaci dat mezi databází a těmito objekty. |
EF | Entity Framework (EF) je open-source knihovna od Microsoftu, která umožňuje přistupovat k podporovaným relačním databázím, jako jsou SQL Server, PostgreSQL, MySQL, Oracle a další. |
Entita | Entita je třída, kterou mapujeme na databázovou tabulku. |
Navigation property | Navigační vlastnost (navigation property) je vlastnost entity, která reprezentuje relaci mezi tabulkami. Pomocí navigační vlastnosti můžeme do objektu načíst související data, na která se v databázi odkazujeme (ideálně cizím klíčem, který ale není povinný). |
LINQ | Jedná se o dotazovací jazyk integrovaný do C#, pomocí kterého můžeme pracovat s SQL daty, ADO.NET datasety, XML dokumenty, .NET kolekcema apod. LINQ nabízí dvojí syntaxi: lze použít dotazy podobné SQL nebo Fluent API. S EF tvoří vhodný doplněk. |
Obsah
Úvod
Použití ORM je dnes již standardem a Entity Framework je běžnou volbou v prostředí .NET. V příkladu budu používat aktuální verzi EF Core 5 ve variantě Code First s existující databází, kterou preferuji pro stávající i nové projekty. Tato varianta umožňuje využít nástroje SSDT, pokud se připojujeme k SQL Serveru.
K dispozici jsou i další možnosti, například:
- Code First s novou databází.
- EF designer ve variantě Model-First nebo Database-First.
- Generování aplikační části z již existující databáze pomocí Reverse Engineeringu.
Příprava prostředí
Připravíme si databázi, která bude simulovat jednoduchý objednávkový systém a poběží nám na SQL Serveru.
create database OrderSystem;
use OrderSystem;
go
create table Customer
(
Id int identity not null,
FirstName nvarchar(255) not null,
LastName nvarchar(255) not null,
constraint PK_Customer_Id primary key (Id)
);
create table [Address]
(
CustomerId int not null,
City nvarchar(255) not null,
Street nvarchar(255) not null,
PostalCode nvarchar(255) not null,
constraint PK_Customer_CustomerId primary key (CustomerId),
constraint FK_Customer_CustomerId foreign key (CustomerId) references Customer (Id)
);
create table [Product]
(
Id int identity not null,
Number int not null,
[Name] nvarchar(255) not null,
constraint PK_Product_Id primary key (Id),
);
create table [Order]
(
Id int identity not null,
CustomerId int not null,
[Status] int not null,
OrderDate DateTime2(0) not null,
constraint PK_Order_Id primary key (Id),
constraint FK_Order_CustomerId foreign key (CustomerId) references Customer (Id)
);
create table [OrderLine]
(
OrderId int not null,
ProductId int not null,
Quantity int not null,
UnitPrice decimal(10,2) not null,
constraint PK_OrderLine_OrderId_ProductId primary key (OrderId, ProductId),
constraint FK_OrderLine_OrderId foreign key (OrderId) references [Order] (Id),
constraint FK_OrderLine_ProductId foreign key (ProductId) references Product (Id)
);
Konfigurace
Vytvoříme si konzolový projekt a nainstalujeme do něj balíček EF s podporou SQL Serveru.
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Vytvoření entit
Vytvoříme si třídy, které budou "reprezentovat" databázové tabulky.
- Třída nemusí mapovat všechny sloupce z tabulky.
- Pro použití navigačních vlastností není nutné mít nastavené cizí klíče na straně databáze. Cizí klíče jsou velmi vhodné, ale někdy bohužel nejsou k dispozici. Díky tomu se například můžeme odkazovat i na tabulky v jiné databázi pomocí synonym objektu.
- Navigační vlastnosti jsou volitelné a mapují se buď automaticky podle konvence nebo, v případě nestandardní jmenné konvence, manuálně pomocí data anotací nebo Fluent API.
- Pokud entita obsahuje konstruktor, který neodpovídá konvencím, musíme vytvořit další, který může být privátní a konvencím odpovídá. Osobně vytvářím vždy prázdný privatní konstruktor a nemusím to již v průběhu projektu řešit. Také není vhodné, aby EF používal veřejný konstruktor, pokud obsahuje další kód, například validace.
- Pokud bychom v entitě chtěli použít vlastnost, která není součástí tabulky, musíme použít metodu Ignore nebo atribut NotMapped.
Nastavení DbContext třídy
- Konfigurace EF a mapování entit na tabulky probíhá ve třídě, která dědí z tzv. DbContext třídy.
- Entity se mapují do tzv. DbSet objektů, které lze následně použít pro práci s daty.
- Každá entita, nad kterou budeme chtít provádět DML operace, musí mít nastavený primární klíč pomocí .HasKey(x => x.Id).
- Pokud entita primární klíč nemá, lze ji použít pouze pro čtení, což musíme EF oznámit pomocí .HasNoKey().
using EFCode.Model;
using Microsoft.EntityFrameworkCore;
namespace EFDemo.Database
{
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("dbo");
modelBuilder
.Entity<Customer>()
.ToTable("Customer")
.HasKey(x => x.Id);
modelBuilder
.Entity<Address>()
.ToTable("Address")
.HasKey(x => x.CustomerId);
modelBuilder
.Entity<Product>()
.ToTable("Product")
.HasKey(x => x.Id);
modelBuilder
.Entity<Order>()
.ToTable("Order")
.HasKey(x => x.Id);
modelBuilder
.Entity<OrderLine>()
.ToTable("OrderLine")
.HasKey(x => new { x.OrderId, x.ProductId });
}
public DbSet<Customer> Customers { get; set; }
public DbSet<Address> Addresses { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> OrderLines { get; set; }
}
}
Použití
Konfiguraci máme hotovou a můžeme se připojit k databázi. V našem případě, kdy pracujeme s konzolovým projektem, vytvoříme instanci databázového kontextu následujícím způsobem:
var connectionString = @"Server=localhost;Database=OrderSystem;Trusted_Connection=True;";
var optionsBuilder =
new DbContextOptionsBuilder<AppDbContext>()
.UseSqlServer(connectionString);
using var context = new AppDbContext(optionsBuilder.Options);
V případě ASP.NET Core frameworku pak můžeme použít dependency injection (DI), kdy v souboru Startup.cs do metody ConfigureServices() přidáme
services.AddDbContext<AppDbContext>(
options => options.UseSqlServer(connectionString));
- Pro čtení dat můžeme použít LINQ
- Tabulky můžeme joinovat, díky navigačním vlastnostem, pomocí metod Include a ThenInclude.
- Pokud bychom chtěli tabulky načíst pouze pro čtení, můžeme použít metodu AsNoTracking().
Následující příklad ukazuje, jak nahrát data do databáze, přečíst je a zobrazit v konzoli, a také jak data smazat pomocí přímého SQL dotazu:
Závěr
O Entity Frameworku bylo napsáno mnoho, ale snažil jsem se poskytnout základní přehled o tom, jak ORM nastavit a použít na příkladu, který zahrnuje více než dvě tabulky. 😅 Snad jsem nezapomněl na nic důležitého a těším se na setkání u dalšího článku. 🚀