Deze handleiding maakt deel uit van het programmeertraject:
Inhoud
- Wat vooraf ging
- Inleiding
- Modellen mappen
- Eigenschappen/Properties mappen
- Sleutels mappen
- Verplichte invoer
- Maximale lengte
- Standaardwaarden
- De databank opvullen
Wat vooraf ging
- U bent vertrouwd met Klassen.
- U bent vertrouwd met de Entity Framework.
Inleiding
We kiezen voor een Code-First benadering van Entity Framework.
In de Code-First benadering vertrekt u vanuit de bestaande geprogrammeerde modellen en wordt de databank automatisch gegenereerd.
We gaan dus eerst deze modellen aanmaken op basis van de entiteiten.
Entiteiten hebben attributen die we gaan vertalen naar eigenschappen/properties in onze modellen.
Deze eigenschappen/properties hebben bepaalde kenmerken, ze zijn bv. een sleutel, ze zijn verplicht in te vullen, ze hebben een maximale lengte,… Deze kenmerken worden toegekend bij conventie en kunnen eventueel meer geduid worden door ofwel:
- Data annotatie (de traditionele manier)
- Fluent Api (een alternatief van Entity Framework)
We gaan de belangrijkste kenmerken bespreken. Wie alle kenmerken en alle opties wilt kennen verwijs ik naar de officiële handleiding.
In de introductie hebben we reeds onderstaande modellen bekeken.
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }
We hebben ook gezien dat, om deze klassen te mappen, we een klasse nodig hebben die overerft van DbContext.
using Microsoft.EntityFrameworkCore; public class BlogContext : DbContext { public BlogContext() { } //entiteiten mappen public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { } }
De methode OnModelCreating(ModelBuilder modelBuilder)
wordt gebruikt door de Fluent Api van Entity Framework.
Merk op dat onze modellen Blog en Post reeds gemapt zijn naar de achterliggende database via respectievelijk: public DbSet Blogs { get; set; }
en public DbSet Posts { get; set; }
.
We gaan met deze modellen verder werken (ze worden ook gebruikt in de officiële handleiding).
Relaties zien we in een volgende handleiding en negeer ik voorlopig.
Modellen mappen
Modellen worden gemapt naar tabellen in een databank.
Conventie
Modellen worden gemapt naar tabellen in de achterliggende databank als ze:
- opgenomen zijn in de
DbSet
in deDbContext
overgeërfde klasse. - of genoemd zijn in de
OnModelCreating(ModelBuilder modelBuilder)
. - of via een navigation property, anders gezegd, als ze gerelateerd zijn aan een reeds opgenomen model via een relatie.
Eén van deze methoden volstaat om een entiteit te mappen naar een tabel, gebruik je er toch meerdere, dan is dit niet foutief, maar overbodig.
Opgenomen in de DbSet
In het reeds bekeken voorbeeld worden de entiteiten Blog en Post gemapt naar een tabel omdat ze opgenomen worden in de DbSet
.
using Microsoft.EntityFrameworkCore; public class BlogContext : DbContext { //entiteiten mappen public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { } }
Genoemd in OnModelCreating(ModelBuilder modelBuilder)
In onderstaand voorbeeld wordt de entiteit Post gemapt naar een tabel omdat ze opgenomen wordt in de OnModelCreating(ModelBuilder modelBuilder)
, Blog wordt gemapt omwille van de bovenstaande regel (“Opgenomen in de DbSet
“).
using Microsoft.EntityFrameworkCore; class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>(); } }
Beide entiteiten mappen in OnModelCreating(ModelBuilder modelBuilder)
kan uiteraard ook.
using Microsoft.EntityFrameworkCore; class BlogContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(); modelBuilder.Entity<Post>(); } }
Navigation property
Bestaat er een Navigation property (relatie) tussen entiteiten, zie later, dan wordt de gerelateerde entiteit automatisch meegemapt.
In onderstaand voorbeeld bestaat een één op veel relatie tussen Blog en Post. Blog wordt gemapt volgens één van bovenstaande manieren en Post wordt automatisch meegemapt.
using Microsoft.EntityFrameworkCore; class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public Blog Blog { get; set; } }
- Het model Blog wordt gemapt omdat het is opgenomen in de van
DbContext
overgeërfde klasse viapublic DbSet Blogs { get; set; }
. - Het model Post wordt gemapt omdat het in relatie staat met het gemapte model Blog via
public List Posts { get; set; }
.
Modellen niet mappen
Modellen worden gemapt volgens bovenstaande conventies.
Wilt u een model niet mappen, voldoe dan niet aan bovenstaande conventies of geef duidelijk aan dat u dat model niet wilt mappen. Dit laatste doet u als volgt:
Data annotation
Voeg de attribute tag [NotMapped]
toe voor het model dat u niet wilt mappen.
In onderstaand voorbeeld wordt het model BlogMetadata niet gemapt.
public class Blog { public int BlogId { get; set; } public string Url { get; set; } public BlogMetadata Metadata { get; set; } } [NotMapped] public class BlogMetadata { public DateTime LoadedFromDatabase { get; set; } }
Fluent API
Voeg de functie Ignore()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt het model BlogMetadata niet gemapt.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Ignore<BlogMetadata>(); } }
Modellen onder een andere naam mappen
De door de gemapte Modellen gegenereerde tabellen krijgen standaard de Engelse meervoudsvorm klasnaam + s als naam. Wilt u dit niet dan moet u zelf de gewenste naam voor de tabel meegeven. Eventueel met een specifiek schema.
Data annotation
Voeg de attribute tag [Table("Tabelnaam")]
toe voor het model dat u wilt mappen onder een specifieke naam.
In onderstaand voorbeeld wordt het model Post gemapt onder de naam Posten.
[Table("Posten")] public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }
Wilt u eventueel ook een schema meegeven dan kan dit als volgt:
[Table("Posten", Schema = "blogging"))] public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }
Fluent API
Voeg de functie ToTable()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt het model Post gemapt onder de naam Posten.
class BlogContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>().ToTable("Posten"); } }
Wilt u eventueel ook een schema meegeven dan kan dit als volgt:
class BlogContext : DbContext { protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Post>().ToTable("Posten", schema: "blogging"); } }
Eigenschappen/Properties mappen
Eigenschappen/properties worden gemapt naar velden in een databank.
Conventie
Alle publieke eigenschappen/properties met een Get
en een Set
worden gemapt.
Eigenschappen/Properties niet mappen
Wilt u een eigenschappen/properties niet mappen, voldoe dan niet aan bovenstaande conventies of geef duidelijk aan dat u de eigenschap/property niet wilt mappen. Dit laatste doet u als volgt:
Data annotation
Voeg de attribute tag [NotMapped]
toe voor de eigenschap/property dat u niet wilt mappen.
In onderstaand voorbeeld wordt het model LoadedFromDatabase niet gemapt.
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [NotMapped] public DateTime LoadedFromDatabase { get; set; } }
Fluent API
Voeg de functie Ignore()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property LoadedFromDatabase niet gemapt.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().Ignore(b => b.LoadedFromDatabase); } }
Merk de Lambda-notatie op Ignore(b => b.LoadedFromDatabase)
.
De Lambda-syntax is (input-parameters) => expression
. In bovenstaand voorbeeld is de input-parameter = b. De b is een willekeurige benaming en mag dus ook anders geduid worden. => is de Lambda-operator.
Eigenschappen/properties mappen onder een andere naam
Vermits u zelf de namen van de eigenschappen/properties kunt kiezen zal er zelden nood zijn om deze een andere kolomnaam te geven. Moest u dit toch willen, dan kan u het als volgt doen:
Data annotation
Voeg de attribute tag [Column("kolomnaam")]
toe voor de eigenschap/property dat u van naam wilt wijzigen.
In onderstaand voorbeeld wordt de eigenschap/property BlogId met de naam blog_id gemapt.
public class Blog { [Column("blog_id")] public int BlogId { get; set; } public string Url { get; set; } }
Fluent API
Voeg de functie HasColumnName()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property BlogId met de naam blog_id gemapt.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.BlogId) .HasColumnName("blog_id"); } }
Datatypen mappen
Onderstaande tabel toont de conventionele mapping van datatypen.
C# Data Type | Mapping naar SQL Server Data Type |
int | int |
string | nvarchar(Max) |
decimal | decimal(18,2) |
float | real |
byte[] | varbinary(Max) |
datetime | datetime |
bool | bit |
byte | tinyint |
short | smallint |
long | bigint |
double | float |
char | geen mapping |
sbyte | geen mapping (throws exception) |
object | geen mapping |
Wilt u hiervan afwijken, dan kan dit.
Data annotation
Voeg de attribute tag [Column(TypeName = "Datatype")]
toe voor de eigenschap/property dat u van naam wilt wijzigen.
In onderstaand voorbeeld wordt de eigenschap/property URL gemapt naar het type [Column(TypeName = "varchar(200)")]
en de eigenschap/property Rating gemapt naar het type [Column(TypeName = "decimal(5, 2)")]
.
public class Blog { public int BlogId { get; set; } [Column(TypeName = "varchar(200)")] public string Url { get; set; } [Column(TypeName = "decimal(5, 2)")] public decimal Rating { get; set; } }
Fluent API
Voeg de functie HasColumnType()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property URL gemapt naar het type [Column(TypeName = "varchar(200)")]
en de eigenschap/property Rating gemapt naar het type [Column(TypeName = "decimal(5, 2)")]
.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(eb => { eb.Property(b => b.Url).HasColumnType("varchar(200)"); eb.Property(b => b.Rating).HasColumnType("decimal(5, 2)"); }); } }
Merk de verschillende Lambda-expressies op.
Sleutels mappen
Een sleutel (key) is een veld dat iedere instantie (record) uniek maakt. De sleutel zelf mag dus, in dezelfde tabel, geen tweemaal voorkomen. Een sleutel is vaak één veld (een Id) maar kan ook een combinatie van velden zijn en dan dient de combinatie van deze velden binnen de tabel uniek te zijn.
In relaties spreken we over Primary keys en Foreign keys. Relaties bespreken we in een volgende handleiding meer in detail.
Conventie
Alle eigenschappen/properties met de naam Id of een combinatie van de klasnaam + Id worden als sleutel gemapt.
Niet samengestelde sleutels van het type short, int, long, of Guid krijgen automatisch een waarde toegekend. Ik ga hier niet dieper op ingaan maar wie wilt kan hier verder lezen hoe u automatisch waarden kunt toekennen.
In onderstaand voorbeeld zijn de sleutels de eigenschappen/properties BlogId en PostId.
public class Blog { public int BlogId { get; set; } public string Url { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } }
Data annotation
Voeg de attribute tag [Key]
toe voor de eigenschap/property dat u als sleutel wilt mappen.
In onderstaand voorbeeld wordt de eigenschap/property LicensePlate als sleutel gemapt.
public class Car { [Key] public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } }
Fluent API
Voeg de functie HasKey()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property LoadedFromDatabase als sleutel gemapt.
class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>().HasKey(c => c.LicensePlate); } }
Eventueel kunt u ook nog een eigen naam toekennen aan de sleutel.
Samengestelde sleutel
Een samengestelde sleutel bestaat uit meerdere velden. Een samengestelde sleutel kan u enkel aanmaken via de Fluent Api in Entity framework core.
U kunt een samengestelde sleutel maken via HasKey(c => new { c.State, c.LicensePlate })
.
class MyContext : DbContext { public DbSet<Car> Cars { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Car>().HasKey(c => new { c.State, c.LicensePlate }); } } public class Car { public string State { get; set; } public string LicensePlate { get; set; } public string Make { get; set; } public string Model { get; set; } }
Verplichte invoer
U kunt bepalen dat een bepaalde eigenschap/property verplicht een waarde moet krijgen, de waarde is Required.
Conventie
Alle eigenschappen/properties met een datatype dat geen Null kan bevatten (zoals int, decimal, boolean,…) moet een waarde krijgen en is dus standaard Required.
Weet dat u in C# datatypes nullable kunt maken door er een vraagteken achter te plaatsen, dus het datatype int? kan wel een null bevatten, het datatype int niet.
Data annotation
Voeg de attribute tag [Required]
toe voor de eigenschap/property dat u als Required wilt mappen.
In onderstaand voorbeeld wordt de eigenschap/property Url als verplicht in te vullen, Required, gemapt.
public class Blog { public int BlogId { get; set; } [Required] public string Url { get; set; } }
Fluent API
Voeg de functie IsRequired()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property Url als verplicht in te vullen, Required, gemapt.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); } }
Maximale lengte
U kunt de maximale lengte van een in te voeren tekst (string) beperken.
Weet dat Entity Framework geen controle doet op de maximale lengte, dit gebeurt door de database provider (bv. SQLite).
Conventie
De maximale lengte wordt bepaald door de gebruikte database provider.
Data annotation
Voeg de attribute tag [MaxLength(lenght)]
toe voor de eigenschap/property dat u wilt mappen met een maximale lengte.
In onderstaand voorbeeld wordt de eigenschap/property Url gemapt met een maximale lengte van 500 tekens.
public class Blog { public int BlogId { get; set; } [MaxLength(500)] public string Url { get; set; } }
Fluent API
Voeg de functie HasMaxLength()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property Url gemapt met een maximale lengte van 500 tekens.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500); } }
Standaardwaarden
U kunt bij het mappen al meteen standaardwaarden toekennen aan bepaalde eigenschappen/properties.
Standaardwaarden kunnen enkel worden toegekend in de Fluent Api.
Fluent API
Voeg de functie HasDefaultValue()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property Rating gemapt naar een standaardwaarde van 3.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Rating) .HasDefaultValue(3); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public int Rating { get; set; } }
Voeg de functie HasDefaultValueSql()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
In onderstaand voorbeeld wordt de eigenschap/property Created gemapt naar de huidige datum.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(b => b.Created) .HasDefaultValueSql("getdate()"); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public DateTime Created { get; set; } }
De databank opvullen
De databank opvullen met startgegevens, eventueel enkel om uit te testen, heet Data Seeding.
Data Seeding kan enkel in de Fluent Api.
Fluent API
Voeg de functie HasData()
toe aan de methode protected override void OnModelCreating(ModelBuilder modelBuilder)
.
class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>().HasData(new Blog {BlogId = 1, Url = "https://www.pcvogroeipunt.be"}); modelBuilder.Entity<Blog>().HasData(new Blog {BlogId = 2, Url = "https://ictopleidingen.azurewebsites.net"}); } }