Freigeben über



Juli 2016

Band 31, Nummer 7

Dieser Artikel wurde maschinell übersetzt.

Xamarin – Arbeiten mit lokalen Datenbanken in Xamarin.Forms mithilfe von SQLite

Durch Alessandro Del Del

Meistens, arbeiten Applikationen mit Daten. Dies gilt nicht nur für Desktop- und Webanwendungen, sondern auch für mobile apps. In vielen Fällen mobiler apps Austauschen von Daten über Netzwerke und Cloud-Speicher und Dienste wie Pushbenachrichtigungen nutzen. Es gibt jedoch Situationen, in denen mobilen apps nur die Daten lokal speichern müssen. Einfache, unstrukturierte Daten, z. B. benutzereinstellungen und Optionen können Clientanwendungen Informationen in lokalen Dateien, z. B. XML oder Text oder durch bestimmte Objekte, die die verschiedenen Entwicklungsplattformen angebotenen speichern. Im Fall von komplexen, strukturierte Daten benötigen Applications eine andere Möglichkeit, Informationen zu speichern.

Die gute Nachricht ist, dass lokale Datenbanken problemlos in Ihre mobile app mithilfe von SQLite eingeschlossen werden können (sqlite.org). SQLite ist eine open-Source einfache serverlose Datenbankmodul, die einfache Erstellung von lokalen Datenbanken und führen Operationen für Daten ermöglicht. Informationen werden in Tabellen gespeichert, und Datenoperationen beim Schreiben von C#-Code und LINQ-Abfragen ausgeführt werden können. SQLite passt perfekt plattformübergreifende Entwicklung, da es sich um eine portable Datenbankmodul handelt. In der Tat auf IOS- und Android bereits installiert ist, und kann problemlos eingesetzt werden für Windows sowie. Aus diesem Grund ist SQLite auch die perfekte Ergänzung zum Erstellen von plattformübergreifenden, datenorientierte mobiler apps mit Xamarin.Forms, die eine lokale Datenbank benötigen. In diesem Artikel zeige ich, wie eine mobile Anwendung abzielt Android, iOS und die universelle Windows-Plattform (UWP) mit Xamarin.Forms erstellt, und, die zum Speichern und Abrufen von lokalen Daten SQLite nutzt. Ich nehme an, dass Sie bereits wissen, wie eine Xamarin.Forms-Anwendung mit Visual Studio 2015 zu erstellen; Was XAML ist. und wie Sie das Debuggen einer Xamarin.Forms-app mithilfe der verschiedenen Emulators, die mit den verschiedenen Plattform-SDKs enthalten. Weitere Informationen können Sie die folgenden Artikel lesen: "Erstellen eine plattformübergreifenden UX mit Xamarin.Forms" (msdn.com/magazine/mt595754), "Teilen Benutzeroberflächencode auf mobilen Plattformen mit Xamarin.Forms" (msdn.com/magazine/dn904669) und "Erstellen eine plattformübergreifenden mobilen Golf-App mit c# und Xamarin" (msdn.com/magazine/dn630648). Letztere beschreibt das Arbeiten mit Daten über die Microsoft Azure-Plattform. Dieser Artikel und der Beispielcode basieren auf Xamarin.Forms 2.0, die Sie durch die Installation von Xamarin 4.0.3 abrufen.

Aktivieren SQLite für UWP-Apps

Das Kernmodul SQLite ist bereits auf IOS- und Android, aber nicht in Windows enthalten. Aus diesem Grund müssen Sie SQLite Binärdateien app-Paket enthalten. Statt manuell diese Binärdateien mit jedem Projekt, können Sie die SQLite-Erweiterung für Visual Studio 2015 nutzen diese bietet eine vorkompilierter Binärdateien für das Datenbankmodul und automatisiert die Aufgabe des einschließlich der erforderlichen Dateien mit neuen Projekten. Ich beschreibe vor zeigt, wie Sie neue Projekte erstellen, da die Erweiterung funktioniert auf die IDE, nicht auf der Projektebene und die vorkompilierten Binärdateien SQLite bereitstellt, jedes Mal, wenn Sie die SQLite-Bibliotheken in Projektmappen enthalten. Es gibt mehrere SQLite Erweiterungen, die jeweils für eine bestimmte Windows-Version, die heruntergeladen werden kann mit dem Tool Erweiterungen und Updates in Visual Studio 2015, siehe Abbildung 1.

Der SQLite herunterladen für universelle Windows-Plattform-Erweiterung in Visual Studio 2015
Abbildung 1 die SQLite für universelle Windows-Plattform-Erweiterung in Visual Studio 2015 herunterladen

In diesem Fall herunterladen Sie und installieren Sie die SQLite für universelle Windows-Plattform-Erweiterung. Auf diese Weise wird eine UWP-app, die SQLite verwendet, auch die vorkompilierte Datenbank-Engine-Binärdateien enthalten. Falls erforderlich, starten Sie Visual Studio 2015 nach der Installation der Erweiterungs.

Erstellen ein Beispielprojekt

Führen Sie zunächst wird ein neues Projekt basierend auf Xamarin Forms erstellen. Die Projektvorlage, die Sie in Visual Studio 2015 verwenden, heißt leere App (Xamarin.Forms Portable) und befindet sich im Ordner plattformübergreifende des Visual C#-Knotens im Dialogfeld Neues Projekt (siehe Abbildung 2).

Erstellen ein neue Xamarin Forms-Projekt in Visual Studio 2015
Abbildung 2: Erstellen einer neuen Xamarin Forms-Projekt in Visual Studio 2015

Der Grund für die Auswahl von tragbaren Projekttyp anstelle der Shared-Typ ist, Sie erzeugt eine wieder verwendbare Datenzugriffsschicht innerhalb einer Bibliothek sollten ist ein freigegebenes Projekt Bereich nur innerhalb der Projektmappe zu der er gehört. Am Ende des Artikels erkläre ich, gründlicher die Unterschiede zwischen portablen Bibliotheken und der Projekte freigegeben werden.

Wenn Sie auf "OK" klicken, generiert Visual Studio 2015 eine neue Lösung, die Projekte für iOS, Android, UWP, Windows-Runtime und Windows Phone sowie eine Portable Klassenbibliothek (PCL)-Projekt enthält. Letzteres ist, wo Sie den Großteil des Codes schreiben werde, die über den plattformspezifischen Projekten gemeinsam genutzt werden. 

Der SQLite NuGet-Paket installieren

Wenn Sie das Projekt erstellt haben, benötigen Sie eine verwaltete Möglichkeit, SQLite-Datenbanken zugreifen. Es gibt viele Bibliotheken, die für eine SQLite-Datenbanken in Microsoft .NET Framework zu ermöglichen, aber der benötigten ist eine spezielle portable Bibliothek, die auch Xamarin-apps abzielt. SQLite-Net bezeichnet, und es ist ein open-Source und lightweight-Bibliothek für .NET, Mono und Xamarin. Es ist als NuGet-Paket mit dem Namen Sqlite-Net-Pcl verfügbar. Sie können das NuGet-Paket auf Projektmappenebene entweder die NuGet-Paket-Manager-Konsole installieren, Installation Sqlite-Net-Pcl eingeben oder in der UI NuGet in Visual Studio 2015, die es Ihnen ermöglichen, indem Sie mit der rechten Maustaste im Projektmappen-Explorer auf den Namen der Projektmappe, und wählen Sie dann NuGet-Pakete verwalten. Abbildung 3 suchen und installieren Sie die Sqlite-Net-Pcl-Paket über die UI NuGet veranschaulicht.

Installieren die entsprechenden NuGet-Pakete
Abbildung 3: installieren die entsprechenden NuGet-Pakete

Jetzt haben Sie alles, was Sie benötigen, und jetzt können Sie dem Kodieren beginnen.

Plattformspezifischen Code: Angeben der Verbindungszeichenfolge

Wie in jeder Datenbank, Code eine SQLite-Datenbank über die Verbindungszeichenfolge zugreift, was im ersten Schritt müssen Sie erstellen. Da eine SQLite-Datenbank eine Datei, die sich in einem lokalen Ordner befindet handelt, erfordert das Erstellen der Verbindungszeichenfolge den Pfadnamen der Datenbank. Obwohl der Großteil des Codes, den Sie schreiben müssen auf verschiedenen Plattformen freigegeben ist, unterscheidet sich der, wie Android, iOS und Windows Pfadnamen behandelt, deshalb muss der plattformspezifischen Code erstellen die Verbindungszeichenfolge. Rufen Sie anschließend die Verbindungszeichenfolge über Abhängigkeitsinjektion.

Portable im Projekt fügen Sie eine neue Schnittstelle namens IDatabaseConnection.cs, und Schreiben Sie den folgenden Code:

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

Diese Schnittstelle macht eine Methode namens "DbConnection", wird in jedem Projekt plattformspezifischen implementiert sein, und die richtige Verbindungszeichenfolge zurück.

Der nächste Schritt ist eine Klasse hinzufügen, jede plattformspezifische-Projekt, das die-Schnittstelle implementiert, und gibt die richtige Verbindungszeichenfolge, basierend auf einer Beispieldatenbank CustomersDb.db3 aufgerufen. (Wenn Sie nicht mit SQLite vertraut sind, ist .db3 die Erweiterung, die SQLite-Datenbank identifiziert.) LocalDataAccess.Droid im Projekt eine neue Klasse namens DatabaseConnection_Android.cs hinzufügen, und Schreiben Sie den Code in Abbildung 4.

Abbildung 4 generieren eine Verbindungszeichenfolge in der Android-Projekts

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

Ein Attribut namens Xamarin.Forms.Dependency gibt an, dass die angegebene Klasse eine erforderliche Schnittstelle implementiert. Dieses Attribut wird auf Namespaceebene mit dem Schlüsselwort Assembly angewendet. Für Android muss die Datenbankdatei in den persönlichen Ordner gespeichert werden, damit der Pfadnamen für die Datenbank den Dateinamen (CustomersDb.db3) und der persönlichen Ordnerpfad besteht. Der resultierende Pfadname wird als Parameter an den Konstruktor der Klasse SQLiteConnection zugewiesen und an den Aufrufer zurückgegeben. Klicken Sie unter iOS Sie verwenden die gleiche API, aber der Ordner, in dem die SQLite-Datenbank befindet, ist Personal\Library.

Nun fügen Sie eine neue Klasse namens DatabaseConnection_iOS.cs dem iOS-Projekt, und Schreiben Sie den Code in Abbildung 5.

Abbildung 5 Erstellen einer Verbindungszeichenfolge in der iOS-Projekt

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

Befindet sich unter Windows 10 SQLite-Datenbank im lokalen Anwendungsordner. Die API, die für den Zugriff auf den lokalen Ordner unterscheidet sich von den anderen Plattformen, da Sie Klassen aus dem Windows.Storage-Namespace anstelle von System.IO verwenden. Fügen Sie eine neue Klasse namens DatabaseConnection_UWP.cs die universelle Windows-Projekt, und Schreiben Sie den Code in Abbildung 6.

Abbildung 6 Erstellen einer Verbindungszeichenfolge in die universelle Windows-Projekt

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

Dieses Mal wird die app lokalen Pfad durch die Eigenschaft Windows.Storage.ApplicationData.Current.LocalFolder.Path zurückgegeben mit den Namen der Datenbank an die Verbindungszeichenfolge über das SQLiteConnection-Objekt zurückgeben kombiniert wird. Jetzt haben Sie plattformspezifischen Code geschrieben, das ermöglicht die Generierung der richtigen Verbindungszeichenfolge basierend auf der Plattform, auf der die Anwendung ausgeführt wird. Von nun an wird Ihren gesamten Code gemeinsam genutzt werden. Im nächste Schritt wird ein Datenmodell implementiert.

Schreiben Sie ein Datenmodell

Das Ziel der app ist eine vereinfachte Liste von Kunden, die eine SQLite-Datenbank gespeicherten arbeiten und Operationen auf Daten zu unterstützen. Die erste erforderliche Schritt für ist an diesem Punkt eine Klasse einen Kunden dar, der eine Tabelle in der Datenbank zugeordnet werden. Fügen Sie eine Klasse namens Customer.cs Portable im Projekt hinzu. Diese Klasse muss die INotifyPropertyChanged-Schnittstelle, um benachrichtigt zu werden, die darin gespeicherten Aufrufer der Änderungen an den Daten implementieren. Es verwendet spezielle Attribute im Namespace SQLite kommentieren Eigenschaften Validierungsregeln und andere Informationen in einer Weise, die sehr nahe der datenanmerkungen aus dem System.ComponentModel.DataAnnotations-Namespace ist. Abbildung 7 zeigt die Beispiel Customer-Klasse.

Abbildung 7: implementieren ein Datenmodell

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

Der Kernpunkt dieser Klasse ist Objekte mit SQLite Attributen versehen. Das Attribut der Tabelle ermöglicht einen Tabellennamen ein, in diesem Fall Kunden zuweisen. Dies ist nicht obligatorisch, aber wenn Sie nicht angeben, generiert SQLite eine neue Tabelle basierend auf den Klassennamen und den Kunden in diesem Fall. Damit der Code aus Gründen der Konsistenz, eine neue Tabelle mit einem Namen im plural generiert. Die PrimaryKey und AutoIncrement-Attribute angewendet, um die Id-Eigenschaft machen den Primärschlüssel in der Tabelle "Customers" und automatische Inkrement. Das NotNull-Attribut, das auf die CompanyName-Eigenschaft angewendet kennzeichnet dieses nach Bedarf, d. h., dass die Überprüfung der Speicher fehl, wenn der Eigenschaftswert null ist. Das MaxLength-Attribut angewendet wird, an die physikalische Adresse-Eigenschaft gibt die maximale Länge für den Wert der Eigenschaft an. Ein weiteres interessantes Attribut ist die Spalte, die auf eine einen Eigenschaftennamen für einen anderen Spaltennamen in der Datenbank angewendet werden können.

Implementierung des Datenzugriffs

Nach dem Schreiben eines einfachen Datenmodells, benötigen Sie eine Klasse, die Methoden bereitstellt, die Operationen für Daten ausführen. Aus Gründen der Übersichtlichkeit und da dies ein einfaches Beispiel ist, nicht ich hier einen Model-View-ViewModel (MVVM)-Ansatz verwendet nicht alle Leser haben noch keine Erfahrung mit diesem Muster und wird in jedem Fall eine bessere Leistung bei großen Projekten verwendet. Sie können das Beispiel sicherlich umschreiben, wie gewünscht.

Beginnen wir mit eine neue Klasse namens CustomersDataAccess.cs tragbaren im Projekt erfordert die folgende using-Direktiven hinzu:

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

Der ersten Dinge in dieser Klasse sind ein privates Feld, in der Verbindungszeichenfolge gespeichert und ein Objekt, das mit der Implementierung von Sperren auf Datenoperationen, Datenbank-Konflikte zu vermeiden:

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

Genauer gesagt, sollten die Sperren das folgende Format aufweisen:

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

Verwenden von XAML zum Erstellen der Benutzeroberfläche und nutzen können Bindung anzeigen und Eingeben von Informationen, mit Xamarin.Forms; aus diesem Grund müssen Sie die Daten auf eine Weise verfügbar machen, die XAML zur Verfügung steht. Der beste Ansatz ist eine Eigenschaft des Typs ObservableCollection < Customer > wie folgt bereitstellen:

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

Der ObservableCollection-Typ verfügt über integrierte Unterstützung für die Benachrichtigung, Daher ist es die am besten geeignete Auflistung für die Datenbindung in XAML-basierte Plattformen.

Jetzt es Zeit ist für den Konstruktor der Klasse implementieren, die auch zum Aufrufen der Plattform-spezifische Implementierung der DbConnection-Methode, die die richtige Verbindungszeichenfolge zurückgibt, siehe Abbildung 8.

Abbildung 8: Implementieren des Klassenkonstruktors

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

Beachten Sie, wie der Code die DependencyService.Get-Methode aufgerufen wird. Dies ist eine generische Methode, die in diesem Fall die plattformspezifische Implementierung des angegebenen generischen Typs, IDatabaseConnection zurückgibt. Bei diesem Ansatz basierend auf Abhängigkeitsinjektion ist der Code die plattformspezifische Implementierung der DbConnection-Methode aufgerufen werden. Die Laufzeit Xamarin weiß dann, wie zum Auflösen des Methodenaufrufs, der auf Basis des Betriebssystems, auf dem die Anwendung ausgeführt wird. Das Aufrufen dieser Methode auch bewirkt, dass die Datenbank erstellt werden, wenn eine gefunden wird. Das SQLiteConnection-Objekt macht eine generische Methode namens CreateTable < T >, wobei der generische Typ Modellklasse, Kunden in diesem Fall ist. Mit dieser einfachen Zeile des Codes erstellen Sie eine neue Customers-Tabelle. Wenn die Tabelle bereits vorhanden ist, wird nicht sie überschrieben. Der Code initialisiert auch die Kunden-Eigenschaft. Ruft die Tabelle < T > generische Methode SQLiteConnection, wobei der generische Typ noch Modellklasse ist. Tabelle < T > Gibt ein Objekt vom Typ TableQuery < T >, das die IEnumerable < T >-Schnittstelle implementiert und kann auch mit LINQ abgefragt werden. Das zurückgegebene Ergebnis besteht aus einer Liste von Objekten < T >; ist allerdings binden eine TableQuery-Objekt, das die Benutzeroberfläche direkt nicht das geeignete Verfahren zum Präsentieren von Daten, damit eine neue ObservableCollection < Customer > basierend auf das zurückgegebene Ergebnis generiert und der Kunden-Eigenschaft zugewiesen. Der Code ruft auch eine Methode namens AddNewCustomer, wenn die Tabelle leer ist, wie folgt definiert ist:

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

Mit dieser Methode wird ein neuer Kunde auf die Auflistung der Kunden mit Standardeigenschaftswerten und vermeidet die Bindung an eine leere Auflistung. 

Abfragen von Daten

Abfragen von Daten ist sehr wichtig. SQLite ist im Wesentlichen zwei Arten von Abfragen zu implementieren. Die erste Methode LINQ für das Ergebnis eines Aufrufs an die Tabelle < T >-Methode verwendet, ist ein Objekt vom Typ TableQuery < T > und die IEnumerable < T >-Schnittstelle implementiert. Das zweite ist das Aufrufen einer Methode namens SQLiteConnection.Query < T >, die ein Argument vom Typzeichenfolgen akzeptiert, die eine in SQL geschriebene Abfrage darstellt. Der Code in Abbildung 9 veranschaulicht, wie die Liste der Kunden nach Land, die mit beiden Ansätzen zu filtern.

Abbildung 9 Filtern einer Liste von Kunden nach Land

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

Die erste Überladung GetFilteredCustomers gibt das Ergebnis einer LINQ-Abfrage, die Daten basierend auf der Ländername als Methodenargument filtert. Die zweite Überladung ruft die Abfrage aus, um SQL-Abfragen direkt ausführen. Diese Methode erwartet, dass das Ergebnis einer generischen Liste, deren generischer Typ die gleiche Abfrage übergeben ist. Eine SQLiteException wird ausgelöst, wenn die Abfrage fehlschlägt. Natürlich können Sie eine angegebenen Objektinstanz, die mithilfe von LINQ oder Erweiterung Methoden, wie im folgenden Code, durch die "FirstOrDefault" über die Liste der Kunden, und ruft die angegebene Instanz auf Grundlage der ID, abrufen:

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

Ausführen von CRUD-Operationen

Erstellen, lesen, aktualisieren und löschen (CRUD) Vorgänge sind ebenfalls sehr wichtig. Lesen von Daten in der Regel erfolgt unter Verwendung der im vorherigen Abschnitt beschriebenen Ansätze, jetzt erläutert das Erstellen, aktualisieren und Löschen von Informationen in einer SQLite-Datenbank. Das SQLiteConnection-Objekt macht die INSERT-, InsertAll, Update und UpdateAll Methoden zum Einfügen oder Aktualisieren von Objekten in der Datenbank. InsertAll und UpdateAll Ausführen eine Insert oder update-Operation für eine Sammlung, die IEnumerable < T > als Argument übergebenen implementiert. Der INSERT- oder Update-Vorgang in einem Batch ausgeführt wird, und beide Methoden ermöglichen auch das der Vorgang als eine Transaktion ausgeführt wird. Bedenken Sie, dass während der InsertAll erfordert, dass keine Elemente in der Auflistung in der Datenbank vorhanden sind, UpdateAll erfordert, dass alle Elemente in der Auflistung bereits in der Datenbank vorhanden. In diesem Fall habe ich eine ObservableCollection < Customer >, die Objekte aus der Datenbank abgerufen und neue Objekte hinzugefügt haben, über die Benutzeroberfläche, die noch nicht gespeichert wurden oder ausstehende Bearbeitungen über vorhandene Objekte enthalten kann. Aus diesem Grund nicht mit InsertAll und UpdateAll empfohlen. Ein guter Ansatz einfach überprüft, ob eine Instanz der Customer-Klasse über eine ID verfügt. Ist dies der Fall ist, ist die Instanz bereits in der Datenbank vorhanden, muss nur aktualisiert werden. Wenn die Id 0 (null) ist, vorhanden nicht die Instanz in der Datenbank. Daher muss sie gespeichert werden. Der Code in Abbildung 10 einfügen oder Aktualisieren einer einzelnen Instanz eines Customer-Objekts basierend auf der vorherigen Aspekt veranschaulicht.

Abbildung 10 einfügen oder Aktualisieren einer einzelnen Instanz von einem Kunden ObjectDepending auf das Vorhandensein einer Kunden-ID-Klasse

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

Der Code in Abbildung 11, stattdessen wird veranschaulicht, wie einfügen oder aktualisieren alle Instanzen des Kunden.

Abbildung 11: Einfügen oder aktualisieren alle Instanzen des Kunden

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

Die INSERT- und Update-Methoden zurückgeben, eine ganze Zahl, die die Anzahl der Zeilen, die hinzugefügt oder aktualisiert darstellt. INSERT bietet auch eine Überladung, die eine Zeichenfolge, die zusätzliche SQL-Anweisungen akzeptiert, für die eingefügten Zeilen ausgeführt werden soll. Außerdem ist es zu erwähnen, dass Einfügen automatisch, durch einen Verweis, die Eigenschaft in das Geschäftsobjekt aktualisiert, die in diesem Fall werden von den primären Schlüssel ' Customer.ID ' zugeordnet wurde. Die SQLiteConnection-Klasse auch verfügbar macht, die Delete < T > und DeleteAll < T > Methoden, die eine oder alle Objekte in einer Tabelle dauerhaft zu löschen. Der Löschvorgang wird nicht rückgängig gemacht werden, daher bekannt sind. Der folgende Code implementiert eine Methode namens DeleteCustomer, mit der die angegebene Instanz aus der Customers-Auflistung im Arbeitsspeicher und der Datenbank gelöscht:

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

Weist der angegebene Kunden-Id, ist es in der Datenbank vorhanden, ist er dauerhaft gelöscht, und es ist auch aus der Customers-Auflistung entfernt. Delete < T > Gibt eine ganze Zahl, die die Anzahl der gelöschten Zeilen darstellt. Sie können auch alle Objekte aus einer Tabelle löschen. Sie können sicherlich DeleteAll < T >, in dem der generische Typ ist das Geschäftsobjekt z. B. Kunde, aber ich stattdessen eine alternative Methode anzeigen möchten, damit Sie Kenntnisse über andere Member können aufrufen. Die SQLiteConnection-Klasse macht eine Methode namens DropTable < T >, wodurch eine Tabelle in der Datenbank dauerhaft verloren gehen. Löschen einer Tabelle könnte beispielsweise wie folgt implementiert werden:

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

Der Code löscht die Tabelle Customers, dann erstellt, und schließlich bereinigt und erstellt die Customers-Auflistung. Abbildung 12 zeigt die vollständige Auflistung für die CustomersDataAccess.cs-Klasse.

Abbildung 12 die CustomersDataAccess.cs-Klasse

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

Eine einfache Benutzeroberfläche mit Daten

Nun, dass Sie das Modell und der Datenzugriffsschicht haben, benötigen Sie eine Benutzeroberfläche zum darstellen und Bearbeiten von Daten. Wie Sie wissen, mit Xamarin.Forms kann die Benutzeroberfläche mit c# oder XAML geschrieben werden, aber der zweite Schritt bietet eine bessere Trennung zwischen der Benutzeroberfläche und dem prozeduralen Code und bietet Ihnen eine sofortige Wahrnehmung der Benutzeroberfläche hierarchische Organisation meiner Wahl für diesen Artikel ist. Es ist erwähnenswert, dass mit Xamarin.Forms 2.0 können Sie auch XAML-Kompilierung (XamlC) für die Optimierung der Leistung und Kompilierungsfehler Kontrollkästchen aktivieren können. Weitere Informationen zur XamlC, besuchen Sie bit.ly/24BSUC8.

Jetzt schreiben wir eine einfache Seite, die eine Liste von Daten mit Schaltflächen angezeigt wird. In Xamarin.Forms sind Seiten gemeinsam genutzte Elemente, damit diese das Portable Projekt hinzugefügt haben. Zu diesem Zweck im Projektmappen-Explorer mit der rechten Maustaste des Portable-Projekts, und wählen Sie hinzufügen | Neues Element. Suchen Sie im Dialogfeld Neues Element hinzufügen den plattformübergreifenden-Knoten, und wählen Sie die Xaml-Page-Vorlage, siehe Abbildung 13. Nennen Sie die neue Seite CustomersPage.cs, und klicken Sie auf Hinzufügen.

Hinzufügen einer neuen Seite basierend auf XAML
Abbildung 13: Hinzufügen einer neuen Seite auf der Grundlage von XAML

Um die Liste der Kunden anzuzeigen, wird die einfache Benutzeroberfläche besteht ein ListView-Steuerelement aus, die an Daten gebunden, die Kunden-Auflistung, die von der CustomersDataAccess-Klasse verfügbar gemacht wird. Der Artikel DataTemplate besteht aus vier Dateneingabe-Steuerelemente, jeder Daten an eine Eigenschaft der Customer-Modell-Klasse gebunden. Wenn Sie noch keine Erfahrung mit anderen XAML-basierten Plattformen wie Windows Presentation Foundation (WPF) und UWP haben, können Sie Eintrag als Entsprechung für das TextBox-Steuerelement in Betracht ziehen. Die Dateneingabe-Steuerelemente sind in einem StackLayout Bereich gruppiert in einem Container ViewCell enthalten ist. Für diejenigen, die von WPF und UWP stammen, entspricht die StackLayout Xamarin den StackPanel-Container. ViewCell ermöglicht das Erstellen von benutzerdefinierter Zellen in Steuerelementen (z. B. ListView). Sie werden bemerken, dass ich verwende ein Steuerelement zur Eingabe von mit der IsEnabled-Eigenschaft, die als "false" für die Eigenschaft ' Customer.ID ' statt ein Label-Steuerelement naturgemäß schreibgeschützt ist. Wie Sie sich erinnern können, wenn Sie die SQLiteConnection.Insert-Methode aufrufen, aktualisiert diese die Eigenschaft, die als Primärschlüssel in Ihrem Modell zugeordnet werden, sodass die Benutzeroberfläche automatisch entsprechend dieser Änderung kann soll. Leider aktualisieren nicht das Label-Steuerelement selbst mit den neuen Wert während der Eingabe-Steuerelement, und dies der Grund, den Eintrag verwendet ist, den jedoch als schreibgeschützt festgelegt.

Der zweite Teil der Benutzeroberfläche besteht aus ToolbarItem Schaltflächen, die bieten eine einfache und freigegebene Möglichkeit, ein Eingreifen des Benutzers über eine einfache Menüleiste anzubieten, die auf allen Plattformen zur Verfügung stehen. Der Einfachheit halber werden diese Schaltflächen im Bereich der Menüleiste sekundären implementiert die plattformspezifische Symbole erfordert. Abbildung 14 zeigt den vollständigen Code für die Benutzeroberfläche.

Abbildung 14: die CustomersPage-Benutzeroberfläche

<?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>

Beachten Sie, wie jede ToolbarItem die Order-Eigenschaft, die als sekundäre Datenbank zugewiesen wurde. Wenn Sie den primären Bereich in der Symbolleiste verfügbar machen und einige Symbole angeben möchten, ändern Sie diesen als Primär. Außerdem können Sie die Eigenschaft Priorität angeben der Reihenfolge, in der ein ToolbarItem auf der Symbolleiste angezeigt wird, während ein Click-Ereignis aktiviert verglichen werden kann, und erfordert einen Ereignishandler.

Der nächste Schritt ist C#-Code schreiben, der die CustomersDataAccess-Klasse instanziiert, die Daten bindet Objekte und Daten durchführt. Abbildung 15 veranschaulicht die C#-Codebehind für die Seite (Siehe Kommentare für Weitere Informationen).

Abbildung 15 CustomersPage Codebehind

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

BindingContext-Eigenschaft entspricht dem DataContext in WPF und UWP und die Datenquelle für die aktuelle Seite darstellt.

Testen der Anwendung mit Emulatoren

Jetzt ist es Zeit, die Anwendung zu testen. In der Datei App.cs müssen Sie die Startseite zu ändern. Wenn Sie ein Xamarin.Forms-Projekt erstellen, generiert Visual Studio 2015 eine Seite in prozeduralem Code geschrieben und ein Objekt namens MainPage zugewiesen. Diese Zuordnung wird im Konstruktor App-Klasse, also App.cs öffnen, und Ersetzen Sie den App-Konstruktor wie folgt:

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

Sie werden überrascht sein, dass eine Instanz der CustomersPage MainPage direkt zugeordnet ist; Stattdessen wird er als Parameter für eine Instanz der NavigationPage-Klasse gekapselt. Der Grund ist, dass mit einer "NavigationPage" die einzige Möglichkeit zum Anzeigen einer Menüleiste unter Android ist, aber dies sich nicht auf allen Verhalten der Benutzeroberfläche wirkt. Abhängig von der Plattform, die Sie die app testen möchten, wählen Sie das Startprojekt und einen entsprechenden Emulator in der Standardsymbolleiste in Visual Studio, und drücken Sie F5. Abbildung 16 zeigt die unter Android und Windows 10 Mobile-app.

Die Beispiel-App auf unterschiedlichen Plattformen ausgeführt
Abbildung 16: die Beispiel-App auf unterschiedlichen Plattformen ausgeführt

Beachten Sie, wie die Elemente der Symbolleiste in der Menüleiste ordnungsgemäß angezeigt werden und wie Sie mit Daten auf die gleiche Weise auf verschiedenen Geräten und Betriebssystemen verwendet werden können.

Schreiben von plattformspezifischen Code mit freigegebenen Projekten

Freigeben von Code ist ein Kernkonzept von Xamarin.Forms. In der Tat der Code aus, der plattformspezifische APIs nutzen, nicht einmal geschrieben werden kann und für iOS, Android und Windows Projekte freigegeben. Wenn Sie ein Xamarin.Forms-Projekt erstellen, bietet Visual Studio 2015 die leere App (Xamarin.Forms Portable) und die leere App (Xamarin.Forms freigegeben)-Projektvorlagen, basierend auf PCLs und Projekten bzw. freigegeben. Im Allgemeinen ist in Visual Studio können Sie Code mithilfe von entweder portable Klassenbibliotheken, die wieder verwendbare DLL-Bibliotheken zu, die auf mehrere Plattformen abzielen, aber lassen keine plattformspezifischen Code oder freigegebene Projekte, die Assembly nicht liefern erstellen schreiben freigeben, also ihre beschränkt auf die Projektmappe, die sie angehören. Freigegebene Projekte lassen die plattformspezifischen Code schreiben.

Im Fall von Xamarin.Forms Wenn Visual Studio 2015 eine neue Projektmappe generiert hinzugefügt ein PCL-Projekt oder ein freigegebenes Projekt abhängig von der von Ihnen ausgewählte Vorlage. In beiden Fällen ist die ausgewählte Vorlage die Stelle, die Sie alle freigegebenen Code einfügen. Aber im Projekt portablen code Sie Schnittstellen, die eine plattformspezifische Implementierung im iOS, Android und Windows-Projekten, deren Mitglieder werden müssen, und klicken Sie dann über die Abhängigkeitsinjektion aufgerufen werden. Dies ist, was Sie in diesem Artikel gesehen haben.

Bei freigegebenen Projekten können Sie bedingte Präprozessordirektiven (#if, #else-#endif) und Umgebungsvariablen, mit denen Sie problemlos verstehen welche Plattform, die Ihre app ausgeführt wird, damit Sie die plattformspezifischen Code direkt in das freigegebene Projekt schreiben können. In der Beispiel-app in diesem Artikel beschrieben wird wird die Verbindungszeichenfolge mit plattformspezifischen APIs erstellt. Wenn Sie ein freigegebenes Projekt verwenden, könnten Sie den Code in in Abbildung 17 direkt im freigegebenen Projekt.  Beachten Sie, dass ein freigegebenes Projekt NuGet-Pakete nicht unterstützt, damit Sie die Datei SQLite.cs enthalten müssen (verfügbar auf GitHub unter bit.ly/1QU8uiR).

Abbildung 17: schreiben die Verbindungszeichenfolge in einem freigegebenen Projekt mit bedingte Präprozessordirektiven

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

Wie Sie sehen können, verwenden Sie die #if und #else-Direktive, um die Plattform, auf die app erkennen ausgeführt wird. Jede Plattform wird dargestellt, die Umgebungsvariablen __IOS__, __ANDROID__ oder __WINPHONE__, __WINPHONE__, in denen Windows abzielt 8.x, Windows Phone 8.x und die UWP. Auswählen zwischen portablen Bibliotheken und freigegebenen Projekten streng hängt von Ihren Anforderungen. Portable Bibliotheken können wiederverwendet werden und sehr saubere Trennung zwischen freigegebenen und plattformspezifischen Code erfordern. Freigegebene Projekte können Sie plattformspezifischen Code zusammen mit freigegebenem Code zu schreiben, aber sie wiederverwendbare Bibliotheken erstellen und schwieriger zu verwalten, wenn die CodeBase sehr viel wächst sind.

Weitere Verbesserungen

Die beispielanwendung in diesem Artikel beschriebenen kann sicherlich in vielerlei Hinsicht verbessert werden. Beispielsweise empfiehlt das MVVM-Muster implementieren und Befehle aus, um die Benutzeroberfläche bereitstellen, anstatt Behandlung click-Ereignisse, und könnten Sie erwägen, verschieben Symbolleistenelemente primären Bereich in der Menüleiste plattformspezifische Symbole angeben. Aus Sicht der Daten müssen Sie die Arbeit mit Beziehungen und Fremdschlüssel. Da behandeln sowohl mit der SQLite-Bibliothek nicht einfach, möchten Sie möglicherweise sollten die SQLite-Net-Erweiterungsbibliothek (bit.ly/24yhhnP), ein open-Source-Projekt, das den C#-Code vereinfacht Sie mit Beziehungen und komplexere Szenarien arbeiten müssen. Dies ist nur eine kleine Liste der möglichen Verbesserungen, die Sie für weitere Studien ausprobieren können.

Nachbereitung

In den meisten Fällen benötigen mobile apps lokale Speicherung von Daten. Xamarin.Forms-Clientanwendungen können mit SQLite einfach verwalten, lokale Datenbanken, die mit einem open-Source serverlose, und tragbaren Engine, die C#- und LINQ-Abfragen unterstützt. SQLite bietet intuitiv Objekte zum Arbeiten mit Tabellen und Datenbankobjekte, sehr einfach zu lokalen Datenzugriff auf jeder Plattform zu implementieren. Sehen Sie sich die SQLite-Dokumentation (sqlite.org/docs.html) Weitere Informationen und zusätzliche Szenarien.


Alessandro Del Sole ist seit 2008 Microsoft MVP.  Er wurde bereits fünfmal mit diesem Titel ausgezeichnet und ist Autor zahlreicher Bücher, E-Books, Schulungsvideos und Artikel zur .NET-Entwicklung mit Visual Studio. Del Sole funktioniert wie eine Lösung Developer Experten für die Brain-Sys (Brain-sys.it) konzentriert sich auf die Entwicklung mit .NET training und Beratung. Sie können ihm auf Twitter folgen: @progalex..

Dank der folgenden technischen Experten für die Überprüfung dieses Artikels: Kevin Ashley und Sara Silva