Juillet 2016

Volume 31, numéro 7

Cet article a fait l'objet d'une traduction automatique.

Xamarin - Utilisation de bases de données locales dans Xamarin Forms grâce à SQLite

Par Alessandro Del Del

Plus souvent non, les applications fonctionnent avec des données. Cela est vrai, non seulement pour les applications de bureau et Web, mais également pour les applications mobiles. Dans de nombreux cas, les applications mobiles échangent des données via des réseaux et tirer parti du stockage en nuage et les services, tels que des notifications push. Toutefois, il existe des situations dans lesquelles les applications mobiles suffit stocker des données localement. Avec des données simples, non structurées, telles que les paramètres utilisateur et les options, les applications peuvent stocker des informations à l’intérieur des fichiers locaux, tels que XML ou texte, ou via des objets spécifiques offertes par les différentes plateformes de développement. Dans le cas des données structurées complexes, les applications doivent différemment pour stocker des informations.

La bonne nouvelle est que vous pouvez facilement inclure des bases de données locales dans votre application mobile à l’aide de SQLite (sqlite.org). SQLite est une open source, le moteur de base de données légère et sans serveur facile créer des bases de données locales et effectuer des opérations sur les données. Les informations sont stockées dans des tables et des opérations de données peuvent être effectuées en écrivant du code c# et LINQ. SQLite convient parfaitement le développement multiplateforme, car il s’agit d’un moteur de base de données portable. En fait, il est préinstallé sur iOS et Android, et il peut être facilement déployé sur Windows, également. Pour cette raison, SQLite est également le complément idéal de créer des applications mobiles interplateforme, orientés données avec Xamarin.Forms nécessitant une base de données locale. Dans cet article, je montrerai comment créer une application mobile qui cible Android, iOS et la plate-forme de Windows universelle (UWP) avec Xamarin.Forms, et qui tire parti de SQLite pour stocker et récupérer des données locales. Je suppose que vous savez déjà comment créer une application Xamarin.Forms avec Visual Studio 2015 ; qu’est XAML ; et comment déboguer une application Xamarin.Forms utilisant les différents émulateurs inclus avec les différents kits de développement de plate-forme. Pour plus d’informations, vous pouvez lire les articles suivants : « Créer une expérience utilisateur interplateforme avec Xamarin.Forms » (msdn.com/magazine/mt595754), « « partage de Code d’interface utilisateur sur les plateformes mobiles avec Xamarin.Forms (msdn.com/magazine/dn904669) et « Créer une application de Golf Mobile multiplateforme à l’aide de c# et Xamarin » (msdn.com/magazine/dn630648). Celui-ci explique comment travailler avec des données sur la plateforme Microsoft Azure. Cet article et l’exemple de code sont basées sur Xamarin.Forms 2.0, que vous puissiez obtenir en installant Xamarin 4.0.3.

L’activation de SQLite sur les applications UWP

Le moteur de base de SQLite est déjà inclus dans iOS et Android, mais pas sur Windows. Par conséquent, vous devez inclure les fichiers binaires de SQLite avec votre package d’application. Au lieu d’inclure manuellement ces fichiers binaires avec chaque projet, vous pouvez tirer parti de l’extension SQLite pour Visual Studio 2015, qui fournit des binaires précompilés pour le moteur de base de données et automatise la tâche, y compris les fichiers requis avec les nouveaux projets. Je décris Ceci démontre comment créer des projets, car l’extension fonctionne à l’IDE de niveau, pas au niveau du projet et fournira les binaires précompilés SQLite chaque fois que vous incluez les bibliothèques SQLite dans vos solutions. Il existe plusieurs extensions de SQLite, chacun d'entre eux ciblant une version spécifique de Windows, ce qui peut être téléchargée à l’aide de l’outil de mises à jour et des Extensions dans Visual Studio 2015, comme indiqué dans Figure 1.

Télécharger le SQLite pour l’Extension de plateforme Windows universelle dans Visual Studio 2015
Figure 1 téléchargement le SQLite pour l’Extension à la plateforme Windows universelle dans Visual Studio 2015

Dans ce cas, téléchargez et installez le SQLite pour l’extension de la plateforme Windows universelle. En procédant ainsi, une application UWP utilise SQLite inclut également des binaires du moteur de base de données précompilé. Si nécessaire, redémarrez Visual Studio 2015 après l’installation de l’extension.

Création d’un exemple de projet

La première chose à faire est de créer un nouveau projet basé sur les formulaires Xamarin. Le modèle de projet que vous utilisez dans Visual Studio 2015 est appelé application vide (Xamarin.Forms Portable) et se trouve dans le dossier interplateforme du nœud Visual c# dans la boîte de dialogue Nouveau projet (voir Figure 2).

Création d’un nouveau projet de formulaires Xamarin dans Visual Studio 2015
Figure 2 Création d’un nouveau Xamarin Forms projet dans Visual Studio 2015

Pour choisir le type de projet Portable au lieu du type partagé parce que vous pouvez souhaiter générer une couche d’accès aux données réutilisables dans une bibliothèque, tandis que le champ d’application d’un projet partagé est uniquement dans la solution à laquelle il appartient. À la fin de cet article, j’expliquerai plus en détail les différences entre les bibliothèques portables et les projets partagés.

Lorsque vous cliquez sur OK, Visual Studio 2015 génère une nouvelle solution qui contient les projets qui ciblent iOS, Android, UWP, Windows Runtime et Windows Phone et un projet de bibliothèque de classes Portable (PCL). Ce dernier est où vous allez écrire la plupart du code qui sera partagé entre les projets spécifiques à la plateforme. 

Installation du Package SQLite NuGet

Une fois que vous avez créé le projet, vous avez besoin d’une façon gérée pour accéder aux bases de données SQLite. Il existe de nombreuses bibliothèques qui permettent l’utilisation de bases de données SQLite dans Microsoft .NET Framework, mais que vous avez besoin est une bibliothèque spéciale de portables qui cible les applications Xamarin. Il s’agit de SQLite-net, et il est une bibliothèque légère pour les applications .NET, Mono et Xamarin et open source. Il est disponible sous forme de package NuGet avec la nom sqlite-net-pcl. Vous pouvez installer le package NuGet au niveau de la solution à partir de la console de gestionnaire de Package NuGet, en tapant install sqlite-net-pcl ou à partir de l’UI NuGet dans Visual Studio 2015, vous activez en double-cliquant sur le nom de la solution dans l’Explorateur de solutions, puis cliquez sur Gérer les Packages NuGet pour la Solution. Figure 3 montre comment rechercher et installer le package sqlite-net-pcl via l’UI NuGet.

Installez les Packages NuGet appropriés
Figure 3 installation les Packages NuGet appropriés

Vous disposez maintenant tout ce dont vous avez besoin et vous êtes prêt à commencer à coder.

Code propre à la plate-forme : Fournir la chaîne de connexion

Comme avec n’importe quel type de base de données, votre code accède à une base de données SQLite via la chaîne de connexion, qui est la première chose que vous devez créer. Comme une base de données SQLite est un fichier qui réside dans un dossier local, la construction de la chaîne de connexion nécessite le chemin d’accès de la base de données. Bien que la plupart du code que vous écrirez est partagé entre différentes plates-formes, Android, iOS et Windows gestion de chemins d’accès étant différente, la création de la chaîne de connexion nécessite code spécifique à la plateforme. Vous appelez alors la chaîne de connexion via l’injection de dépendance.

Dans le projet Portable, ajoutez une nouvelle interface appelée IDatabaseConnection.cs et écrire le code suivant :

public interface IDatabaseConnection
{
  SQLite.SQLiteConnection DbConnection();
}

Cette interface expose une méthode appelée DbConnection, qui est implémentée dans chaque projet spécifique à la plateforme et retourne la chaîne de connexion correcte.

L’étape suivante ajoute une classe à chaque projet spécifique à la plateforme qui implémente l’interface et retourne la chaîne de connexion appropriée, basée sur une base de données exemple que j’appellerai CustomersDb.db3. (Si vous n’êtes pas familiarisé avec SQLite, .db3 est l’extension de fichier qui identifie les bases de données SQLite.) Dans le projet LocalDataAccess.Droid, ajoutez une nouvelle classe appelée DatabaseConnection_Android.cs et écrire le code illustré Figure 4.

Figure 4 génération d’une chaîne de connexion dans le projet Android

using SQLite;
using LocalDataAccess.Droid;
using System.IO;
[assembly: Xamarin.Forms.Dependency(typeof(DatabaseConnection_Android))]
namespace LocalDataAccess.Droid
{
  public class DatabaseConnection_Android : IDatabaseConnection
  {
    public SQLiteConnection DbConnection()
    {
      var dbName = "CustomersDb.db3";
      var path = Path.Combine(System.Environment.
        GetFolderPath(System.Environment.
        SpecialFolder.Personal), dbName);
      return new SQLiteConnection(path);
    }
  }
}

Un attribut appelé Xamarin.Forms.Dependency indique que la classe spécifiée implémente une interface nécessaire. Cet attribut est appliqué au niveau de l’espace de noms avec le mot clé assembly. Sur Android, le fichier de base de données doit être stocké dans le dossier personnel, donc le chemin d’accès de la base de données est constitué du nom de fichier (CustomersDb.db3) et le chemin d’accès du dossier personnel. Le chemin d’accès résultant est affecté en tant que paramètre au constructeur de la classe SQLiteConnection et retourné à l’appelant. Sur iOS, vous utilisez la même API, mais le dossier dans lequel réside la base de données SQLite est Personal\Library.

À présent, ajoutez une nouvelle classe nommée DatabaseConnection_iOS.cs au projet iOS et écrire le code illustré Figure 5.

Figure 5 Création d’une chaîne de connexion dans le projet iOS

using LocalDataAccess.iOS;
using SQLite;
using System;
using System.IO;
[assembly: Xamarin.Forms.Dependency(typeof(DatabaseConnection_iOS))]
namespace LocalDataAccess.iOS
{
  public class DatabaseConnection_iOS
  {
    public SQLiteConnection DbConnection()
    {
      var dbName = "CustomersDb.db3";
      string personalFolder =
        System.Environment.
        GetFolderPath(Environment.SpecialFolder.Personal);
      string libraryFolder =
        Path.Combine(personalFolder, "..", "Library");
      var path = Path.Combine(libraryFolder, dbName);
      return new SQLiteConnection(path);
    }
  }
}

Sur Windows 10, la base de données SQLite réside dans le dossier local de l’application. L’API que vous utilisez pour accéder au dossier local est différent à partir d’autres plateformes, parce que vous travaillez avec les classes de l’espace de noms Windows.Storage au lieu de System.IO. Ajoutez une nouvelle classe nommée DatabaseConnection_UWP.cs au projet Windows universel et écrire le code illustré Figure 6.

Figure 6 générant une chaîne de connexion dans le projet Windows universel

using SQLite;
using Xamarin.Forms;
using LocalDataAccess.UWP;
using Windows.Storage;
using System.IO;
[assembly: Dependency(typeof(DatabaseConnection_UWP))]
namespace LocalDataAccess.UWP
{
  public class DatabaseConnection_UWP : IDatabaseConnection
  {
    public SQLiteConnection DbConnection()
    {
      var dbName = "CustomersDb.db3";
      var path = Path.Combine(ApplicationData.
        Current.LocalFolder.Path, dbName);
      return new SQLiteConnection(path);
    }
  }
}

Cette fois le chemin d’accès du dossier local de l’application est retourné par la propriété Windows.Storage.ApplicationData.Current.LocalFolder.Path, qui est combinée avec le nom de la base de données pour retourner la chaîne de connexion via l’objet SQLiteConnection. Maintenant vous avez écrit le code spécifique à la plateforme qui permet la génération de la chaîne de connexion appropriée en fonction de la plateforme sur laquelle l’application est en cours d’exécution. Dès lors, tout votre code sera partagé. L’étape suivante est l’implémentation d’un modèle de données.

Écriture d’un modèle de données

L’objectif de l’application est pour travailler avec une liste de clients stockées dans une base de données SQLite simplifiée et prennent en charge les opérations sur les données. À ce stade, la première chose dont a besoin est une classe pour représenter un client qui sera mappé à une table dans la base de données. Dans le projet Portable, ajoutez une classe appelée Customer.cs. Cette classe doit implémenter l’interface INotifyPropertyChanged pour avertir les appelants de modifications dans les données qu’elle contient. Il utilise des attributs spéciaux dans l’espace de noms SQLite pour annoter des propriétés avec des règles de validation et autres informations d’une manière qui est très proche de l’espace de noms System.ComponentModel.DataAnnotations les annotations de données. Figure 7 montre l’exemple de classe de client.

Figure 7 implémentation d’un modèle de données

using SQLite;
using System.ComponentModel;
namespace LocalDataAccess
{
  [Table("Customers")
  public class Customer: INotifyPropertyChanged
  {
    private int _id;
    [PrimaryKey, AutoIncrement]
    public int Id
    {
      get
      {
        return _id;
      }
      set
      {
        this._id = value;
        OnPropertyChanged(nameof(Id));
      }
    }
    private string _companyName;
    [NotNull]
    public string CompanyName
    {
      get
      {
        return _companyName;
      }
      set
      {
        this._companyName = value;
        OnPropertyChanged(nameof(CompanyName));
      }
    }
    private string _physicalAddress;
    [MaxLength(50)]
    public string PhysicalAddress
    {
      get
      {
        return _physicalAddress;
      }
      set
      {
        this._physicalAddress=value;
        OnPropertyChanged(nameof(PhysicalAddress));
      }
    }
    private string _country;
    public string Country
    {
      get
      {
        return _country;
      }
      set
      {
        _country = value;
        OnPropertyChanged(nameof(Country));
      }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string propertyName)
    {
      this.PropertyChanged?.Invoke(this,
        new PropertyChangedEventArgs(propertyName));
    }
  }
}

Le point clé de cette classe est annoter des objets avec des attributs de SQLite. L’attribut de la Table permet d’affecter un nom de table, dans ce cas clients. Ce n’est pas obligatoire, mais si vous ne spécifiez pas un, SQLite génère une nouvelle table basée sur le nom de la classe client dans ce cas. Donc, par souci de cohérence, le code génère une nouvelle table avec un nom au pluriel. Les attributs PrimaryKey et AutoIncrement appliqués à la propriété Id en faire la clé primaire dans la table Customers avec un incrément automatique. L’attribut NOT NULL est appliqué à la propriété CompanyName marqué comme requis, ce qui implique que la validation de la banque de données échouera si la valeur de propriété est null. L’attribut MaxLength est appliqué à la propriété PhysicalAddress indique la longueur maximale de la valeur de propriété. Un autre attribut intéressant est la colonne que vous pouvez appliquer à un nom de propriété pour fournir un autre nom de colonne dans la base de données.

L’implémentation de l’accès aux données

Après avoir écrit un simple modèle de données, vous devez une classe qui fournit des méthodes qui effectuent des opérations sur les données. Par souci de clarté et comme il s’agit d’un exemple simple, ne sont pas utiliser une approche Model-View-ViewModel (MVVM) ici. pas tous les lecteurs ont connaissance de ce modèle et, dans tous les cas, il est mieux utilisé dans des projets volumineux. Vous pouvez certainement réécrire l’exemple que vous le souhaitez.

Nous allons commencer par une nouvelle classe appelée CustomersDataAccess.cs dans le projet Portable, qui requiert les éléments suivants à l’aide de directives :

using SQLite;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;
using System.Collections.ObjectModel;

Les choses de cette classe sont un champ privé qui stocke la chaîne de connexion et un objet qui permet de mettre en œuvre des verrous sur les opérations de données, pour éviter les conflits de base de données :

private SQLiteConnection database;
private static object collisionLock = new object();

Plus spécifiquement, les verrous doivent avoir la forme suivante :

// Use locks to avoid database collisions
lock(collisionLock)
{
  // Data operation here ...
}

Avec Xamarin.Forms, XAML vous permet de créer l’interface utilisateur, et vous pouvez tirer parti de la liaison de données pour afficher et entrer des informations. pour cette raison, vous devez exposer les données de façon à qu'utiliser XAML. La meilleure approche expose une propriété de type ObservableCollection < client > comme suit :

public ObservableCollection<Customer> Customers { get; set; }

Le type ObservableCollection a prise en charge intégrée pour la notification de modification ; Par conséquent, il est de la collection la plus appropriée pour la liaison de données dans les plates-formes basées sur XAML.

Il est temps d’implémenter le constructeur de classe, qui est également responsable de l’appel de l’implémentation spécifique à la plateforme de la méthode DbConnection, qui retourne la chaîne de connexion appropriée, comme indiqué dans Figure 8.

Figure 8 implémentation du constructeur de classe

public CustomersDataAccess()
{
  database =
    DependencyService.Get<IDatabaseConnection>().
    DbConnection();
  database.CreateTable<Customer>();
  this.Customers =
    new ObservableCollection<Customer>(database.Table<Customer>());
  // If the table is empty, initialize the collection
  if (!database.Table<Customer>().Any())
  {
    AddNewCustomer();
  }
}

Notez comment le code appelle la méthode DependencyService.Get. Il s’agit d’une méthode générique qui retourne l’implémentation spécifique à la plateforme du type générique fourni, IDatabaseConnection dans ce cas. Avec cette approche basée sur l’injection de dépendance, le code appelle l’implémentation spécifique à la plateforme de la méthode DbConnection. Puis, le runtime Xamarin sait comment résoudre l’appel de méthode basée sur le système d’exploitation sur lequel l’application est en cours d’exécution. L’appel de cette méthode entraîne également la base de données doit être créé si un objet n’est pas trouvé. L’objet SQLiteConnection expose une méthode générique appelée CreateTable < T >, où le type générique est votre classe de modèle, le client dans ce cas. Avec cette simple ligne de code, vous créez une nouvelle table de clients. Si la table existe déjà, il n’est remplacé. Le code initialise également la propriété clients ; Il appelle la méthode générique de la Table < T > à partir de SQLiteConnection, où le type générique est toujours la classe de modèle. La table < T > Retourne un objet de type TableQuery < T >, qui implémente l’interface IEnumerable < T > et peut également être interrogée avec LINQ. Le résultat retourné se compose d’une liste d’objets < T > ; Toutefois, la liaison d’une requête TableQuery objet à l’interface utilisateur directement n’est pas la meilleure façon de présenter des données pour une nouveau ObservableCollection < client > basée sur le résultat renvoyé est générée et affectée à la propriété de clients. Le code appelle également une méthode appelée AddNewCustomer si la table est vide, défini comme suit :

public void AddNewCustomer()
{
  this.Customers.
    Add(new Customer
    {
      CompanyName = "Company name...",
      PhysicalAddress = "Address...",
      Country = "Country..."
    });
}

Cette méthode ajoute un nouveau client à la collection de clients avec les valeurs de propriété par défaut simplement et évite la liaison à une collection vide. 

Interrogation des données

Il est très important d’interrogation des données. SQLite a essentiellement deux méthodes d’implémentation des requêtes. Le premier est l’utilisation de LINQ avec le résultat d’un appel à la méthode de la Table < T >, qui est un objet de type TableQuery < T > et implémente l’interface IEnumerable < T >. La seconde consiste à appeler une méthode appelée SQLiteConnection.Query < T >, qui prend un argument de chaînes de type qui représente une requête écrite dans SQL. Le code dans Figure 9 montre comment filtrer la liste des clients par pays, à l’aide de ces deux approches.

Figure 9 filtrage d’une liste de clients par pays

public IEnumerable<Customer> GetFilteredCustomers(string countryName)
{
  lock(collisionLock)
  {
    var query = from cust in database.Table<Customer>()
                where cust.Country == countryName
                select cust;
    return query.AsEnumerable();
  }
}
public IEnumerable<Customer> GetFilteredCustomers()
{
  lock(collisionLock)
  {
    return database.Query<Customer>(
      "SELECT * FROM Item WHERE Country = 'Italy'").AsEnumerable();
  }
}

La première surcharge de GetFilteredCustomers retourne le résultat d’une requête LINQ qui filtre les données en fonction du nom de pays, fourni comme argument de méthode. La deuxième surcharge appelle la requête pour exécuter directement des requêtes SQL. Cette méthode attend le résultat d’une liste générique, dont le type générique est le même transmis à la requête. Une SQLiteException est levée si la requête échoue. Bien sûr, vous pouvez récupérer une instance de l’objet spécifié à l’aide des méthodes LINQ ou une extension, comme dans le code suivant qui appelle FirstOrDefault sur la liste des clients et récupère l’instance de client spécifié en fonction de son id :

public Customer GetCustomer(int id)
{
  lock(collisionLock)
  {
    return database.Table<Customer>().
      FirstOrDefault(customer => customer.Id == id);
  }
}

L’exécution d’opérations CRUD

Créer, lire, mettre à jour et suppression (CRUD) opérations sont également très importantes. Lecture des données est généralement effectuée à l’aide des méthodes décrites dans la section précédente, maintenant que j’expliquerai comment créer, mettre à jour et supprimer des informations dans une base de données SQLite. L’objet SQLiteConnection expose les méthodes Insert, InsertAll, mise à jour et UpdateAll pour insérer ou mettre à jour les objets dans la base de données. InsertAll et UpdateAll exécutent une opération d’insertion ou mise à jour d’une collection qui implémente IEnumerable < T > est transmis comme argument. L’opération insert ou update est exécutée dans un lot, et les deux méthodes permettent également de l’exécution de l’opération comme une transaction. N’oubliez pas que si InsertAll nécessite qu’il n’existe aucun élément dans la collection dans la base de données, UpdateAll requiert que tous les éléments dans la collection déjà existent dans la base de données. Dans ce cas, j’ai un ObservableCollection < client > qui peut contenir des objets récupérés à partir de la base de données et les nouveaux objets ajoutés via l’interface utilisateur qui n’ont pas encore été enregistré, ou modifications en attente sur des objets existants. Pour cette raison, il n’est pas recommandé d’utiliser InsertAll et UpdateAll. Une bonne approche simplement vérifie si une instance de la classe Customer a un ID. Si c’est le cas, l’instance existe déjà dans la base de données afin qu’il suffit à mettre à jour. Si l’Id est égal à zéro, l’instance n’existe pas dans la base de données ; Par conséquent, il doit être enregistré. Le code dans Figure 10 montre comment insérer ou mettre à jour une seule instance d’un objet de client basée sur le compte précédent.

Figure 10 insérer ou mettre à jour une Instance unique d’un ObjectDepending client de l’Existence d’un ID de classe de client

public int SaveCustomer(Customer customerInstance)
{
  lock(collisionLock)
  {
    if (customerInstance.Id != 0)
    {
      database.Update(customerInstance);
      return customerInstance.Id;
    }
    else
    {
      database.Insert(customerInstance);
      return customerInstance.Id;
    }
  }
}

Le code dans Figure 11, au lieu de cela, montre comment insérer ou mettre à jour toutes les instances du client.

Figure 11 insérer ou mettre à jour toutes les Instances du client

public void SaveAllCustomers()
{
  lock(collisionLock)
  {
    foreach (var customerInstance in this.Customers)
    {
      if (customerInstance.Id != 0)
      {
        database.Update(customerInstance);
      }
      else
      {
        database.Insert(customerInstance);
      }
    }
  }
}

Les méthodes Insert et Update retournent un entier qui représente le nombre de lignes ajoutées ou mises à jour. INSERT offre également une surcharge qui accepte une chaîne contenant des instructions SQL supplémentaires, que vous pouvez souhaiter exécuter sur les lignes insérées. En outre, il est intéressant de mentionner que Insert met automatiquement à jour, par référence, la propriété de votre objet métier qui a été mappé à la clé primaire, Customer.Id dans ce cas. La classe SQLiteConnection expose également les supprimer < T > DeleteAll < T > méthodes pour supprimer définitivement un ou tous les objets d’une table. L’opération de suppression est irréversible, soyez donc conscient de ce que vous faites. Le code suivant implémente une méthode appelée DeleteCustomer qui supprime l’instance de client spécifié de la collection de clients en mémoire et de la base de données :

public int DeleteCustomer(Customer customerInstance)
{
  var id = customerInstance.Id;
  if (id != 0)
  {
    lock(collisionLock)
    {
      database.Delete<Customer>(id);
    }
  }
  this.Customers.Remove(customerInstance);
  return id;
}

Si le client spécifié a un id, il existe dans la base de données, afin que celui-ci est définitivement supprimé, et il est également supprimé de la collection de clients. Supprimer < T > Retourne un entier qui représente le nombre de lignes supprimées. Vous pouvez également supprimer tous les objets d’une table. Vous pouvez appeler certainement DeleteAll < T >, où le type générique est votre objet métier tels que des clients, mais je veux afficher une approche alternative à la place, afin d’avoir connaissance des autres membres. La classe SQLiteConnection expose une méthode appelée DropTable < T >, qui détruit définitivement d’une table dans la base de données. Par exemple, vous pouvez implémenter une suppression de la table comme suit :

public void DeleteAllCustomers()
{
  lock(collisionLock)
  {
    database.DropTable<Customer>();
    database.CreateTable<Customer>();
  }
  this.Customers = null;
  this.Customers = new ObservableCollection<Customer>
    (database.Table<Customer>());
}

Le code supprime la table Customers, puis crée un et enfin nettoie et recrée le regroupement de clients. Figure 12 montre l’intégralité de la classe CustomersDataAccess.cs.

Figure 12 la classe CustomersDataAccess.cs

using SQLite;
using System.Collections.Generic;
using System.Linq;
using Xamarin.Forms;
using System.Collections.ObjectModel;
namespace LocalDataAccess
{
  public class CustomersDataAccess
  {
    private SQLiteConnection database;
    private static object collisionLock = new object();
    public ObservableCollection<Customer> Customers { get; set; }
    public CustomersDataAccess()
    {
      database =
        DependencyService.Get<IDatabaseConnection>().
        DbConnection();
      database.CreateTable<Customer>();
      this.Customers =
        new ObservableCollection<Customer>(database.Table<Customer>());
      // If the table is empty, initialize the collection
      if (!database.Table<Customer>().Any())
      {
        AddNewCustomer();
      }
    }
    public void AddNewCustomer()
    {
      this.Customers.
        Add(new Customer
        {
          CompanyName = "Company name...",
          PhysicalAddress = "Address...",
          Country = "Country..."
        });
    }
    // Use LINQ to query and filter data
    public IEnumerable<Customer> GetFilteredCustomers(string countryName)
    {
      // Use locks to avoid database collitions
      lock(collisionLock)
      {
        var query = from cust in database.Table<Customer>()
                    where cust.Country == countryName
                    select cust;
        return query.AsEnumerable();
      }
    }
    // Use SQL queries against data
    public IEnumerable<Customer> GetFilteredCustomers()
    {
      lock(collisionLock)
      {
        return database.
          Query<Customer>
          ("SELECT * FROM Item WHERE Country = 'Italy'").AsEnumerable();
      }
    }
    public Customer GetCustomer(int id)
    {
      lock(collisionLock)
      {
        return database.Table<Customer>().
          FirstOrDefault(customer => customer.Id == id);
      }
    }
    public int SaveCustomer(Customer customerInstance)
    {
      lock(collisionLock)
      {
        if (customerInstance.Id != 0)
        {
          database.Update(customerInstance);
          return customerInstance.Id;
        }
        else
        {
          database.Insert(customerInstance);
          return customerInstance.Id;
        }
      }
    }
    public void SaveAllCustomers()
    {
      lock(collisionLock)
      {
        foreach (var customerInstance in this.Customers)
        {
          if (customerInstance.Id != 0)
          {
            database.Update(customerInstance);
          }
          else
          {
            database.Insert(customerInstance);
          }
        }
      }
    }
    public int DeleteCustomer(Customer customerInstance)
    {
    var id = customerInstance.Id;
      if (id != 0)
      {
        lock(collisionLock)
        {
          database.Delete<Customer>(id);
        }
      }
      this.Customers.Remove(customerInstance);
      return id;
    }
    public void DeleteAllCustomers()
    {
      lock(collisionLock)
      {
        database.DropTable<Customer>();
        database.CreateTable<Customer>();
      }
      this.Customers = null;
      this.Customers = new ObservableCollection<Customer>
        (database.Table<Customer>());
    }
  }
}

Une interface utilisateur Simple avec la liaison de données

Maintenant que vous avez le modèle et la couche d’accès aux données, vous avez besoin d’une interface utilisateur pour présenter et modifier les données. Comme vous le savez, grâce à Xamarin.Forms l’interface utilisateur peut être écrites en c# ou XAML, mais ce dernier fournit une meilleure séparation entre l’interface utilisateur et les procédures de code et vous donne une perception immédiate de l’organisation hiérarchique de l’interface utilisateur, c’est mon choix pour cet article. Il est intéressant de mentionner que, avec Xamarin.Forms 2.0, vous pouvez également activer compilation XAML (XamlC) pour l’optimisation des performances et de contrôle des erreurs de compilation. Pour plus d’informations sur XamlC, visitez bit.ly/24BSUC8.

Maintenant nous allons écrire une page simple qui affiche une liste de données à l’aide des boutons. Dans Xamarin.Forms, pages sont les éléments partagés, elles sont ajoutées au projet Portable. Pour ce faire, dans l’Explorateur de solutions, cliquez sur le projet Portable et sélectionnez Ajouter | Nouvel élément. Dans la boîte de dialogue Ajouter un nouvel élément, localisez le nœud de Cross-Platform et sélectionnez le modèle de formulaire Xaml Page, comme indiqué dans Figure 13. Nommez la nouvelle page CustomersPage.cs et cliquez sur Ajouter.

Ajout d’une nouvelle Page basée sur XAML
Figure 13. Ajout d’une nouvelle Page basée sur XAML

Pour afficher la liste des clients, l’interface utilisateur simple sera composé d’un contrôle ListView qui sont liées à la collection de clients exposée par la classe CustomersDataAccess des données. DataTemplate éléments se compose de quatre contrôles d’entrée, chaque lié aux données d’une propriété de la classe de modèle client. Si vous avez l’expérience avec d’autres plates-formes basées sur XAML, telles que Windows Presentation Foundation (WPF) et UWP, vous pouvez envisager d’entrée comme l’équivalent pour le contrôle de zone de texte. Les contrôles d’entrée sont regroupés dans un panneau StackLayout, qui est inclus dans un conteneur ViewCell. Pour ceux qui proviennent de WPF et UWP, le StackLayout est l’équivalent de Xamarin du conteneur StackPanel. ViewCell permet de créer des cellules personnalisées dans les contrôles d’élément (par exemple, ListView). Vous remarquerez que j’utilise un contrôle d’entrée avec la propriété IsEnabled affectée comme False pour la propriété Customer.Id, au lieu d’un contrôle Label, qui est en lecture seule par nature. Comme vous vous souvenez peut-être, lorsque vous appelez la méthode SQLiteConnection.Insert, cela met à jour la propriété mappée en tant que clé primaire dans votre modèle, l’interface utilisateur doit donc être en mesure de refléter automatiquement cette modification. Malheureusement, le contrôle Label ne met à jour avec la nouvelle valeur, tandis que le contrôle d’entrée, et c’est la raison de l’entrée est utilisée, mais en lecture seule.

La deuxième partie de l’interface utilisateur se compose de boutons ToolbarItem, qui fournissent un moyen facile et partagé pour offrir une interaction utilisateur via la barre de menus pratique qui est disponible sur toutes les plateformes. Par souci de simplicité, ces boutons seront implémentés dans la zone secondaire de la barre de menus, qui ne nécessite pas les icônes spécifiques à la plateforme. Figure 14 illustre le code complet pour l’interface utilisateur.

Figure 14 de l’Interface utilisateur de CustomersPage

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="https://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="LocalDataAccess.CustomersPage">
  <ListView x:Name="CustomersView"
            ItemsSource="{Binding Path=Customers}"
            ListView.RowHeight="150">
    <ListView.ItemTemplate>
      <DataTemplate>
        <ViewCell>
          <StackLayout Orientation="Vertical">
            <Entry Text="{Binding Id}" IsEnabled="False"/>
            <Entry Text="{Binding CompanyName}" />
            <Entry Text="{Binding PhysicalAddress}"/>
            <Entry Text="{Binding Country}"/>
          </StackLayout>
        </ViewCell>          
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
<ContentPage.ToolbarItems>
  <ToolbarItem Name="Add" Activated="OnAddClick"
               Priority="0" Order="Secondary" />
  <ToolbarItem Name="Remove" Activated="OnRemoveClick"
               Priority="1" Order="Secondary" />
  <ToolbarItem Name="Remove all" Activated="OnRemoveAllClick"
               Priority="2" Order="Secondary" />
  <ToolbarItem Name="Save" Activated="OnSaveClick"
               Priority="3" Order="Secondary" />
</ContentPage.ToolbarItems>
</ContentPage>

Remarquez comment chaque ToolbarItem a la propriété d’ordre attribuée en tant que serveur secondaire ; Si vous souhaitez rendre disponibles dans la zone principale de la barre d’outils et de fournir des icônes, modifier ce principal. En outre, la propriété de priorité permet de spécifier l’ordre dans lequel un ToolbarItem apparaît sur la barre d’outils, tandis que l’activé peut être comparée à un événement click et requiert un gestionnaire d’événements.

L’étape suivante écrit le code c# qui instancie la classe CustomersDataAccess, lie les objets de données et effectue des opérations sur les données. Figure 15 montre le code-behind c# de la page (voir les commentaires pour plus d’informations).

Figure 15 les code-behind CustomersPage

using System;
using System.Linq;
using Xamarin.Forms;
namespace LocalDataAccess
{
  public partial class CustomersPage : ContentPage
  {
    private CustomersDataAccess dataAccess;
    public CustomersPage()
    {
      InitializeComponent();
      // An instance of the CustomersDataAccessClass
      // that is used for data-binding and data access
      this.dataAccess = new CustomersDataAccess();
    }
    // An event that is raised when the page is shown
    protected override void OnAppearing()
    {
      base.OnAppearing();
      // The instance of CustomersDataAccess
      // is the data binding source
      this.BindingContext = this.dataAccess;
    }
    // Save any pending changes
    private void OnSaveClick(object sender, EventArgs e)
    {
      this.dataAccess.SaveAllCustomers();
    }
    // Add a new customer to the Customers collection
    private void OnAddClick(object sender, EventArgs e)
    {
      this.dataAccess.AddNewCustomer();
    }
    // Remove the current customer
    // If it exist in the database, it will be removed
    // from there too
    private void OnRemoveClick(object sender, EventArgs e)
    {
      var currentCustomer =
        this.CustomersView.SelectedItem as Customer;
      if (currentCustomer!=null)
      {
        this.dataAccess.DeleteCustomer(currentCustomer);
      }
    }
    // Remove all customers
    // Use a DisplayAlert object to ask the user's confirmation
    private async void OnRemoveAllClick(object sender, EventArgs e)
    {
      if (this.dataAccess.Customers.Any())
      {
        var result =
          await DisplayAlert("Confirmation",
          "Are you sure? This cannot be undone",
          "OK", "Cancel");
        if (result == true)
        {
          this.dataAccess.DeleteAllCustomers();
          this.BindingContext = this.dataAccess;
        }
      }
    }
  }
}

La propriété BindingContext est l’équivalent de DataContext dans WPF et UWP et représente la source de données pour la page actuelle.

Test de l’Application avec des émulateurs

Il est maintenant temps de tester l’application. Dans le fichier App.cs, vous devez modifier la page de démarrage. Lorsque vous créez un projet Xamarin.Forms, Visual Studio 2015 génère une page écrite en code procédural et affectée à un objet appelé MainPage. Cette assignation est dans le constructeur de classe d’application, ouvrez App.cs et remplacez le constructeur d’application comme suit :

public App()
{
  // The root page of your application
  MainPage = new NavigationPage(new CustomersPage());
}

Vous serez peut-être surpris de constater qu’une instance de la CustomersPage n’est pas affectée à MainPage directement. au lieu de cela, il est encapsulé en tant que paramètre d’une instance de la classe NavigationPage. La raison est que, à l’aide d’un NavigationPage est le seul moyen d’afficher une barre de menus sur Android, mais cela n’affecte pas du tout le comportement de l’interface utilisateur. Selon la plate-forme que vous souhaitez tester l’application, sélectionnez le projet de démarrage et un émulateur approprié dans la barre d’outils dans Visual Studio, appuyez sur F5. Figure 16 montre l’application en cours d’exécution sur Android et Windows 10 Mobile.

L’exemple d’application en cours d’exécution sur différentes plateformes
Figure 16 l’exemple d’application en cours d’exécution sur différentes plateformes

Notez la manière dont les éléments de barre d’outils sont affichés correctement dans la barre de menus et comment vous pouvez travailler avec des données de la même façon sur différents appareils et systèmes d’exploitation.

Écriture de Code spécifique à la plateforme avec les projets partagés

Partage de code est un concept clé dans Xamarin.Forms. En fait, tout le code qui ne tirent parti des API spécifiques à la plateforme peut être écrites une seule fois et projets partagés pour iOS, Android et Windows. Lorsque vous créez un projet Xamarin.Forms, Visual Studio 2015 offre l’application vide (Xamarin.Forms Portable) et les modèles de projet application vide (Xamarin.Forms partagée), basé sur PCL et partage des projets, respectivement. En règle générale, dans Visual Studio, vous pouvez partager du code à l’aide soit PCL, produisant des bibliothèques réutilisables .dll qui visent plusieurs plateformes, mais ne pas autoriser l’écriture de code spécifique à une plateforme ou projets partagés, qui ne produisent pas un assembly, par conséquent, leur portée est limitée à la solution à laquelle ils appartiennent. Projets partagés permettent d’écrire du code de plate-forme spécifiques.

Dans le cas Xamarin.Forms, lorsque Visual Studio 2015 génère une nouvelle solution, il ajoute un projet PCL ou un projet partagé, selon le modèle sélectionné. Dans les deux cas, le modèle sélectionné est l’emplacement que vous placez tout le code partagé. Mais dans le projet portable vous codez des interfaces qui auront une implémentation spécifique à la plateforme dans les projets Windows, dont seront membres iOS, Android, puis être appelés à l’aide de l’injection de dépendance. C’est ce que vous avez vu dans cet article.

Dans le cas de projets partagés, vous pouvez utiliser les directives de préprocesseur conditionnelles (#if, #else, #endif) et les variables d’environnement qui vous permettent de facilement comprennent quelle plate-forme de votre application s’exécute, donc vous pouvez écrire directement le code spécifique à la plateforme dans le projet partagé. Dans l’exemple d’application décrit dans cet article, la chaîne de connexion est construite à l’aide des API spécifiques à la plateforme. Si vous utilisez un projet partagé, vous pouvez écrire le code illustré Figure 17 directement dans le projet partagé.  N’oubliez pas qu’un projet partagé ne prenant en charge les packages NuGet, vous devez inclure le fichier SQLite.cs (disponible sur GitHub à l’adresse bit.ly/1QU8uiR).

Figure 17 écrire la chaîne de connexion dans un projet partagé avec les Directives de préprocesseur conditionnelles

private string databasePath {
  get {
    var dbName = "CustomersDb.db3";
    #if __IOS__
    string folder = Environment.GetFolderPath
      (Environment.SpecialFolder.Personal);
    folder = Path.Combine (folder, "..", "Library");
    var databasePath = Path.Combine(folder, dbName);
    #else
    #if __ANDROID__
    string folder = Environment.GetFolderPath
      (Environment.SpecialFolder.Personal);
    var databasePath = Path.Combine(folder, dbName);
    #else  // WinPhone
    var databasePath =
      Path.Combine(Windows.Storage.ApplicationData.Current.
      LocalFolder.Path, dbName);
    #endif
    #endif
    return databasePath;
  }
}

Comme vous pouvez le voir, vous utilisez le #if et #else, directives pour détecter plate-forme sur laquelle l’application est en cours d’exécution. Chaque plate-forme est représenté par les variables d’environnement __IOS__, __ANDROID__ ou __WINPHONE__, où __WINPHONE__ cible Windows 8.x, Windows Phone 8.x et la série UWP. Choix entre les bibliothèques portables et les projets partagés strictement dépend de vos besoins. Les bibliothèques portables sont réutilisables et nécessitent très claire entre le code partagé et spécifique à la plateforme. Projets partagés vous permettent d’écrire du code spécifique à la plate-forme ainsi que du code partagé, mais elles ne produisent pas de bibliothèques réutilisables et elles sont plus difficiles à gérer lorsque votre base de code augmente très bien.

Parmi les autres améliorations

L’exemple d’application décrit dans cet article peut être améliorée certainement bien des égards. Par exemple, vous pouvez souhaiter implémenter le modèle MVVM et exposer les commandes à l’interface utilisateur, au lieu de la gestion des événements de clic, et vous pouvez envisager de déplacer des éléments de barre d’outils à la zone principale dans la barre de menus, en fournissant les icônes spécifiques à la plateforme. À partir du point de vue de données, vous devrez peut-être des relations et des clés étrangères. Étant donné que gère à la fois avec la bibliothèque SQLite n’est pas simple, vous souhaiterez prendre en compte la bibliothèque d’Extensions de SQLite-Net (bit.ly/24yhhnP), un projet open source qui simplifie le code c# que vous avez besoin fonctionner avec des relations et des scénarios plus avancés. Il s’agit d’une simple liste d’améliorations possibles, que vous pouvez essayer d’autres études petit.

Synthèse

Dans de nombreux cas, les applications mobiles besoin de stockage de données local. Avec SQLite, les applications Xamarin.Forms peuvent gérer facilement des bases de données locales à l’aide d’une source ouverte, sans serveur et le moteur portable qui prend en charge les langages c# et les requêtes LINQ. SQLite propose des objets très intuitives pour l’utilisation des tables et des objets de base de données, rendant très facile à implémenter un accès local aux données sur n’importe quelle plateforme. Consultez la documentation de SQLite (sqlite.org/docs.html) pour plus d’informations et des scénarios supplémentaires.


Alessandro Del Sole est MVP Microsoft depuis 2008.  Promu MVP de l'année cinq fois, il est l'auteur de nombreux livres, livres, des vidéos et articles sur le développement .NET avec Visual Studio. DEL unique fonctionne comme un solution developer expert de cerveau-Sys (cerveau-sys.it), en mettant l’accent sur le développement .NET, formation et de Conseil. Vous pouvez le suivre sur Twitter : @progalex.

Remercie les experts techniques suivants d'avoir relu cet article : Kevin Alice et Sara Silva