Deze handleiding maakt deel uit van het programmeertraject:
Inhoud
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 bent vertrouwd met Entity Framework Modellen.
Inleiding
Dit is een praktische toepassing van Entity Framework Modellen in Xamarin.
Dit is een eerste deel, het project wordt uitgewerkt over meerdere posts.
Xamarin Project
We gaan dit nu toepassen binnen een Xamarin-project. We beginnen met onderstaand overzicht van onze Blogs aan te maken.
- Open het Template-project dat we aangemaakt hebben in de handleiding SQLite met Xamarin.
Er zijn reeds de nodige folders aangemaakt waaronder een map Models. Deze folder gaan we gebruiken om onze modellen in te plaatsen.
Model Blog
We maken het Blog-model aan.
- Klik rechts op de folder Models.
- Kies Add – New Item… – Class en geef de Class de naam Blog.cs (let op het enkelvoud!).
- Maak onderstaande Class aan.
namespace TestEF.Models { public class Blog { public int BlogId { get; set; } public string Name { get; set; } public string Url { get; set; } } }
- BlogId zal automatisch de sleutel worden.
- Name bevat de naam van de blog, verplicht in te vullen en maximaal 100 tekens.
- URL bevat de URL van de blog, verplicht in te vullen en maximaal 500 tekens.
We gaan dit zo dadelijk mappen via Fluent Api (Annotation had ook gekund).
Het model Blog wordt gemapt door het op te nemen in een van DbContext
overgeërfde klasse via public DbSet Blogs { get; set; }
. We hebben dit reeds voorzien in onze “template” in het bestand DatabaseContext dat u vindt in de folder Services.
- Open het bestand DatabaseContext.cs in de folder Services.
using Microsoft.EntityFrameworkCore; namespace TestEF.Services { public class DatabaseContext : DbContext { string _dbPath; public DatabaseContext(string dbPath) { _dbPath = dbPath; Database.EnsureDeleted(); Database.EnsureCreated(); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite($"Filename={_dbPath}"); } } }
Voeg de onderstaande code toe:
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}"); } } }
Het model mappen
Ik verkies om het model te mappen via Fluent Api.
Voeg de methode OnModelCreating(ModelBuilder modelBuilder)
toe.
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) { } } }
We wensen dat:
- BlogId zal automatisch de sleutel worden.
- Name bevat de naam van de blog, verplicht in te vullen en maximaal 100 tekens.
- URL bevat de URL van de blog, verplicht in te vullen en maximaal 500 tekens.
U doet dit als volgt:
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); } } }
Tenslotte gaan we wat basiswaarden toevoegen om te debuggen. We hebben deze code al besproken, we voegen het nu toe.
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 } } }
Merk de preprocessor directives op.
ViewModel BlogListViewModel
De ViewModel maakt de verbinding tussen de View, de XAML-layout die we nog moeten aanmaken en het achterliggende Model.
We wensen een lijst van alle Blogs weer te geven. Het opvragen van deze lijst dient dus te gebeuren in de ViewModel.
Er is reeds een folder aangemaakt met de naam ViewModel. Deze folder bevat reeds het BaseViewModel-bestand.
using System.ComponentModel; using System.Runtime.CompilerServices; namespace TestEF.ViewModels { public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; void OnPropertyChanged([CallerMemberName] string name = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } } }
Al onze ViewModels gaan we overerven van dit basismodel.
- Klik rechts op de folder ViewModels.
- Kies Add – New Item… – Class en geef de Class de naam BlogListViewModel.cs.
- Maak onderstaande Class aan.
using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using TestEF.Models; namespace TestEF.ViewModels { public class BlogListViewModel : BaseViewModel { public ObservableCollection<Blog> BlogItems { get; set; } public BlogListViewModel() { List<Blog> BlogLijst = App.Databank.Blogs.ToList(); BlogItems = new ObservableCollection<Blog>(BlogLijst); } } }
- Merk de gebruikte
using
op. - De klasse is publiek en erft van BaseViewModel
public class BlogListViewModel : BaseViewModel
. - Er is een eigenschap van het type ObservableCollection aangemaakt met als type Blog. ObservableCollection werkt met notificaties (net zoals het BaseViewModel) voor het toevoegen, verwijderen en updaten en is dus beter geschikt dan een gewone List om met databanken te werken.
- De lijst met alle Blogs uit de databank wordt opgevraagd met een eenvoudig LINQ-commando
App.Databank.Blogs.ToList()
. - 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. - De bekomen lijst wordt tenslotte meegegeven aan de ObservableCollection.
View BlogListViewPage
Nu de ViewModel klaar is kan de View gemaakt en opgevuld worden (u mag ook eerst de View maken en dan pas het ViewModel).
De XAML is hoofdzakelijk een ListView.
- Klik rechts op de folder Views.
- Kies Add – New Item… – Xamarin.Forms – Content Page en geef het een passende naam BlogListViewPage.xaml.
- Maak onderstaande XAML aan.
<?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.BlogListViewPage" Title="Mijn Blogs" > <ListView x:Name="MyListView" HasUnevenRows="True" ItemTapped="Handle_ItemTapped" CachingStrategy="RecycleElement" 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="Naam" FontAttributes="Bold" /> <Label Grid.Column="2" Text="Website" 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 BlogId}" FontSize="Small" /> <Label Grid.Column="1" Text="{Binding Name}" FontSize="Small" /> <Label Grid.Column="2" Text="{Binding Url}" FontSize="Small" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage>
Deze XAML mag normaal geen geheimen bevatten. Misschien toch een paar kleine aandachtspuntjes:
- Vergeet de titel
Title="Mijn Blogs
niet toe te voegen. - De ListView heeft een event
ItemTapped="Handle_ItemTapped"
. Vergeet deze niet aan de Code-behind toe te voegen voor u uittest (zie hieronder)! - Er is een ListView.Header toegevoegd. Deze bevat een Grid met 3 kolommen.
- Er is een ListView.ItemTemplate die de items zal weergeven. Deze bevat eveneens een Grid met 3 kolommen.
- Merk de verschillende Bindings op binnen deze Grid van de Listview.ItemTemplate!
De verbinding met het ViewModel gebeurt via Code-behind.
BlogListViewModel viewModel = new BlogListViewModel(); MyListView.ItemsSource = viewModel.BlogItems;
Er wordt een variabele viewModel van het type BlogListViewModel: BlogListViewModel viewModel = new BlogListViewModel();
.
De ObservableCollection BlogItems uit het viewModel wordt toegekend aan de Itemsource van de ListView MyListView: MyListView.ItemsSource = viewModel.BlogItems;
.
- Pas de Code-behind aan. De volledige Code-behind is:
using System.Collections.ObjectModel; using TestEF.ViewModels; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace TestEF.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class BlogListViewPage : ContentPage { public BlogListViewPage() { InitializeComponent(); BlogListViewModel viewModel = new BlogListViewModel(); MyListView.ItemsSource = viewModel.BlogItems; } async void Handle_ItemTapped(object sender, ItemTappedEventArgs e) { } } }
- Vergeet ook niet de app op te starten met dit formulier door het toe te voegen aan MainPage App.xaml.cs. Het formulier wordt toegevoegd als een NavigationPage
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 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 } } }
Het resultaat ziet u hieronder.
We wensen nu een Blog te kunnen aanklikken (tappen) en dan de details van deze blog te zien (op ene heel eenvoudige manier).
Laat ons misschien eerst de View aanmaken.
View BlogViewPage
- Klik rechts op de folder Views.
- Kies Add – New Item… – Xamarin.Forms – Content Page en geef het een passende naam BlogViewPage.xaml.
- Maak onderstaande eenvoudige XAML aan.
<?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>>
- Merk de Binding van de titel op
Title="{Binding Name}"
. - Voor de BlogId kunt u misschien beter een Label gebruiken, de BlogId mag immers niet gewijzigd worden (het is een automatisch genummerde sleutel). Later, als we de CRUD bespreken heb ik deze wijziging doorgevoerd.
De XAML van BlogViewPage is gemaakt.
We navigeren naar deze BlogViewPage vanuit BlogListViewPage door het aanklikken/tappen van een Blog uit de lijst. Deze code gaan we nu toevoegen.
- Ga terug naar de Code-behind van BlogListViewPage.xaml.cs.
De nodige Event (handler) was reeds aangemaakt. We passen hem nu aan met de Naviagatie-code.
async void Handle_ItemTapped(object sender, ItemTappedEventArgs e) { if (e.Item != null) { Blog blog = (Blog)e.Item; await Navigation.PushAsync(new BlogViewPage(blog)); } //Deselect Item ((ListView)sender).SelectedItem = null; }
- Er wordt eerst gekeken of er wel degelijk een item is geselecteerd
if (e.Item != null)
. - Er wordt een nieuwe variabele van het type blog aangemaakt op basis van het geselecteerde item
Blog blog = (Blog)e.Item;
. - Er wordt genavigeerd naar BlogViewPage waaraan we de geselecteerde Blog meegeven
await Navigation.PushAsync(new BlogViewPage(blog));
. - Uiteindelijk wordt de selectie ongedaan gemaakt
((ListView)sender).SelectedItem = null;
.
De volledige code is:
using System.Collections.ObjectModel; using TestEF.Models; using TestEF.ViewModels; using Xamarin.Forms; using Xamarin.Forms.Xaml; namespace TestEF.Views { [XamlCompilation(XamlCompilationOptions.Compile)] public partial class BlogListViewPage : ContentPage { public BlogListViewPage() { InitializeComponent(); BlogListViewModel viewModel = new BlogListViewModel(); MyListView.ItemsSource = viewModel.BlogItems; } async void Handle_ItemTapped(object sender, ItemTappedEventArgs e) { if (e.Item != null) { Blog blog = (Blog)e.Item; await Navigation.PushAsync(new BlogViewPage(blog)); } //Deselect Item ((ListView)sender).SelectedItem = null; } } }
Als we dit nu zouden uittesten dan zal u merken dat de View BlogViewPage geopend wordt maar nog geen gegevens bevat.
ViewModel BlogViewModel
Laat ons eerst opnieuw een ViewModel aanmaken. In dit ViewModel wordt de gewenste Blog geselecteerd.
- Klik rechts op de folder ViewModels.
- Kies Add – New Item… – Class en geef de Class de naam BlogViewModel.cs.
- Maak onderstaande Class aan.
using TestEF.Models; namespace TestEF.ViewModels { class BlogViewModel : BaseViewModel { public Blog BlogItem { get; set; } public BlogViewModel(Blog item = null) { BlogItem = item; } } }
Veel doet dit ViewModel niet, het ontvangt een item van het type Blog en geeft het terug.
Ik hoor u al denken, waarom al die moeite voor zo weinig? Klopt, maar we gaan dit ViewModel later nog hergebruiken om relaties weer te geven en CRUD (Create, Read, Update en Delete).
Tenslotte moeten we nog deze ViewModel verbinden met de View, dit gebeurt in de Code-behind van de View BlogViewPage
- Open BlogViewPage.xaml.cs en pas de code als volgt 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; } } }
- Vergeet niet de nodige
using
s toe te voegen. - Merk de parameter
Blog BlogDetail
op.
De View BlogViewPage zal nu de geselecteerde Blog weergeven.
Gebruikt u geen MVVM, en dus geen Viewmodel dan kan de code ingekort worden tot:
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(); BindingContext = BlogDetail; } } }