Mobiele apps programmeren met Xamarin – Entity Framework – Relaties

print
Deze handleiding maakt deel uit van het programmeertraject:


Inhoud


Wat vooraf ging


Inleiding

De Relatie beschrijft hoe twee entiteiten zich verhouden in een:

  • één op één relatie
  • één op veel relatie
  • veel op veel relatie

We hebben reeds kennisgemaakt met relaties tijdens de introductie. We vertrokken vanuit 2 entiteiten, zonder relatie.

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; }
}

Er is echter een één op veel relaties tussen Blog en Post. Op een Blog staan één of meer Posten en een Post staat maar op één Blog.

Een relatie kan op meerdere manieren worden gelegd maar de meest eenvoudige is de volgende:

  • voeg aan de entiteit Blog een lijst van Posten toe public List Posts { get; set; }, de zogenaamde navigatie property. Op deze manier geef je aan dat één Blog veel Posten kan bevatten.
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; }
}

Het meest gebruikte patroon is echter het patroon waarin aan de veel-kant ook een verwijzing wordt opgenomen naar de één-kant, de zogenaamde inverse navigation property samen met de Foreign key. Het onderstaande voorbeeld doet dus identiek hetzelfde van het bovenstaande, namelijk een één op veel relatie leggen tussen de entiteiten Blog en Post

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 int BlogId { get; set; }
    public Blog Blog { get; set; }
}

We gaan hier nu wat dieper op deze verschillende technieken in.


Begrippen

Laat ons eerst kennis nemen van een aantal begrippen:

  • Principal entity: De entiteit die de sleutel (primary key) bevat, ook de parent van de relatie genoemd.
  • Principal key: De eigenschap(pen) die op unieke wijze de entiteit identificeert. De sleutel (primary key) van de entiteit.
  • Dependent entity: De entiteit die de foreign key property(s) bevat, ook de child van de relatie genoemd.
  • Foreign key: De eigenschap(pen) in de afhankelijke entiteit (Dependent entity) die wordt gebruikt voor het opslaan van de waarden van de principal key waarmee de entiteit is verbonden.
  • Navigation property: Een eigenschap/property gedefinieerd in de principal en/of dependent entity  dat de refenentie bevat naar de gerelateerde entiteit.
    • Collection navigation property: Een navigation property dat de referentie bevat naar meerdere gerelateerde entiteiten.
    • Reference navigation property: Een navigation property dat de referentie bevat naar een enkele gerelateerde entiteit.
    • Inverse navigation property: De navigation property aan de andere kant van de relatie.

We duiden deze begrippen op ons voorbeeld:

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 int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • Blog is de principal entity
  • Blog.BlogId is de principal key
  • Post is de dependent entity
  • Post.BlogId is de foreign key
  • Post.Blog is een reference navigation property
  • Blog.Posts is een collection navigation property
  • Post.Blog is de inverse navigation property van Blog.Posts (en vice versa)

Eén op veel Relatie

Conventies

Bij conventie wordt een (één op veel) relatie aangemaakt wanneer een Navigation property aanwezig is in een model.

Dit kan op één van onderstaande drie manieren verwezenlijkt worden.

Fully Defined Relationships

Dit is het meest gebruikte patroon.

  • Er is een Navigation property aan beide zijden van de relatie (zowel aan de parent als aan de child zijde).
  • Er is een Foreign key in de Dependent entity (child).

We hebben deze conventie reeds toegepast in bovenstaand voorbeeld. Ik herneem het hier nog eens.

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 int BlogId { get; set; }
    public Blog Blog { get; set; }
}

No Foreign Key Property

Hoewel het aan te raden is om een Foreign key zelf te bepalen mag deze worden weggelaten. Indien u de Foreign key weglaat wordt de Foreign key automatisch aangemaakt (een zogenaamde Shadow property).

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; }
}

Single Navigation Property

Bij conventie volstaat zelfs één Navigatie property. Hoewel dit de meest eenvoudige patroon is, is dit ook het meest onduidelijke patroon. Kijkt u immers enkel naar de entiteit Post dan kunt u niet afleiden dat deze in een relatie ligt met de entiteit Blog. Dit patroon is dan ook niet te verkiezen.

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; }
}

Bijkomende voorbeelden vindt u hier.

Data annotation

Omdat het werken bij conventie zo eenvoudig is ga ik niet verder ingaan op data annotation. Het bestaat echter wel en wie nieuwsgierig is kan hier verder lezen.

Fluent API

Wat geldt voor data annotation is eigenlijk ook van toepassing op Fluent API. De conventie is zo eenvoudig dat u de Fluent API niet nodig hebt. Toch wil ik een standaardvoorbeeld geven, wie meer wilt weten kan hier verder lezen.

using Microsoft.EntityFrameworkCore;
public class BlogContext : DbContext
{
    //entiteiten
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts);
    }
}

Opmerking: is de relatie reeds gelegd op het niveau van het model dan dient u deze NIET opnieuw te leggen in de Fluent API. Omgekeerd geldt ook, legt u de relatie in de Fluent API, dan moet u deze niet leggen in de modellen.

Aan u de keuze, ofwel legt u de relatie via de modellen, ofwel via de Fluent API, beiden mag maar is eigenlijk dubbel werk en dus niet te verkiezen.

Bijkomende voorbeelden vindt u hier.


Eén op één Relatie

Conventies

Een één op één relatie heeft een reference navigation property aan beide kanten. Eén van beide kanten bevat eveneens een Foreign key.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public BlogImage BlogImage { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Merkt u het verschil op met de veel op veel relatie?

Bijkomende voorbeelden vindt u hier.

Fluent API

In de Fluent Api wordt dit:

using Microsoft.EntityFrameworkCore;
public class BlogContext : DbContext
{
    //entiteiten
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<BlogImage> BlogImages { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .HasOne(b => b.BlogImage)
            .WithOne(i => i.Blog)
            .HasForeignKey<BlogImage>(b => b.BlogId);
    }
}

Bijkomende voorbeelden vindt u hier.


Veel op veel Relatie

Conventies

Een veel op veel relatie kan niet rechtstreeks gelegd worden, er dient een zogenaamde tussentabel aangemaakt te worden en twee één op veel relaties gelegd.

Onderstaand voorbeeld toont een veel op veel relatie tussen Post en Tag. Een post kan veel tags hebben en een tag kan op veel posten voorkomen.

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class Tag
{
    public string TagId { get; set; }

    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public int PostId { get; set; }
    public Post Post { get; set; }

    public string TagId { get; set; }
    public Tag Tag { get; set; }
}

Fluent API

In de Fluent Api wordt dit:

using Microsoft.EntityFrameworkCore;
public class BlogContext : DbContext
{
    //entiteiten
    public DbSet<Post> Posts { get; set; }
    public DbSet<Tag> Tags { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<PostTag>()
            .HasKey(t => new { t.PostId, t.TagId });

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Post)
            .WithMany(p => p.PostTags)
            .HasForeignKey(pt => pt.PostId);

        modelBuilder.Entity<PostTag>()
            .HasOne(pt => pt.Tag)
            .WithMany(t => t.PostTags)
            .HasForeignKey(pt => pt.TagId);
    }
}

Bijkomende voorbeelden vindt u hier.


Meerdere relaties

Uiteraard kan een entiteit in relatie staan met meerdere andere entiteiten. Uit bovenstaande voorbeelden zou u kunnen afleiden dat de entiteit Blog in relatie staat met de entiteit Post en de entiteit BlogImage.

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
    public BlogImage BlogImage { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

public class BlogImage
{
    public int BlogImageId { get; set; }
    public byte[] Image { get; set; }
    public string Caption { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { 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

We hadden reeds onderstaande code toegevoegd.

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"});   
    }
}

We wensen nu ook data toe te voegen aan de entiteit Post die gerelateerd is, als child, met de entiteit Blog.

Belangrijk is dat u de Foreign key opneemt, die verwijst naar de Primary key van de entiteit Blog waarmee u de entiteit Post wilt relateren.

class BlogContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { 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"});   
        modelBuilder.Entity<Post>().HasData(
           new { BlogId = 1, PostId = 1, Title = "Post 1", Content = "Test 1" },
           new { BlogId = 1, PostId = 2, Title = "Post 2", Content = "Test 2" },
           new { BlogId = 2, PostId = 3, Title = "Post 3", Content = "Test 3" },
           new { BlogId = 2, PostId = 4, Title = "Post 4", Content = "Test 4" });
    }
}

We zijn nu klaar om dit in de praktijk, in Xamarin, uit te werken.

Geef een reactie

Deze website gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.

  • Abonneer je op deze website d.m.v. e-mail

    Voer je e-mailadres in om je in te schrijven op deze website en e-mailmeldingen te ontvangen van nieuwe berichten.