Deze handleiding maakt deel uit van het programmeertraject:
Inhoud
- Wat vooraf ging
- Inleiding
- Model Post
- Relaties leggen tussen Blog en Post
- Gegevens toevoegen aan Post
- Lijst met gerelateerde posts weergeven per blog
- Lijst met gegevens uit meerdere modellen
Wat vooraf ging
- U bent vertrouwd met Klassen.
- U bent vertrouwd met de MVVM architectuur.
- U weet hoe u een Xamarin project met SQLite kunt aanmaken.
- U kunt een Entity Framwork Model aanmaken in Xamarin.
- U kunt relaties leggen in Entity Framework.
Inleiding
In deze handleiding brengen we het leggen van relaties in de praktijk.
Deze handleiding bouwt verder op de praktijk handleiding over Entity Framework Modellen. Deze handleiding eindigde bij onderstaand formulier.
Het eindresultaat van deze handleiding ziet u hieronder.
Weet dat App.Databank
een publieke variabele is die u vindt in App.xaml.cs: public static DatabaseContext Databank;
en die de verbinding maakt met de DatabaseContext.
using Xamarin.Forms; using Xamarin.Forms.Xaml; using TestEF.Services; using TestEF.Views; [assembly: XamlCompilation(XamlCompilationOptions.Compile)] namespace TestEF { public partial class App : Application { public static DatabaseContext Databank; //public static string LocatieDB; public App(string dbPath) { InitializeComponent(); Databank = new DatabaseContext(dbPath); MainPage = new NavigationPage(new BlogListViewPage()); } protected override void OnStart() { // Handle when your app starts } protected override void OnSleep() { // Handle when your app sleeps } protected override void OnResume() { // Handle when your app resumes } } }
Model Post
Eest voegen we een nieuw model Post toe.
- Klik rechts op de folder Models.
- Kies Add – New Item… – Class en geef de Class de naam Post.cs (let op het enkelvoud!).
- Maak onderstaande Class aan.
namespace TestEF.Models { public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } } }
- PostId zal automatisch de sleutel worden.
- Title bevat de titel van de post, verplicht in te vullen en maximaal 100 tekens.
- Content bevat de inhoud van de post.
We gaan dit zo dadelijk mappen via Fluent Api (Annotation had ook gekund).
- Open het bestand DatabaseContext.cs in de folder Services.
using Microsoft.EntityFrameworkCore; using TestEF.Models; namespace TestEF.Services { public class DatabaseContext : DbContext { string _dbPath; public DatabaseContext(string dbPath) { _dbPath = dbPath; Database.EnsureDeleted(); Database.EnsureCreated(); } public DbSet<Blog> Blogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite($"Filename={_dbPath}"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //Name modelBuilder.Entity<Blog>() .Property(b => b.Name) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Name) .HasMaxLength(100); //URL modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500); #if DEBUG modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Name="PCVO Groeipunt", Url = "https://www.pcvogroeipunt.be" }); modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 2, Name="ICT-opleidingen", Url = "https://ictopleidingen.azurewebsites.net" }); #endif } } }
- Pas dit nu als volgt aan:
using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; using TestEF.Models; namespace TestEF.Services { public class DatabaseContext : DbContext { string _dbPath; public DatabaseContext(string dbPath) { _dbPath = dbPath; Database.EnsureDeleted(); Database.EnsureCreated(); } public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite($"Filename={_dbPath}"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //Name modelBuilder.Entity<Blog>() .Property(b => b.Name) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Name) .HasMaxLength(100); //URL modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500); //Title Post modelBuilder.Entity<Post>() .Property(p => p.Title) .IsRequired(); modelBuilder.Entity<Post>() .Property(p => p.Title) .HasMaxLength(100); #if DEBUG modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Name="PCVO Groeipunt", Url = "https://www.pcvogroeipunt.be" }); modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 2, Name="ICT-opleidingen", Url = "https://ictopleidingen.azurewebsites.net" }); #endif } } }
Relaties leggen tussen Blog en Post
We leggen de relaties zoals we die gezien hebben in de theoretische handleiding over Entity Framework Modellen.
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; } }
Het Blog-model wordt:
using System.Collections.Generic; namespace TestEF.Models { public class Blog { public int BlogId { get; set; } public string Name { get; set; } public string Url { get; set; } public List<Post> Posts { get; set; } } }
Vergeet de using System.Collections.Generic;
niet die we nodig hebben voor de List.
Het Post-model wordt:
namespace TestEF.Models { 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; } } }
Gegevens toevoegen aan Post
Vervolgens gaan we gegevens toevoegen aan Post, dit gebeurt in het bestand DatabaseContext.cs.
- Open het bestand DatabaseContext.cs in de folder Services.
- Pas de code als volgt aan.
using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; using TestEF.Models; namespace TestEF.Services { public class DatabaseContext : DbContext { string _dbPath; public DatabaseContext(string dbPath) { _dbPath = dbPath; Database.EnsureDeleted(); Database.EnsureCreated(); } public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite($"Filename={_dbPath}"); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //Name modelBuilder.Entity<Blog>() .Property(b => b.Name) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Name) .HasMaxLength(100); //URL modelBuilder.Entity<Blog>() .Property(b => b.Url) .IsRequired(); modelBuilder.Entity<Blog>() .Property(b => b.Url) .HasMaxLength(500); //Title Post modelBuilder.Entity<Post>() .Property(p => p.Title) .IsRequired(); modelBuilder.Entity<Post>() .Property(p => p.Title) .HasMaxLength(100); #if DEBUG modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Name="PCVO Groeipunt", Url = "https://www.pcvogroeipunt.be" }); modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 2, Name="ICT-opleidingen", 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" }); #endif } } }
Vergeet niet de BlogId mee te geven om de relatie tot stand te brengen!
Lijst met gerelateerde posts weergeven per blog
We hebben reeds een formulier BlogViewPage dat de detail gegevens van een Blog bevat.
De View BlogViewPage.xaml van dit formulier:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TestEF.Views.BlogViewPage" Title="{Binding Name}"> <ContentPage.Content> <StackLayout> <Entry Text="{Binding BlogId}" /> <Entry Text="{Binding Name}" /> <Entry Text="{Binding Url}" /> </StackLayout> </ContentPage.Content> </ContentPage>>
Het ViewModel BlogViewModel.cs:
using TestEF.Models; namespace TestEF.ViewModels { class BlogViewModel : BaseViewModel { public Blog BlogItem { get; set; } public BlogViewModel(Blog item = null) { BlogItem = item; } } }
De Code-behind BlogViewPage.xaml.cs:
using TestEF.Models; using TestEF.ViewModels; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace TestEF.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class BlogViewPage : ContentPage { public BlogViewPage(Blog BlogDetail) { InitializeComponent(); BlogViewModel viewModel = new BlogViewModel(BlogDetail); BindingContext = viewModel.BlogItem; } } }
We gaan eerst een eenvoudige lijst met gerelateerd Posts weergeven.
View aanpassen
- Aan de View BlogViewPage.xaml voegen we een ListView MyListView toe.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TestEF.Views.BlogViewPage" Title="{Binding Name}"> <ContentPage.Content> <StackLayout> <Entry Text="{Binding BlogId}" /> <Entry Text="{Binding Name}" /> <Entry Text="{Binding Url}" /> <ListView x:Name="MyListView" HasUnevenRows="True" SeparatorVisibility="Default" Margin="5"> <ListView.Header> <Grid BackgroundColor="LightGray"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="2*" /> <ColumnDefinition Width="3*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="ID" FontAttributes="Bold" /> <Label Grid.Column="1" Text="Titel" FontAttributes="Bold" /> <Label Grid.Column="2" Text="Inhoud" FontAttributes="Bold" /> </Grid> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid BackgroundColor="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="2*" /> <ColumnDefinition Width="3*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="{Binding PostId}" FontSize="Small" /> <Label Grid.Column="1" Text="{Binding Title}" FontSize="Small" /> <Label Grid.Column="2" Text="{Binding Content}" FontSize="Small" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage.Content> </ContentPage>
ViewModel aanpassen
U moet de gerelateerde Posts selecteren.
- Maak een ObservableCollection aan van het type Post.
public ObservableCollection<Post> PostLijst;
Een ObservableCollection is als een lijst van een specifiek type (hier Post maar “observeert” constant zodat wijzigingen meteen worden doorgegeven als notificatie.
- Selecteer de gerelateerde Posts en ken deze toe aan de ObservableCollection. Dit kan via
App.Databank.Posts.Where(p => p.BlogId == BlogItem.BlogId)
. De p is willekeurig gekozen.
var Query = App.Databank.Posts.Where(p => p.BlogId == BlogItem.BlogId); PostLijst = new ObservableCollection<Post>(Query);
Het ViewModel BlogViewModel.cs is nu:
using System.Collections.ObjectModel; using System.Linq; using TestEF.Models; namespace TestEF.ViewModels { class BlogViewModel : BaseViewModel { public Blog BlogItem { get; set; } public ObservableCollection<Post> PostLijst; public BlogViewModel(Blog item = null) { BlogItem = item; var Query = App.Databank.Posts.Where(p => p.BlogId == BlogItem.BlogId); PostLijst = new ObservableCollection<Post>(Query); } } }
Vergeet de using
s niet!
Code-behind aanpassen
Tenslotte moet de opgevulde ObservableCollection uit het ViewModel gekoppeld worden aan de View. Dit gebeurt in de code-behind van de View.
- Pas de Code-behind BlogViewPage.xaml.cs aan:
using TestEF.Models; using TestEF.ViewModels; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace TestEF.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class BlogViewPage : ContentPage { public BlogViewPage(Blog BlogDetail) { InitializeComponent(); BlogViewModel viewModel = new BlogViewModel(BlogDetail); BindingContext = viewModel.BlogItem; MyListView.ItemsSource = viewModel.PostLijst; } } }
Lijst met gegevens uit meerdere modellen
Tenslotte bekijken we hoe we gegevens uit meerdere modellen kunnen combineren in één lijst.
View aanpassen
- Aan de View BlogViewPage.xaml voegen we een ListView MyListView2 toe.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="TestEF.Views.BlogViewPage" Title="{Binding Name}"> <ContentPage.Content> <StackLayout> <Entry Text="{Binding BlogId}" /> <Entry Text="{Binding Name}" /> <Entry Text="{Binding Url}" /> <ListView x:Name="MyListView" HasUnevenRows="True" SeparatorVisibility="Default" Margin="5"> <ListView.Header> <Grid BackgroundColor="LightGray"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="2*" /> <ColumnDefinition Width="3*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="ID" FontAttributes="Bold" /> <Label Grid.Column="1" Text="Titel" FontAttributes="Bold" /> <Label Grid.Column="2" Text="Inhoud" FontAttributes="Bold" /> </Grid> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid BackgroundColor="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="2*" /> <ColumnDefinition Width="3*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="{Binding PostId}" FontSize="Small" /> <Label Grid.Column="1" Text="{Binding Title}" FontSize="Small" /> <Label Grid.Column="2" Text="{Binding Content}" FontSize="Small" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> <ListView x:Name="MyListView2" HasUnevenRows="True" SeparatorVisibility="Default" Margin="5"> <ListView.Header> <Grid BackgroundColor="LightGray"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="Blog" FontAttributes="Bold" /> <Label Grid.Column="1" Text="Titel" FontAttributes="Bold" /> <Label Grid.Column="2" Text="Inhoud" FontAttributes="Bold" /> </Grid> </ListView.Header> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid BackgroundColor="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Label Grid.Column="0" Text="{Binding Naam}" FontSize="Small" /> <Label Grid.Column="1" Text="{Binding Titel}" FontSize="Small" /> <Label Grid.Column="2" Text="{Binding Inhoud}" FontSize="Small" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackLayout> </ContentPage.Content> </ContentPage>
Merk op dat we voor de Bindings hier namen gebruiken die niet rechtstreeks uit de modellen komen!
Meteen wordt duidelijk waar deze vandaan komen.
ViewModel aanpassen
U moet specifieke gegevens (kolommen) uit de gerelateerde Posts selecteren.
Hiertoe kunnen we het best een LINQ to Entities query te schrijven.
Deze ziet er als volgt uit:
from b in App.Databank.Blogs join p in App.Databank.Posts on b.BlogId equals p.BlogId where b.BlogId == BlogItem.BlogId select new { Naam = b.Name, Titel = p.Title, Inhoud = p.Content };
Dit heeft veel weg van een SQL-commando en zal, voor zij die SQL kennen, meteen duidelijk zijn. Let op, de select staat hier achteraan.
Merk ook de benamingen op die gebruikt werden in de View.
Wie meer wilt weten over LINQ to Entities verwijs ik naar de officiële handleiding.
Let op, het resultaat van deze query kan niet van het type ObservableCollection zijn omdat het resultaat geen gekend type/model is maar een combinatie (anoniem-type).
We kennen het resultaat het best toe aan het type IQueryable al neemt u voor de query zelf het best het type var (omwille van het anonieme-type).
Het ViewModel BlogViewModel.cs is nu:
using System.Collections.ObjectModel; using System.Linq; using TestEF.Models; namespace TestEF.ViewModels { class BlogViewModel : BaseViewModel { public Blog BlogItem { get; set; } public ObservableCollection<Post> PostLijst; public IQueryable BlogPostLijst; public BlogViewModel(Blog item = null) { BlogItem = item; var Query = App.Databank.Posts.Where(p => p.BlogId == BlogItem.BlogId); PostLijst = new ObservableCollection<Post>(Query); var Query2 = from b in App.Databank.Blogs join p in App.Databank.Posts on b.BlogId equals p.BlogId where b.BlogId == BlogItem.BlogId select new { Naam = b.Name, Titel = p.Title, Inhoud = p.Content }; BlogPostLijst = Query2; } } }
Een alternatieve uitwerking, meer volgens het boekje van Entity Framework is het onderstaande code:
var Query2 = App.Databank.Blogs.SelectMany( b => App.Databank.Posts.Where( p => (b.BlogId == p.BlogId) && (b.BlogId == BlogItem.BlogId)).Select( p => new { Naam = b.Name, Titel = p.Title, Inhoud = p.Content }));
Ik weet niet of dit eenvoudiger is.
Sorteren
Wilt u de gegevens sorteren, dan kan dit door een toevoeging binnen LINQ via orderby.
from b in App.Databank.Blogs join p in App.Databank.Posts on b.BlogId equals p.BlogId where b.BlogId == BlogItem.BlogId orderby p.Title descending, p.Content select new { Naam = b.Name, Titel = p.Title, Inhoud = p.Content };
of nadien via de code Query2.OrderByDescending(s => s.Titel).ThenBy(s => s.Inhoud)
.
Query2 = Query2.OrderByDescending(s => s.Titel).ThenBy(s => s.Inhoud);
Code-behind aanpassen
Tenslotte moet de opgevulde IQueryable uit het ViewModel gekoppeld worden aan de View.
- Pas de Code-behind BlogViewPage.xaml.cs aan:
using TestEF.Models; using TestEF.ViewModels; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace TestEF.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class BlogViewPage : ContentPage { public BlogViewPage(Blog BlogDetail) { InitializeComponent(); BlogViewModel viewModel = new BlogViewModel(BlogDetail); BindingContext = viewModel.BlogItem; MyListView.ItemsSource = viewModel.PostLijst; MyListView2.ItemsSource = viewModel.BlogPostLijst; } } }