Databáze v Xamarin.Mac
Tento článek popisuje použití kódování klíč-hodnota a klíč-hodnota pozorování umožňující datovou vazbu mezi databázemi SQLite a prvky uživatelského rozhraní v Xcode Interface Builder. Popisuje také použití SQLite.NET ORM k poskytování přístupu k datům SQLite.
Přehled
Při práci s C# a .NET v aplikaci Xamarin.Mac máte přístup ke stejným databázím SQLite, ke kterým má aplikace Xamarin.iOS nebo Xamarin.Android přístup.
V tomto článku se podíváme na dva způsoby přístupu k datům SQLite:
- Přímý přístup – přímým přístupem k databázi SQLite můžeme použít data z databáze pro kódování klíč-hodnota a datovou vazbu pomocí prvků uživatelského rozhraní vytvořených v Tvůrci rozhraní Xcode. Pomocí technik kódování klíč-hodnota a datových vazeb v aplikaci Xamarin.Mac můžete výrazně snížit množství kódu, který musíte psát a udržovat pro naplnění a práci s prvky uživatelského rozhraní. Výhodou je také další oddělení backingových dat (datový model) od front-endového uživatelského rozhraní (Model-View-Controller), což usnadňuje údržbu a flexibilnější návrh aplikace.
- SQLite.NET ORM – Pomocí opensourcového SQLite.NET Správce vztahů objektů (ORM) můžeme výrazně snížit množství kódu potřebného ke čtení a zápisu dat z databáze SQLite. Tato data se pak dají použít k naplnění položky uživatelského rozhraní, jako je zobrazení tabulky.
V tomto článku se budeme zabývat základy práce s kódováním klíč-hodnota a datovou vazbou s databázemi SQLite v aplikaci Xamarin.Mac. Důrazně doporučujeme, abyste si nejprve prošli článek Hello, Mac , konkrétně úvod do Xcode a Tvůrce rozhraní a výstupy a akce , protože se zabývá klíčovými koncepty a technikami, které budeme používat v tomto článku.
Vzhledem k tomu, že budeme používat kódování klíč-hodnota a datovou vazbu, projděte si nejprve kód datové vazby a klíč-hodnota, protože základní techniky a koncepty budou popsány v této dokumentaci a její ukázkové aplikaci.
Možná se budete chtít podívat také na třídy a metody v jazyce C#, které jsou uvedeny v Objective-C dokumentu Xamarin.Mac Internals , vysvětluje Register
Export
a atributy používané k připojení tříd jazyka C# k Objective-C objektům a prvkům uživatelského rozhraní.
Přímý přístup SQLite
U dat SQLite, která budou svázaná s prvky uživatelského rozhraní v Tvůrci rozhraní Xcode, se důrazně doporučuje, abyste přistupovali k databázi SQLite přímo (na rozdíl od použití techniky, jako je NAPŘÍKLAD ORM), protože máte úplnou kontrolu nad tím, jak se data zapisují a čtou z databáze.
Jak jsme viděli v dokumentaci k kódování datových vazeb a klíč-hodnota pomocí technik kódování klíč-hodnota a datových vazeb v aplikaci Xamarin.Mac, můžete výrazně snížit množství kódu, který musíte napsat a udržovat pro naplnění a práci s prvky uživatelského rozhraní. V kombinaci s přímým přístupem k databázi SQLite může také výrazně snížit množství kódu potřebného ke čtení a zápisu dat do této databáze.
V tomto článku upravíme ukázkovou aplikaci z datové vazby a dokumentu kódování klíč-hodnota tak, aby jako záložní zdroj pro vazbu používala databázi SQLite.
Zahrnutí podpory databáze SQLite
Než budeme moci pokračovat, musíme přidat podporu databáze SQLite do naší aplikace zahrnutím odkazů na několik z . Soubory DLL.
Postupujte následovně:
V oblasti řešení klikněte pravým tlačítkem myši na složku Reference a vyberte Upravit odkazy.
Vyberte sestavení Mono.Data.Sqlite i System.Data:
Kliknutím na tlačítko OK uložte změny a přidejte odkazy.
Úprava datového modelu
Teď, když jsme přidali podporu pro přímý přístup k databázi SQLite do naší aplikace, potřebujeme upravit objekt datového modelu tak, aby četl a zapisuje data z databáze (a také poskytuje kódování klíč-hodnota a datovou vazbu). V případě ukázkové aplikace upravíme PersonModel.cs třídu a bude vypadat takto:
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
}
}
Pojďme se podrobněji podívat na úpravy níže.
Nejprve jsme přidali několik příkazů using, které jsou potřeba k použití SQLite, a přidali jsme proměnnou pro uložení posledního připojení k databázi SQLite:
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection _conn = null;
Toto uložené připojení použijeme k automatickému uložení všech změn záznamu do databáze, když uživatel upraví obsah v uživatelském rozhraní prostřednictvím datové vazby:
[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);
}
}
Všechny změny provedené ve vlastnostech Name, Occupation nebo isManager budou odeslány do databáze, pokud tam data byla uložena dříve (např. pokud _conn
proměnná není null
). Teď se podíváme na metody, které jsme přidali k vytvoření, aktualizaci, načtení a odstranění lidí z databáze.
Vytvořit nový záznam
Následující kód byl přidán pro vytvoření nového záznamu v databázi SQLite:
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;
}
K vytvoření nového záznamu v databázi používáme SQLiteCommand
. Získáme nový příkaz z SQLiteConnection
(conn), který jsme předali do metody voláním CreateCommand
. Dále nastavíme instrukci SQL tak, aby ve skutečnosti zapisuje nový záznam a poskytuje parametry pro skutečné hodnoty:
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
Později nastavíme hodnoty parametrů pomocí Parameters.AddWithValue
metody v objektu SQLiteCommand
. Pomocí parametrů zajistíme, aby se hodnoty (například jednoduché uvozovky) před odesláním do SQLite správně zakódovaly. Příklad:
command.Parameters.AddWithValue ("@COL1", ID);
A konečně, protože osoba může být manažerem a má pod sebou kolekci zaměstnanců, rekurzivně voláme metodu Create
pro tyto lidi, abychom je uložili do databáze také:
// 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);
}
Aktualizace záznamu
Následující kód byl přidán pro aktualizaci existujícího záznamu v databázi SQLite:
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;
}
Stejně jako výše uvedený příkaz Create získáme SQLiteCommand
od předání SQLiteConnection
a nastavíme SQL tak, aby aktualizoval náš záznam (poskytuje parametry):
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
Vyplníme hodnoty parametrů (příklad: command.Parameters.AddWithValue ("@COL1", ID);
) a znovu rekurzivně voláme aktualizaci u všech podřízených záznamů:
// 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);
}
Načtení záznamu
Následující kód byl přidán pro načtení existujícího záznamu z databáze SQLite:
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;
}
Vzhledem k tomu, že rutinu lze volat rekurzivně z nadřazeného objektu (například objekt manažera, který načítá objekt zaměstnanců), byl přidán speciální kód pro zpracování otevření a zavření připojení k databázi:
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 ();
}
Jako vždy nastavíme SQL tak, aby načetl záznam a použil parametry:
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
Nakonec pomocí čtečky dat spustíme dotaz a vrátíme pole záznamů (která zkopírujeme do instance PersonModel
třídy):
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];
}
}
Pokud je tato osoba manažerem, musíme také načíst všechny své zaměstnance (rekurzivně zavoláním metody Load
):
// 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);
}
}
}
}
Odstranění záznamu
Následující kód byl přidán pro odstranění existujícího záznamu z databáze SQLite:
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;
}
Tady poskytneme SQL k odstranění záznamů manažerů i záznamů všech zaměstnanců pod tímto manažerem (pomocí parametrů):
// 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);
Po odebrání záznamu vymažeme aktuální instanci PersonModel
třídy:
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
Inicializace databáze
Díky změnám datového modelu, které podporují čtení a zápis do databáze, musíme otevřít připojení k databázi a inicializovat ho při prvním spuštění. Pojďme do našeho souboru MainWindow.cs přidat následující kód:
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;
}
Pojďme se podrobněji podívat na výše uvedený kód. Nejprve vybereme umístění pro novou databázi (v tomto příkladu je to plocha uživatele), podíváme se, jestli databáze existuje, a pokud ne, vytvořte ji:
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);
Dále vytvoříme připojení k databázi pomocí cesty, kterou jsme vytvořili výše:
var conn = new SqliteConnection("Data Source=" + db);
Pak vytvoříme všechny tabulky SQL v databázi, které požadujeme:
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 ();
Nakonec použijeme datový model (PersonModel
) k vytvoření výchozí sady záznamů pro databázi při prvním spuštění aplikace nebo v případě chybějící databáze:
// 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);
Když se aplikace spustí a otevře hlavní okno, vytvoříme připojení k databázi pomocí kódu, který jsme přidali výše:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get access to database
DatabaseConnection = GetDatabaseConnection ();
}
Načítání vázaná data
Se všemi komponentami pro přímý přístup k vázaným datům z databáze SQLite můžeme načíst data v různých zobrazeních, která naše aplikace poskytuje, a automaticky se zobrazí v našem uživatelském rozhraní.
Načtení jednoho záznamu
K načtení jednoho záznamu, ve kterém je ID známé, můžeme použít následující kód:
Person = new PersonModel (Conn, "0");
Načítání všech záznamů
Pokud chcete načíst všechny osoby bez ohledu na to, jestli jsou manažerem nebo ne, použijte následující kód:
// 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 ();
V této části používáme přetížení konstruktoru, aby PersonModel
třída načetla osobu do paměti:
var person = new PersonModel (_conn, childID);
Voláme také třídu vázaná na data, která přidá osobu do naší kolekce lidí AddPerson (person)
, tím se zajistí, že naše uživatelské rozhraní rozpozná změnu a zobrazí ji:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
Načítání jenom záznamů nejvyšší úrovně
K načtení pouze správců (například k zobrazení dat v zobrazení osnovy) používáme následující kód:
// 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 ();
Jediný skutečný rozdíl v příkazu SQL (který načítá pouze správce command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1"
), ale funguje stejně jako výše uvedený oddíl.
Databáze a pole se seznamem
Ovládací prvky nabídky dostupné pro macOS (například pole se seznamem) je možné nastavit tak, aby se rozevírací seznam naplnil buď z interního seznamu (který lze předem definovat v Tvůrci rozhraní nebo naplnit kódem), nebo poskytnutím vlastního externího zdroje dat. Další podrobnosti najdete v tématu Poskytování řídicích dat nabídky.
Například upravte výše uvedený příklad jednoduché vazby v Tvůrci rozhraní, přidejte pole se seznamem a zpřístupňte ho pomocí výstupu s názvem EmployeeSelector
:
V kontrole atributů zkontrolujte automatické dokončování a používá vlastnosti zdroje dat:
Uložte změny a vraťte se do Visual Studio pro Mac k synchronizaci.
Poskytování dat pole se seznamem
V dalším kroku přidejte do volaný ComboBoxDataSource
projekt novou třídu a udělejte ji takto:
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
}
}
V tomto příkladu vytváříme novou NSComboBoxDataSource
položku pole se seznamem z libovolného zdroje dat SQLite. Nejprve definujeme následující vlastnosti:
Conn
– Získá nebo nastaví připojení k databázi SQLite.TableName
– Získá nebo nastaví název tabulky.IDField
– Získá nebo nastaví pole, které poskytuje jedinečné ID dané tabulky. Výchozí hodnota jeID
.DisplayField
– Získá nebo nastaví pole, které se zobrazí v rozevíracím seznamu.RecordCount
– Získá počet záznamů v dané tabulce.
Když vytvoříme novou instanci objektu, předáme připojení, název tabulky, volitelně pole ID a zobrazované pole:
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
Metoda GetRecordCount
vrátí počet záznamů v dané tabulce:
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;
}
Volá se pokaždé, IDField
když dojde ke TableName
změně hodnoty vlastnosti nebo DisplayField
vlastnosti.
Metoda IDForIndex
vrátí jedinečné ID (IDField
) záznamu v indexu dané položky rozevíracího seznamu:
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;
}
Metoda ValueForIndex
vrátí hodnotu (DisplayField
) položky v daném indexu rozevíracího seznamu:
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;
}
Metoda IDForValue
vrátí jedinečné ID (IDField
) pro danou hodnotu (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;
}
Vrátí ItemCount
předem vypočítaný počet položek v seznamu, které se počítají při TableName
IDField
změně vlastnosti , nebo DisplayField
vlastnosti:
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
Metoda ObjectValueForItem
poskytuje hodnotu (DisplayField
) pro daný index položek rozevíracího seznamu:
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;
}
Všimněte si, že používáme LIMIT
příkazy a OFFSET
příkazy v našem příkazu SQLite k omezení na jeden záznam, který potřebujeme.
Metoda IndexOfItem
vrátí index položky rozevíracího seznamu dané hodnoty (DisplayField
):
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;
}
Pokud hodnotu nelze najít, NSRange.NotFound
vrátí se hodnota a v rozevíracím seznamu se zruší výběr všech položek.
Metoda CompletedString
vrátí první odpovídající hodnotu (DisplayField
) pro částečně zadaná položka:
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;
}
Zobrazení dat a reakce na události
Pokud chcete všechny části spojit, upravte ho SubviewSimpleBindingController
a udělejte ho takto:
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
}
}
Vlastnost DataSource
poskytuje zástupce ComboBoxDataSource
(vytvořené výše) připojené k poli se seznamem.
Metoda LoadSelectedPerson
načte osobu z databáze pro dané jedinečné ID:
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
V přepsání AwakeFromNib
metody nejprve připojíme instanci vlastního zdroje dat Pole se seznamem:
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
Dále odpovíme uživateli, který upravuje textovou hodnotu pole se seznamem, vyhledáním přidruženého jedinečného ID (IDField
) dat, která jsou prezentována, a načtením dané osoby, pokud se najde:
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
Pokud uživatel vybere novou položku z rozevíracího seznamu, načteme také novou osobu:
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
Nakonec automaticky naplníme pole se seznamem a zobrazíme osobu první položkou v seznamu:
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
SQLite.NET ORM
Jak jsme uvedli výše, pomocí opensourcového SQLite.NET Správce relací objektů (ORM) můžeme výrazně snížit množství kódu potřebného ke čtení a zápisu dat z databáze SQLite. To nemusí být nejlepší trasou, která se má provést při vytváření vazeb dat z důvodu několika požadavků, které kódování klíč-hodnota a datové vazby umístí na objekt.
Podle webu SQLite.Net "SQLite je softwarová knihovna, která implementuje samostatně obsažené, bezserverové bezserverové, nulové konfigurace, transakční databázový stroj SQL. SQLite je nejrozšířenější databázový stroj na světě. Zdrojový kód pro SQLite je ve veřejné doméně."
V následujících částech si ukážeme, jak pomocí SQLite.Net poskytnout data pro zobrazení tabulky.
Zahrnutí SQLite.net NuGetu
SQLite.NET se zobrazí jako balíček NuGet, který zahrnete do aplikace. Než budeme moct přidat podporu databáze pomocí SQLite.NET, musíme tento balíček zahrnout.
Přidejte balíček následujícím postupem:
V oblasti řešení klikněte pravým tlačítkem na složku Balíčky a vyberte Přidat balíčky...
Zadejte
SQLite.net
do vyhledávacího pole a vyberte položku sqlite-net :Kliknutím na tlačítko Přidat balíček dokončete.
Vytvoření datového modelu
Pojďme do projektu přidat novou třídu a zavolat .OccupationModel
V dalším kroku upravíme soubor OccupationModel.cs a nastavíme ho takto:
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
}
}
Nejprve zahrneme SQLite.NET (using Sqlite
), pak zveřejníme několik vlastností, z nichž každý se při uložení tohoto záznamu zapíše do databáze. První vlastnost, která vytvoříme jako primární klíč, a nastavíme ji na automatické přírůstky následujícím způsobem:
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
Inicializace databáze
Díky změnám datového modelu, které podporují čtení a zápis do databáze, musíme otevřít připojení k databázi a inicializovat ho při prvním spuštění. Pojďme přidat následující kód:
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;
}
Nejprve získáme cestu k databázi (v tomto případě Desktop uživatele) a zjistíme, jestli databáze už existuje:
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);
Dále vytvoříme připojení k databázi na cestě, kterou jsme vytvořili výše:
var conn = new SQLiteConnection (db);
Nakonec vytvoříme tabulku a přidáme několik výchozích záznamů:
// 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);
Přidání zobrazení tabulky
Jako příklad použití přidáme zobrazení tabulky do uživatelského rozhraní v tvůrci rozhraní Xcode. Toto zobrazení tabulky zveřejníme prostřednictvím výstupu (OccupationTable
), abychom k němu měli přístup prostřednictvím kódu jazyka C#:
Dále přidáme vlastní třídy pro naplnění této tabulky daty z databáze SQLite.NET.
Vytvoření zdroje dat tabulky
Pojďme vytvořit vlastní zdroj dat, který bude poskytovat data pro naši tabulku. Nejprve přidejte volanou TableORMDatasource
novou třídu a nastavte ji tak, aby vypadala takto:
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
}
}
Když později vytvoříme instanci této třídy, předáme otevřené připojení k databázi SQLite.NET. Metoda LoadOccupations
dotazuje databázi a zkopíruje nalezené záznamy do paměti (pomocí našeho OccupationModel
datového modelu).
Vytvoření delegáta tabulky
Poslední třída, kterou potřebujeme, je vlastní delegát tabulky k zobrazení informací, které jsme načetli z SQLite.NET databáze. Pojďme do našeho projektu přidat nový TableORMDelegate
a vypadat takto:
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
}
}
Tady použijeme kolekci zdroje Occupations
dat (kterou jsme načetli z databáze SQLite.NET) k vyplnění sloupců naší tabulky prostřednictvím GetViewForItem
přepsání metody.
Naplnění tabulky
Se všemi částmi na místě naplníme naši tabulku, když se nafoukne ze souboru .xib přepsáním AwakeFromNib
metody a tím, že bude vypadat takto:
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);
}
Nejprve získáme přístup k naší databázi SQLite.NET a vytvoříme ji a naplníme, pokud ještě neexistuje. Dále vytvoříme novou instanci vlastního zdroje dat tabulky, předáme připojení k databázi a připojíme ji k tabulce. Nakonec vytvoříme novou instanci našeho vlastního delegáta tabulky, předáme zdroj dat a připojíme ji k tabulce.
Shrnutí
Tento článek se podrobně podíval na práci s datovými vazbami a kódováním klíč-hodnota s databázemi SQLite v aplikaci Xamarin.Mac. Nejprve se podívala na zveřejnění třídy Objective-C C# pomocí kódování klíč-hodnota (KVC) a pozorování klíč-hodnota (KVO). Dále ukázala, jak používat třídu kompatibilní s KVO a vytvořit vazbu dat s prvky uživatelského rozhraní v Tvůrci rozhraní Xcode. Článek také popisuje práci s daty SQLite prostřednictvím SQLite.NET ORM a zobrazení dat v zobrazení tabulky.
Související odkazy
- Hello, Mac
- Kódování datových vazeb a klíč-hodnota
- Standardní ovládací prvky
- Zobrazení tabulek
- Zobrazení osnovy
- Zobrazení kolekcí
- Průvodce programováním kódování klíč-hodnota
- Úvod k programovacím tématům týkajícím se vazeb Cocoa
- Úvod do vazeb Cocoa – referenční dokumentace
- NSCollectionView
- pokyny pro lidské rozhraní systému macOS