Datenbanken in Xamarin.Mac
In diesem Artikel wird die Verwendung von Key-Value-Codierung und Key-Value-Observing behandelt, um die Datenbindung zwischen SQLite-Datenbanken und UI-Elementen im Schnittstellen-Generator von Xcode zu ermöglichen. Außerdem wird die Verwendung des SQLite.NET ORM zum Bereitstellen des Zugriffs auf SQLite-Daten behandelt.
Übersicht
Beim Arbeiten mit C# und .NET in einer Xamarin.Mac-Anwendung haben Sie Zugriff auf dieselben SQLite-Datenbanken, auf die eine Xamarin.iOS- oder Xamarin.Android-Anwendung zugreifen kann.
In diesem Artikel werden wir zwei Möglichkeiten für den Zugriff auf SQLite-Daten behandeln:
- Direct Access – Durch den direkten Zugriff auf eine SQLite-Datenbank können wir Daten aus der Datenbank verwenden, um Schlüsselwertcodierung und Datenbindung mit UI-Elementen zu verwenden, die im Schnittstellen-Generator von Xcode erstellt wurden. Mithilfe von Schlüsselwertcodierungs- und Datenbindungstechniken in Ihrer Xamarin.Mac-Anwendung können Sie die Menge an Code, den Sie schreiben und Standard, um UI-Elemente aufzufüllen und zu bearbeiten, erheblich verringern. Außerdem profitieren Sie von der weiteren Entkoppelung Ihrer Sicherungsdaten (Datenmodell) von der Front-End-Benutzeroberfläche (Model-View-Controller), was zu einfacheren Standard nachhaltigen, flexibleren Anwendungsdesigns führt.
- SQLite.NET ORM – Mithilfe des Open Source-SQLite.NET Object Relationship Manager (ORM) können wir die Menge an Code erheblich reduzieren, der zum Lesen und Schreiben von Daten aus einer SQLite-Datenbank erforderlich ist. Diese Daten können dann verwendet werden, um ein Benutzeroberflächenelement wie eine Tabellenansicht aufzufüllen.
In diesem Artikel behandeln wir die Grundlagen der Arbeit mit Key-Value-Codierung und Datenbindung mit SQLite-Datenbanken in einer Xamarin.Mac-Anwendung. Es wird dringend empfohlen, dass Sie zuerst den Artikel "Hello, Mac " durcharbeiten, insbesondere die Abschnitte "Einführung in Xcode" und "Interface Builder " und "Outlets" und "Actions ", da es sich um wichtige Konzepte und Techniken handelt, die wir in diesem Artikel verwenden werden.
Da wir schlüsselwertbasierte Codierung und Datenbindung verwenden werden, arbeiten Sie zuerst durch die Datenbindung und schlüsselwertbasierte Codierung , da kerne Techniken und Konzepte behandelt werden, die in dieser Dokumentation und ihrer Beispielanwendung verwendet werden.
Möglicherweise möchten Sie sich auch die Exposing C#-Klassen /-Methoden im Objective-C Abschnitt des Xamarin.Mac Internals-Dokuments ansehen. Außerdem werden die Register
C#-Klassen und Export
-Attribute erläutert, die zum Verbinden Ihrer C#-Klassen mit Objective-C Objekten und UI-Elementen verwendet werden.
Direkter SQLite-Zugriff
Für SQLite-Daten, die an UI-Elemente im Benutzeroberflächen-Generator von Xcode gebunden werden sollen, wird dringend empfohlen, direkt auf die SQLite-Datenbank zuzugreifen (im Gegensatz zur Verwendung einer Technik wie einem ORM), da Sie die Gesamtkontrolle darüber haben, wie die Daten geschrieben und aus der Datenbank gelesen werden.
Wie wir in der Dokumentation "Datenbindung" und "Key-Value Coding" gesehen haben, können Sie die Menge des Codes, den Sie Standard schreiben müssen, erheblich verringern, indem Sie schlüsselwertbasierte Codierungstechniken und Datenbindungstechniken in Ihrer Xamarin.Mac-Anwendung verwenden. In Kombination mit direktem Zugriff auf eine SQLite-Datenbank kann die Menge des Codes, der zum Lesen und Schreiben von Daten in diese Datenbank erforderlich ist, erheblich reduziert werden.
In diesem Artikel ändern wir die Beispiel-App aus dem Datenbindungs- und Schlüsselwertcodierungsdokument, um eine SQLite-Datenbank als Sicherungsquelle für die Bindung zu verwenden.
Einschließen der SQLite-Datenbankunterstützung
Bevor wir fortfahren können, müssen wir unserer Anwendung SQLite-Datenbankunterstützung hinzufügen, indem wir verweise auf ein paar von . DLLs-Dateien.
Gehen Sie folgendermaßen vor:
Klicken Sie im Lösungspad mit der rechten Maustaste auf den Ordner "Verweise", und wählen Sie "Verweise bearbeiten" aus.
Wählen Sie die Assemblys "Mono.Data.Sqlite " und "System.Data " aus:
Klicken Sie auf die Schaltfläche "OK ", um Ihre Änderungen zu speichern und die Verweise hinzuzufügen.
Ändern des Datenmodells
Da wir nun Unterstützung für den direkten Zugriff auf eine SQLite-Datenbank zu unserer Anwendung hinzugefügt haben, müssen wir unser Datenmodellobjekt ändern, um Daten aus der Datenbank zu lesen und zu schreiben (sowie Schlüsselwertcodierung und Datenbindung bereitzustellen). Im Fall unserer Beispielanwendung bearbeiten wir die PersonModel.cs Klasse und machen sie wie folgt aussehen:
using System;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
#region Private Variables
private string _ID = "";
private string _managerID = "";
private string _name = "";
private string _occupation = "";
private bool _isManager = false;
private NSMutableArray _people = new NSMutableArray();
private SqliteConnection _conn = null;
#endregion
#region Computed Properties
public SqliteConnection Conn {
get { return _conn; }
set { _conn = value; }
}
[Export("ID")]
public string ID {
get { return _ID; }
set {
WillChangeValue ("ID");
_ID = value;
DidChangeValue ("ID");
}
}
[Export("ManagerID")]
public string ManagerID {
get { return _managerID; }
set {
WillChangeValue ("ManagerID");
_managerID = value;
DidChangeValue ("ManagerID");
}
}
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isEmployee")]
public bool isEmployee {
get { return (NumberOfEmployees == 0); }
}
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
[Export("NumberOfEmployees")]
public nint NumberOfEmployees {
get { return (nint)_people.Count; }
}
#endregion
#region Constructors
public PersonModel ()
{
}
public PersonModel (string name, string occupation)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (string name, string occupation, bool manager)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
this.isManager = manager;
}
public PersonModel (string id, string name, string occupation)
{
// Initialize
this.ID = id;
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (SqliteConnection conn, string id)
{
// Load from database
Load (conn, id);
}
#endregion
#region Array Controller Methods
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
#endregion
#region SQLite Routines
public void Create(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Create new record ID?
if (ID == "") {
ID = Guid.NewGuid ().ToString();
}
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
// Save last connection
_conn = conn;
}
public void Update(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
// Save last connection
_conn = conn;
}
public void Load(SqliteConnection conn, string id) {
bool shouldClose = false;
// Clear last connection to prevent circular call to update
_conn = null;
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
// Execute query
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT * FROM [People] WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
}
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
// Save last connection
_conn = conn;
}
public void Delete(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
// Save last connection
_conn = conn;
}
#endregion
}
}
Sehen wir uns die Änderungen im Detail unten an.
Zunächst haben wir mehrere using-Anweisungen hinzugefügt, die erforderlich sind, um SQLite zu verwenden, und wir haben eine Variable hinzugefügt, um unsere letzte Verbindung mit der SQLite-Datenbank zu speichern:
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection _conn = null;
Wir verwenden diese gespeicherte Verbindung, um änderungen am Datensatz automatisch in der Datenbank zu speichern, wenn der Benutzer den Inhalt in der Benutzeroberfläche per Datenbindung ändert:
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
Alle Änderungen, die an den Eigenschaften Name, Beruf oder isManager vorgenommen wurden, werden an die Datenbank gesendet, wenn die Daten dort gespeichert wurden (z. B. wenn die _conn
Variable nicht null
vorhanden ist). Als Nächstes befassen wir uns mit den Methoden, die wir zum Erstellen, Aktualisieren, Laden und Löschen von Personen aus der Datenbank hinzugefügt haben.
Neuen Datensatz erstellen
Der folgende Code wurde hinzugefügt, um einen neuen Datensatz in der SQLite-Datenbank zu erstellen:
public void Create(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Create new record ID?
if (ID == "") {
ID = Guid.NewGuid ().ToString();
}
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
// Save last connection
_conn = conn;
}
Wir verwenden einen SQLiteCommand
, um den neuen Datensatz in der Datenbank zu erstellen. Wir erhalten einen neuen Befehl aus dem SQLiteConnection
(conn), den wir durch Aufrufen CreateCommand
der Methode an die Methode übergeben haben. Als Nächstes legen wir die SQL-Anweisung so fest, dass der neue Datensatz tatsächlich geschrieben wird und Parameter für die tatsächlichen Werte bereitgestellt werden:
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
Später legen wir die Werte für die Parameter mithilfe der Parameters.AddWithValue
Methode für die SQLiteCommand
. Mithilfe von Parametern stellen wir sicher, dass Werte (z. B. ein einzelnes Anführungszeichen) ordnungsgemäß codiert werden, bevor sie an SQLite gesendet werden. Beispiel:
command.Parameters.AddWithValue ("@COL1", ID);
Da eine Person ein Vorgesetzter sein kann und eine Sammlung von Mitarbeitern darunter hat, rufen Create
wir die Methode für diese Personen rekursiv auf, um sie auch in der Datenbank zu speichern:
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
Aktualisieren eines Datensatzes
Der folgende Code wurde hinzugefügt, um einen vorhandenen Datensatz in der SQLite-Datenbank zu aktualisieren:
public void Update(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
// Save last connection
_conn = conn;
}
Wie oben erstellen erhalten wir eine SQLiteCommand
von der übergebenen SQLiteConnection
Daten und legen unsere SQL so fest, dass unser Datensatz aktualisiert wird (Parameter bereitstellen):
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
Wir füllen die Parameterwerte aus (Beispiel: command.Parameters.AddWithValue ("@COL1", ID);
) und erneut, rufen rekursiv aktualisierungen für alle untergeordneten Datensätze auf:
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
Laden eines Datensatzes
Der folgende Code wurde hinzugefügt, um einen vorhandenen Datensatz aus der SQLite-Datenbank zu laden:
public void Load(SqliteConnection conn, string id) {
bool shouldClose = false;
// Clear last connection to prevent circular call to update
_conn = null;
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
// Execute query
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT * FROM [People] WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
}
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
// Save last connection
_conn = conn;
}
Da die Routine rekursiv von einem übergeordneten Objekt aufgerufen werden kann (z. B. ein Managerobjekt, das das Mitarbeiterobjekt lädt), wurde spezieller Code zum Behandeln des Öffnens und Schließens der Verbindung mit der Datenbank hinzugefügt:
bool shouldClose = false;
...
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
...
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
Wie immer legen wir unsere SQL so fest, dass der Datensatz abgerufen und Parameter verwendet werden:
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
Schließlich verwenden wir einen Datenleser, um die Abfrage auszuführen und die Datensatzfelder zurückzugeben (die wir in die Instanz der PersonModel
Klasse kopieren):
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
Wenn diese Person ein Vorgesetzter ist, müssen wir auch alle ihre Mitarbeiter laden (durch rekursives Aufrufen ihrer Load
Methode):
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
Löschen eines Datensatzes
Der folgende Code wurde hinzugefügt, um einen vorhandenen Datensatz aus der SQLite-Datenbank zu löschen:
public void Delete(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
// Save last connection
_conn = conn;
}
Hier stellen wir die SQL bereit, um sowohl den Datensatz der Vorgesetzten als auch die Datensätze aller Mitarbeiter unter diesem Vorgesetzten zu löschen (mit Parametern):
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
Nachdem der Datensatz entfernt wurde, löschen wir die aktuelle Instanz der PersonModel
Klasse:
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
Initialisieren der Datenbank
Da die Änderungen an unserem Datenmodell vorhanden sind, um das Lesen und Schreiben in die Datenbank zu unterstützen, müssen wir eine Verbindung mit der Datenbank öffnen und bei der ersten Ausführung initialisieren. Fügen wir nun den folgenden Code zu unserer MainWindow.cs Datei hinzu:
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection DatabaseConnection = null;
...
private SqliteConnection GetDatabaseConnection() {
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "People.db3");
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
if (!exists)
SqliteConnection.CreateFile (db);
// Create connection to the database
var conn = new SqliteConnection("Data Source=" + db);
// Set the structure of the database
if (!exists) {
var commands = new[] {
"CREATE TABLE People (ID TEXT, Name TEXT, Occupation TEXT, isManager BOOLEAN, ManagerID TEXT)"
};
conn.Open ();
foreach (var cmd in commands) {
using (var c = conn.CreateCommand()) {
c.CommandText = cmd;
c.CommandType = CommandType.Text;
c.ExecuteNonQuery ();
}
}
conn.Close ();
// Build list of employees
var Craig = new PersonModel ("0","Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Craig.Create (conn);
var Larry = new PersonModel ("1","Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documentor"));
Larry.Create (conn);
}
// Return new connection
return conn;
}
Sehen wir uns den obigen Code genauer an. Zuerst wählen wir einen Speicherort für die neue Datenbank (in diesem Beispiel desktop des Benutzers) aus, um festzustellen, ob die Datenbank vorhanden ist, und falls dies nicht der Fall ist, erstellen Sie ihn:
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "People.db3");
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
if (!exists)
SqliteConnection.CreateFile (db);
Als Nächstes richten wir die Verbindung mit der Datenbank mithilfe des oben erstellten Pfads ein:
var conn = new SqliteConnection("Data Source=" + db);
Anschließend erstellen wir alle SQL-Tabellen in der Datenbank, die wir benötigen:
var commands = new[] {
"CREATE TABLE People (ID TEXT, Name TEXT, Occupation TEXT, isManager BOOLEAN, ManagerID TEXT)"
};
conn.Open ();
foreach (var cmd in commands) {
using (var c = conn.CreateCommand()) {
c.CommandText = cmd;
c.CommandType = CommandType.Text;
c.ExecuteNonQuery ();
}
}
conn.Close ();
Schließlich verwenden wir unser Datenmodell (PersonModel
), um einen Standardsatz von Datensätzen für die Datenbank zu erstellen, wenn die Anwendung zum ersten Mal ausgeführt wird oder wenn die Datenbank fehlt:
// Build list of employees
var Craig = new PersonModel ("0","Craig Dunn", "Documentation Manager");
Craig.AddPerson (new PersonModel ("Amy Burns", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Joel Martinez", "Web & Infrastructure"));
Craig.AddPerson (new PersonModel ("Kevin Mullins", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Mark McLemore", "Technical Writer"));
Craig.AddPerson (new PersonModel ("Tom Opgenorth", "Technical Writer"));
Craig.Create (conn);
var Larry = new PersonModel ("1","Larry O'Brien", "API Documentation Manager");
Larry.AddPerson (new PersonModel ("Mike Norman", "API Documentor"));
Larry.Create (conn);
Wenn die Anwendung gestartet und das Hauptfenster geöffnet wird, stellen wir mit dem oben hinzugefügten Code eine Verbindung mit der Datenbank bereit:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get access to database
DatabaseConnection = GetDatabaseConnection ();
}
Laden gebundener Daten
Da alle Komponenten für den direkten Zugriff auf gebundene Daten aus einer SQLite-Datenbank vorhanden sind, können wir die Daten in den verschiedenen Ansichten laden, die unsere Anwendung bereitstellt, und sie wird automatisch in unserer Benutzeroberfläche angezeigt.
Laden eines einzelnen Datensatzes
Um einen einzelnen Datensatz zu laden, in dem die ID bekannt ist, können wir den folgenden Code verwenden:
Person = new PersonModel (Conn, "0");
Alle Datensätze werden geladen.
Um alle Personen zu laden, unabhängig davon, ob sie ein Manager sind oder nicht, verwenden Sie den folgenden Code:
// Load all employees
_conn.Open ();
using (var command = _conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People]";
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (_conn, childID);
AddPerson (person);
}
}
}
_conn.Close ();
Hier verwenden wir eine Überladung des Konstruktors für die Klasse, um die PersonModel
Person in den Arbeitsspeicher zu laden:
var person = new PersonModel (_conn, childID);
Wir rufen auch die Data Bound-Klasse auf, um die Person zu unserer Sammlung von Personen AddPerson (person)
hinzuzufügen. Dadurch wird sichergestellt, dass unsere Benutzeroberfläche die Änderung erkennt und anzeigt:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
Nur Datensätze der obersten Ebene werden geladen
Zum Laden von Managern (z. B. zum Anzeigen von Daten in einer Gliederungsansicht) verwenden wir den folgenden Code:
// Load only managers employees
_conn.Open ();
using (var command = _conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1";
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (_conn, childID);
AddPerson (person);
}
}
}
_conn.Close ();
Der einzige echte Unterschied in der SQL-Anweisung (die nur Manager command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1"
lädt), funktioniert aber genauso wie der obige Abschnitt.
Datenbanken und Kombinationsboxen
Die menüsteuerelemente, die für macOS (z. B. das Kombinationsfeld) verfügbar sind, können so festgelegt werden, dass die Dropdownliste entweder aus einer internen Liste (die im Schnittstellen-Generator vordefiniert oder über Code aufgefüllt werden kann) oder indem Sie eine eigene benutzerdefinierte, externe Datenquelle bereitstellen. Weitere Details finden Sie unter Bereitstellen von Menüsteuerelementdaten .
Bearbeiten Sie beispielsweise das obige Beispiel für einfache Bindung im Schnittstellen-Generator, fügen Sie ein Kombinationsfeld hinzu, und machen Sie es mit einer Steckdose mit dem Namen verfügbar EmployeeSelector
:
Überprüfen Sie im Attributes Inspector die Eigenschaften "AutoVervollständigen " und verwendet Die Datenquelleneigenschaften :
Speichern Sie Ihre Änderungen, und kehren Sie zum synchronisierenden Visual Studio für Mac zurück.
Bereitstellen von Kombinationsfelddaten
Fügen Sie als Nächstes eine neue Klasse zum aufgerufenen ComboBoxDataSource
Projekt hinzu, und sehen Sie wie folgt aus:
using System;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
public class ComboBoxDataSource : NSComboBoxDataSource
{
#region Private Variables
private SqliteConnection _conn = null;
private string _tableName = "";
private string _IDField = "ID";
private string _displayField = "";
private nint _recordCount = 0;
#endregion
#region Computed Properties
public SqliteConnection Conn {
get { return _conn; }
set { _conn = value; }
}
public string TableName {
get { return _tableName; }
set {
_tableName = value;
_recordCount = GetRecordCount ();
}
}
public string IDField {
get { return _IDField; }
set {
_IDField = value;
_recordCount = GetRecordCount ();
}
}
public string DisplayField {
get { return _displayField; }
set {
_displayField = value;
_recordCount = GetRecordCount ();
}
}
public nint RecordCount {
get { return _recordCount; }
}
#endregion
#region Constructors
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
public ComboBoxDataSource (SqliteConnection conn, string tableName, string idField, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.IDField = idField;
this.DisplayField = displayField;
}
#endregion
#region Private Methods
private nint GetRecordCount ()
{
bool shouldClose = false;
nint count = 0;
// Has a Table, ID and display field been specified?
if (TableName !="" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT count({IDField}) FROM [{TableName}]";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read count from query
var result = (long)reader [0];
count = (nint)result;
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return the number of records
return count;
}
#endregion
#region Public Methods
public string IDForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public string ValueForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public string IDForValue (string value)
{
NSString result = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] WHERE {DisplayField} = @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", value);
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
result = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return result;
}
#endregion
#region Override Methods
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
public override NSObject ObjectValueForItem (NSComboBox comboBox, nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
public override nint IndexOfItem (NSComboBox comboBox, string value)
{
bool shouldClose = false;
bool found = false;
string field = "";
nint index = NSRange.NotFound;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read () && !found) {
// Read the display field from the query
field = (string)reader [0];
++index;
// Is this the value we are searching for?
if (value == field) {
// Yes, exit loop
found = true;
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return index;
}
public override string CompletedString (NSComboBox comboBox, string uncompletedString)
{
bool shouldClose = false;
bool found = false;
string field = "";
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Escape search string
uncompletedString = uncompletedString.Replace ("'", "");
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] WHERE {DisplayField} LIKE @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", uncompletedString + "%");
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
field = (string)reader [0];
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return field;
}
#endregion
}
}
In diesem Beispiel erstellen wir eine neue NSComboBoxDataSource
, die Kombinationsfeldelemente aus einer beliebigen SQLite-Datenquelle darstellen kann. Zunächst definieren wir die folgenden Eigenschaften:
Conn
– Ruft eine Verbindung mit der SQLite-Datenbank ab oder legt sie fest.TableName
- Ruft den Tabellennamen ab oder legt den Namen fest.IDField
- Ruft das Feld ab, das die eindeutige ID für die angegebene Tabelle bereitstellt, oder legt dieses fest. Der Standardwert istID
.DisplayField
– Ruft das Feld ab, das in der Dropdownliste angezeigt wird, oder legt es fest.RecordCount
- Ruft die Anzahl der Datensätze in der angegebenen Tabelle ab.
Wenn wir eine neue Instanz des Objekts erstellen, übergeben wir die Verbindung, den Tabellennamen, optional das ID-Feld und das Anzeigefeld:
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
Die GetRecordCount
Methode gibt die Anzahl der Datensätze in der angegebenen Tabelle zurück:
private nint GetRecordCount ()
{
bool shouldClose = false;
nint count = 0;
// Has a Table, ID and display field been specified?
if (TableName !="" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT count({IDField}) FROM [{TableName}]";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read count from query
var result = (long)reader [0];
count = (nint)result;
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return the number of records
return count;
}
Sie wird immer dann aufgerufen, IDField
wenn der Wert der TableName
DisplayField
Eigenschaften geändert wird.
Die IDForIndex
Methode gibt die eindeutige ID (IDField
) für den Datensatz im angegebenen Dropdownlistenelementindex zurück:
public string IDForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
Die ValueForIndex
Methode gibt den Wert (DisplayField
) für das Element im angegebenen Dropdownlistenindex zurück:
public string ValueForIndex (nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
Die IDForValue
Methode gibt die eindeutige ID () für den angegebenen Wert zurückIDField
(DisplayField
):
public string IDForValue (string value)
{
NSString result = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] WHERE {DisplayField} = @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", value);
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
result = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return result;
}
Gibt ItemCount
die vorkompilierte Anzahl von Elementen in der Liste zurück, wie berechnet, wenn die TableName
Eigenschaften IDField
DisplayField
geändert werden:
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
Die ObjectValueForItem
Methode stellt den Wert (DisplayField
) für den angegebenen Dropdownlistenelementindex bereit:
public override NSObject ObjectValueForItem (NSComboBox comboBox, nint index)
{
NSString value = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC LIMIT 1 OFFSET {index}";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
value = new NSString((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return value;
}
Beachten Sie, dass wir die Anweisungen und OFFSET
Anweisungen LIMIT
in unserem SQLite-Befehl verwenden, um auf den einzigen Datensatz zu beschränken, den wir benötigen.
Die IndexOfItem
Methode gibt den Dropdownelementindex des angegebenen Werts (DisplayField
) zurück:
public override nint IndexOfItem (NSComboBox comboBox, string value)
{
bool shouldClose = false;
bool found = false;
string field = "";
nint index = NSRange.NotFound;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] ORDER BY {DisplayField} ASC";
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read () && !found) {
// Read the display field from the query
field = (string)reader [0];
++index;
// Is this the value we are searching for?
if (value == field) {
// Yes, exit loop
found = true;
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return index;
}
Wenn der Wert nicht gefunden werden kann, wird der NSRange.NotFound
Wert zurückgegeben, und alle Elemente werden in der Dropdownliste deaktiviert.
Die CompletedString
Methode gibt den ersten übereinstimmenden Wert (DisplayField
) für einen teilweise eingegebenen Eintrag zurück:
public override string CompletedString (NSComboBox comboBox, string uncompletedString)
{
bool shouldClose = false;
bool found = false;
string field = "";
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Escape search string
uncompletedString = uncompletedString.Replace ("'", "");
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {DisplayField} FROM [{TableName}] WHERE {DisplayField} LIKE @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", uncompletedString + "%");
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
field = (string)reader [0];
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return field;
}
Anzeigen von Daten und Reagieren auf Ereignisse
Wenn Sie alle Teile zusammenführen möchten, bearbeiten Sie sie SubviewSimpleBindingController
, und gestalten Sie sie wie folgt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
public partial class SubviewSimpleBindingController : AppKit.NSViewController
{
#region Private Variables
private PersonModel _person = new PersonModel();
private SqliteConnection Conn;
#endregion
#region Computed Properties
//strongly typed view accessor
public new SubviewSimpleBinding View {
get {
return (SubviewSimpleBinding)base.View;
}
}
[Export("Person")]
public PersonModel Person {
get {return _person; }
set {
WillChangeValue ("Person");
_person = value;
DidChangeValue ("Person");
}
}
public ComboBoxDataSource DataSource {
get { return EmployeeSelector.DataSource as ComboBoxDataSource; }
}
#endregion
#region Constructors
// Called when created from unmanaged code
public SubviewSimpleBindingController (IntPtr handle) : base (handle)
{
Initialize ();
}
// Called when created directly from a XIB file
[Export ("initWithCoder:")]
public SubviewSimpleBindingController (NSCoder coder) : base (coder)
{
Initialize ();
}
// Call to load from the XIB/NIB file
public SubviewSimpleBindingController (SqliteConnection conn) : base ("SubviewSimpleBinding", NSBundle.MainBundle)
{
// Initialize
this.Conn = conn;
Initialize ();
}
// Shared initialization code
void Initialize ()
{
}
#endregion
#region Private Methods
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
#endregion
#region Override Methods
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Configure Employee selector dropdown
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
// Wireup events
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
}
#endregion
}
}
Die DataSource
Eigenschaft stellt eine Verknüpfung mit dem ComboBoxDataSource
oben erstellten Kombinationsfeld bereit.
Die LoadSelectedPerson
Methode lädt die Person aus der Datenbank für die angegebene eindeutige ID:
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
Bei der AwakeFromNib
Methodenüberschreibung fügen wir zuerst eine Instanz unserer benutzerdefinierten Kombinationsfeld-Datenquelle an:
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
Als Nächstes antworten wir darauf, dass der Benutzer den Textwert des Kombinationsfelds bearbeitet, indem wir die zugeordnete eindeutige ID (IDField
) der Daten suchen, die präsentiert werden, und die angegebene Person wird geladen, wenn sie gefunden wurde:
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
Außerdem wird eine neue Person geladen, wenn der Benutzer ein neues Element aus der Dropdownliste auswählt:
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
Schließlich füllen wir das Kombinationsfeld automatisch auf und zeigen die Person mit dem ersten Element in der Liste an:
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
SQLite.NET ORM
Wie bereits erwähnt, können wir mit dem Open Source-SQLite.NET Object Relationship Manager (ORM) die Menge an Code erheblich reduzieren, der zum Lesen und Schreiben von Daten aus einer SQLite-Datenbank erforderlich ist. Dies ist möglicherweise nicht die beste Route, die beim Binden von Daten verwendet werden kann, da mehrere Anforderungen, die die Schlüsselwertcodierung und Datenbindung auf einem Objekt platzieren.
Laut der SQLite.Net Website ist SQLite eine Softwarebibliothek, die ein eigenständiges, serverloses, nullkonfigurationsloses, transaktionsbasiertes SQL-Datenbankmodul implementiert. SQLite ist das am häufigsten bereitgestellte Datenbankmodul der Welt. Der Quellcode für SQLite befindet sich in der öffentlichen Do Standard."
In den folgenden Abschnitten wird gezeigt, wie Sie SQLite.Net verwenden, um Daten für eine Tabellenansicht bereitzustellen.
Einschließen des SQLite.net NuGet
SQLite.NET wird als NuGet-Paket dargestellt, das Sie in Ihre Anwendung einschließen. Bevor wir mithilfe von SQLite.NET Datenbankunterstützung hinzufügen können, müssen wir dieses Paket einschließen.
Gehen Sie wie folgt vor, um das Paket hinzuzufügen:
Klicken Sie im Lösungskreuz mit der rechten Maustaste auf den Ordner "Pakete", und wählen Sie "Pakete hinzufügen" aus...
Geben Sie
SQLite.net
das Suchfeld ein, und wählen Sie den sqlite-net-Eintrag aus:Klicken Sie auf die Schaltfläche "Paket hinzufügen", um den Vorgang abzuschließen.
Erstellen des Datenmodells
Fügen wir dem Projekt eine neue Klasse hinzu und rufen sie an OccupationModel
. Als Nächstes bearbeiten wir die OccupationModel.cs Datei und lassen Sie sie wie folgt aussehen:
using System;
using SQLite;
namespace MacDatabase
{
public class OccupationModel
{
#region Computed Properties
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set;}
public string Description { get; set;}
#endregion
#region Constructors
public OccupationModel ()
{
}
public OccupationModel (string name, string description)
{
// Initialize
this.Name = name;
this.Description = description;
}
#endregion
}
}
Zunächst schließen wir SQLite.NET (using Sqlite
) ein und stellen dann mehrere Eigenschaften zur Verfügung, von denen jedes in die Datenbank geschrieben wird, wenn dieser Datensatz gespeichert wird. Die erste Eigenschaft, die wir als Primärschlüssel erstellen und festlegen, dass sie wie folgt automatisch erhöht wird:
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
Initialisieren der Datenbank
Da die Änderungen an unserem Datenmodell vorhanden sind, um das Lesen und Schreiben in die Datenbank zu unterstützen, müssen wir eine Verbindung mit der Datenbank öffnen und bei der ersten Ausführung initialisieren. Fügen wir den folgenden Code hinzu:
using SQLite;
...
public SQLiteConnection Conn { get; set; }
...
private SQLiteConnection GetDatabaseConnection() {
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "Occupation.db3");
OccupationModel Occupation;
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
// Create connection to database
var conn = new SQLiteConnection (db);
// Initially populate table?
if (!exists) {
// Yes, build table
conn.CreateTable<OccupationModel> ();
// Add occupations
Occupation = new OccupationModel ("Documentation Manager", "Manages the Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Technical Writer", "Writes technical documentation and sample applications");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Web & Infrastructure", "Creates and maintains the websites that drive documentation");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documentation Manager", "Manages the API Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documenter", "Creates and maintains API documentation");
conn.Insert (Occupation);
}
return conn;
}
Zunächst wird ein Pfad zur Datenbank (in diesem Fall desktop des Benutzers) abgerufen und überprüft, ob die Datenbank bereits vorhanden ist:
var documents = Environment.GetFolderPath (Environment.SpecialFolder.Desktop);
string db = Path.Combine (documents, "Occupation.db3");
OccupationModel Occupation;
// Create the database if it doesn't already exist
bool exists = File.Exists (db);
Als Nächstes richten wir eine Verbindung mit der Datenbank am oben erstellten Pfad ein:
var conn = new SQLiteConnection (db);
Schließlich erstellen wir die Tabelle und fügen einige Standardeinträge hinzu:
// Yes, build table
conn.CreateTable<OccupationModel> ();
// Add occupations
Occupation = new OccupationModel ("Documentation Manager", "Manages the Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Technical Writer", "Writes technical documentation and sample applications");
conn.Insert (Occupation);
Occupation = new OccupationModel ("Web & Infrastructure", "Creates and maintains the websites that drive documentation");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documentation Manager", "Manages the API Documentation Group");
conn.Insert (Occupation);
Occupation = new OccupationModel ("API Documenter", "Creates and maintains API documentation");
conn.Insert (Occupation);
Hinzufügen einer Tabellenansicht
Als Beispiel für die Verwendung fügen wir unserer Benutzeroberfläche im Benutzeroberflächen-Generator von Xcode eine Tabellenansicht hinzu. Wir stellen diese Tabellenansicht über eine Steckdose (OccupationTable
) zur Verfügung, damit wir über C#-Code darauf zugreifen können:
Als Nächstes fügen wir die benutzerdefinierten Klassen hinzu, um diese Tabelle mit Daten aus der SQLite.NET-Datenbank aufzufüllen.
Erstellen der Tabellendatenquelle
Erstellen wir nun eine benutzerdefinierte Datenquelle, um Daten für unsere Tabelle bereitzustellen. Fügen Sie zunächst eine neue Klasse hinzu, die aufgerufen wird TableORMDatasource
, und lassen Sie sie wie folgt aussehen:
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
using SQLite;
namespace MacDatabase
{
public class TableORMDatasource : NSTableViewDataSource
{
#region Computed Properties
public List<OccupationModel> Occupations { get; set;} = new List<OccupationModel>();
public SQLiteConnection Conn { get; set; }
#endregion
#region Constructors
public TableORMDatasource (SQLiteConnection conn)
{
// Initialize
this.Conn = conn;
LoadOccupations ();
}
#endregion
#region Public Methods
public void LoadOccupations() {
// Get occupations from database
var query = Conn.Table<OccupationModel> ();
// Copy into table collection
Occupations.Clear ();
foreach (OccupationModel occupation in query) {
Occupations.Add (occupation);
}
}
#endregion
#region Override Methods
public override nint GetRowCount (NSTableView tableView)
{
return Occupations.Count;
}
#endregion
}
}
Wenn wir später eine Instanz dieser Klasse erstellen, übergeben wir unsere geöffnete SQLite.NET Datenbankverbindung. Die LoadOccupations
Methode fragt die Datenbank ab und kopiert die gefundenen Datensätze in den Arbeitsspeicher (mit unserem OccupationModel
Datenmodell).
Erstellen des Tabellendelegats
Die letzte Klasse, die wir benötigen, ist ein benutzerdefinierter Tabellendelegat, um die Informationen anzuzeigen, die wir aus der SQLite.NET-Datenbank geladen haben. Fügen wir ein neues TableORMDelegate
Zu unserem Projekt hinzu und lassen Sie es wie folgt aussehen:
using System;
using AppKit;
using CoreGraphics;
using Foundation;
using System.Collections;
using System.Collections.Generic;
using SQLite;
namespace MacDatabase
{
public class TableORMDelegate : NSTableViewDelegate
{
#region Constants
private const string CellIdentifier = "OccCell";
#endregion
#region Private Variables
private TableORMDatasource DataSource;
#endregion
#region Constructors
public TableORMDelegate (TableORMDatasource dataSource)
{
// Initialize
this.DataSource = dataSource;
}
#endregion
#region Override Methods
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTextField view = (NSTextField)tableView.MakeView (CellIdentifier, this);
if (view == null) {
view = new NSTextField ();
view.Identifier = CellIdentifier;
view.BackgroundColor = NSColor.Clear;
view.Bordered = false;
view.Selectable = false;
view.Editable = false;
}
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Occupation":
view.StringValue = DataSource.Occupations [(int)row].Name;
break;
case "Description":
view.StringValue = DataSource.Occupations [(int)row].Description;
break;
}
return view;
}
#endregion
}
}
Hier verwenden wir die Sammlung der Datenquelle Occupations
(die wir aus der SQLite.NET-Datenbank geladen haben), um die Spalten unserer Tabelle über die GetViewForItem
Methodenüberschreibung auszufüllen.
Auffüllen der Tabelle
Wenn alle Teile vorhanden sind, füllen wir unsere Tabelle auf, wenn sie aus der XIB-Datei aufgeblasen wird, indem wir die AwakeFromNib
Methode überschreiben und so aussehen lassen:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get database connection
Conn = GetDatabaseConnection ();
// Create the Occupation Table Data Source and populate it
var DataSource = new TableORMDatasource (Conn);
// Populate the Product Table
OccupationTable.DataSource = DataSource;
OccupationTable.Delegate = new TableORMDelegate (DataSource);
}
Zunächst erhalten wir Zugriff auf unsere SQLite.NET-Datenbank, erstellen und auffüllen, wenn sie noch nicht vorhanden ist. Als Nächstes erstellen wir eine neue Instanz unserer benutzerdefinierten Tabellendatenquelle, übergeben unsere Datenbankverbindung und fügen sie an die Tabelle an. Schließlich erstellen wir eine neue Instanz unseres benutzerdefinierten Tabellendelegats, übergeben unsere Datenquelle und fügen sie an die Tabelle an.
Zusammenfassung
In diesem Artikel wird die Arbeit mit datenbindung und Key-Value-Codierung mit SQLite-Datenbanken in einer Xamarin.Mac-Anwendung ausführlich erläutert. Zunächst wurde eine C#-Klasse Objective-C mithilfe von Schlüsselwertcodierung (Key-Value Coding, KVC) und Key-Value Observing (KVO) bereitgestellt. Als Nächstes wurde gezeigt, wie sie eine KVO-kompatible Klasse und Datenbindung an UI-Elemente im Schnittstellen-Generator von Xcode verwenden. Der Artikel behandelte auch das Arbeiten mit SQLite-Daten über das SQLite.NET ORM und das Anzeigen dieser Daten in einer Tabellenansicht.
Verwandte Links
- Hello, Mac (Hallo, Mac)
- Datenbindung und Schlüsselwertcodierung
- Standardsteuerelemente
- Tabellenansichten
- Gliederungsansichten
- Sammlungsansichten
- Programmierhandbuch zur Schlüsselwertcodierung
- Einführung in Die Programmierungsthemen zu Kakaobindungen
- Einführung in die Cocoa Bindings-Referenz
- NSCollectionView
- macOS-Eingaberichtlinien