Xamarin.Mac 中的資料庫
本文涵蓋使用索引鍵/值編碼和索引鍵/值觀察,以允許 Xcode 介面產生器中 SQLite 資料庫與 UI 元素之間的數據系結。 它也涵蓋使用 SQLite.NET ORM 來提供 SQLite 數據的存取權。
概觀
在 Xamarin.Mac 應用程式中使用 C# 和 .NET 時,您可以存取 Xamarin.iOS 或 Xamarin.Android 應用程式可以存取的相同 SQLite 資料庫。
在本文中,我們將涵蓋兩種方式來存取 SQLite 數據:
- 直接存取 - 藉由直接存取 SQLite 資料庫,我們可以使用資料庫中的數據,搭配 Xcode 介面產生器中建立的 UI 元素進行索引鍵/值編碼和數據系結。 藉由在 Xamarin.Mac 應用程式中使用索引鍵/值編碼和數據系結技術,您可以大幅減少您必須撰寫和維護的程式碼數量,以填入和使用 UI 元素。 您也可以從前端使用者介面(Model-View-Controller)進一步分離備份數據(數據模型),進而更輕鬆地維護、更有彈性的應用程式設計。
- SQLite.NET ORM - 藉由使用 開放原始碼 SQLite.NET 對象關聯性管理員 (ORM), 我們可以大幅減少從 SQLite 資料庫讀取和寫入數據所需的程式代碼數量。 然後,此數據可用來填入使用者介面專案,例如數據表檢視。
在本文中,我們將討論在 Xamarin.Mac 應用程式中使用 SQLite 資料庫的索引鍵/值編碼和數據系結的基本概念。 強烈建議您先完成 Hello,Mac 文章,特別是 Xcode 和 Interface Builder 和 Outlets 和 Actions 簡介小節,因為它涵蓋我們將在本文中使用的重要概念和技術。
由於我們將使用索引鍵/值編碼和數據系結,因此請先完成 數據系結和索引鍵/值編碼 ,因為將涵蓋此檔及其範例應用程式將使用的核心技術和概念。
您可能也想要查看 Xamarin.Mac Internals 檔的公開 C# 類別/方法Objective-C一節,它也會說明 Register
用來將 C# 類別連線至Objective-C物件和 UI 元素的 和 Export
屬性。
直接 SQLite 存取
對於即將系結至 Xcode 介面產生器中 UI 元素的 SQLite 數據,強烈建議您直接存取 SQLite 資料庫(而不是使用 ORM 等技術),因為您可以完全控制從資料庫寫入和讀取數據的方式。
如我們在數據系結和索引鍵/值編碼檔中所見,在 Xamarin.Mac 應用程式中使用索引鍵/值編碼和數據系結技術,您可以大幅減少您必須撰寫和維護的程式代碼數量,以填入和使用 UI 元素。 結合 SQLite 資料庫的直接存取權時,也可以大幅減少讀取和寫入該資料庫所需的程式代碼數量。
在本文中,我們將從數據系結和索引鍵/值編碼檔修改範例應用程式,以使用 SQLite 資料庫作為系結的備份來源。
包含 SQLite 資料庫支援
在繼續之前,我們需要加入幾個 的參考,將 SQLite 資料庫支援新增至應用程式。DLL 檔案。
執行下列操作:
在 Solution Pad 中,以滑鼠右鍵按兩下 [參考] 資料夾,然後選取 [編輯參考]。
同時選取 Mono.Data.Sqlite 和 System.Data 元件:
按兩下 [ 確定] 按鈕以儲存您的變更並新增參考。
修改數據模型
既然我們已新增直接存取 SQLite 資料庫至應用程式的支持,我們需要修改數據模型物件,以從資料庫讀取和寫入數據(以及提供索引鍵/值編碼和數據系結)。 在我們的範例應用程式中,我們將編輯 PersonModel.cs 類別,使其看起來如下:
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
}
}
讓我們仔細看看下面的修改。
首先,我們已新增數個 use 語句,這些語句是使用 SQLite 所需的語句,而且我們新增了變數來儲存對 SQLite 資料庫的最後一個連線:
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection _conn = null;
當使用者透過數據系結修改 UI 中的內容時,我們將使用此已儲存的連線,自動將記錄的任何變更儲存至資料庫:
[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);
}
}
如果之前已儲存數據,則對 Name、Occupation 或 isManager 屬性所做的任何變更都會傳送至資料庫(例如,如果_conn
變數不是 null
)。 接下來,讓我們看看我們已新增至 建立、 更新、 載入 和 刪除 資料庫中人員的方法。
建立新記錄
已新增下列程式代碼,以在 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;
}
我們使用 來 SQLiteCommand
在資料庫中建立新記錄。 我們從 傳入 方法的 SQLiteConnection
(conn) 取得新的指令,方法是呼叫 CreateCommand
。 接下來,我們將 SQL 指令設定為實際寫入新記錄,並提供實際值的參數:
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
稍後我們會使用 Parameters.AddWithValue
上的 SQLiteCommand
方法,設定參數的值。 藉由使用參數,我們可確保值(例如單引號)在傳送至 SQLite 之前會正確編碼。 範例:
command.Parameters.AddWithValue ("@COL1", ID);
最後,由於某個人員可以是經理,並擁有員工集合,因此我們會遞歸地呼叫 Create
這些人員上的方法,以將它們儲存至資料庫:
// 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);
}
更新記錄
已新增下列程式代碼,以更新 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;
}
如同上述建立,我們會從傳入 SQLiteConnection
的 取得 SQLiteCommand
,並將 SQL 設定為更新記錄(提供參數):
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
我們會填入參數值(例如: command.Parameters.AddWithValue ("@COL1", ID);
),並再次以遞歸方式呼叫任何子記錄的更新:
// 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);
}
載入記錄
已新增下列程式代碼,以從 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;
}
由於可以從父物件以遞歸方式呼叫例程(例如載入其員工物件的管理員物件),因此新增了特殊程式碼來處理開啟和關閉資料庫的連接:
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 ();
}
一如往常,我們將 SQL 設定為擷取記錄並使用參數:
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
最後,我們使用數據讀取器來執行查詢並傳回記錄欄位(我們會複製到 類別的 PersonModel
實例):
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];
}
}
如果這個人是經理,我們也需要載入所有員工(同樣地,以遞歸方式呼叫他們的 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);
}
}
}
}
刪除記錄
新增下列程式代碼,以從 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;
}
在這裡,我們提供 SQL 來刪除經理記錄和該經理下任何員工的記錄(使用參數):
// 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);
移除記錄之後,我們會清除 類別的 PersonModel
目前實例:
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
初始化資料庫
隨著數據模型變更,以支援讀取和寫入資料庫,我們需要開啟資料庫的連接,並在第一次執行時將其初始化。 讓我們將下列程式代碼新增至 MainWindow.cs 檔案:
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;
}
讓我們進一步瞭解上述程序代碼。 首先,我們會挑選新資料庫的位置(在此範例中為使用者的 Desktop),查看資料庫是否存在,如果不存在,請加以建立:
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);
接下來,我們會使用上述建立的路徑來建立連線至資料庫:
var conn = new SqliteConnection("Data Source=" + db);
然後,我們會在資料庫中建立我們需要的所有 SQL 數據表:
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 ();
最後,我們會使用我們的數據模型 (PersonModel
) 在第一次執行應用程式時或資料庫遺失時,為資料庫建立一組默認的記錄:
// 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);
當應用程式啟動並開啟 [主視窗] 時,我們會使用上面新增的程式代碼來連線資料庫:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get access to database
DatabaseConnection = GetDatabaseConnection ();
}
載入系結的數據
透過從 SQLite 資料庫直接存取系結資料的所有元件,我們可以在應用程式提供的不同檢視中載入資料,而且它會自動顯示在 UI 中。
載入單一記錄
若要載入識別碼知道的單一記錄,我們可以使用下列程式代碼:
Person = new PersonModel (Conn, "0");
載入所有記錄
若要載入所有使用者,不論他們是經理,都請使用下列程式代碼:
// 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 ();
在這裡,我們會使用 類別的建構 PersonModel
函式多載,將人員載入記憶體中:
var person = new PersonModel (_conn, childID);
我們也呼叫數據系結類別,將人員新增至人員集合 AddPerson (person)
,這可確保我們的UI能夠辨識變更並加以顯示:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
僅載入最上層記錄
若要只載入管理員(例如,若要在大綱檢視中顯示數據),我們會使用下列程式代碼:
// 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 ();
SQL 語句中 唯一的實際差異(只載入管理員 command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1"
),但運作方式與上述區段相同。
資料庫和組合框
macOS 可用的功能表控制項(例如下拉式方塊)可以設定為從內部清單填入下拉式清單(可以在介面產生器中預先定義或透過程式代碼填入),或提供您自己的自定義外部數據源。 如需詳細資訊,請參閱 提供功能表控件數據 。
例如,在 Interface Builder 中編輯上述的簡單系結範例、新增下拉式方塊,並使用名為 的 EmployeeSelector
輸出來公開它:
在 [屬性偵測器] 中,檢查 [自動完成] 和 [使用數據源] 屬性:
儲存變更並返回 Visual Studio for Mac 進行同步處理。
提供下拉式方塊數據
接下來,將新的類別新增至名為 ComboBoxDataSource
的專案,使其看起來如下:
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
}
}
在此範例中,我們會建立新的 NSComboBoxDataSource
,以呈現來自任何 SQLite 數據源的下拉式方塊專案。 首先,我們會定義下列屬性:
Conn
- 取得或設定與 SQLite 資料庫的連接。TableName
- 取得或設定資料表名稱。IDField
- 取得或設定提供指定數據表唯一標識符的欄位。 預設值是ID
。DisplayField
- 取得或設定下拉式清單中顯示的欄位。RecordCount
- 取得指定數據表中的記錄數目。
當我們建立 物件的新實例時,我們會傳入連接、數據表名稱、選擇性的標識符欄位和顯示字段:
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
方法 GetRecordCount
會傳回指定資料表中的記錄數目:
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;
}
每當變更 TableName
、 IDField
或 DisplayField
屬性值時,就會呼叫它。
方法 IDForIndex
會針對指定下拉式清單項目索引處的記錄傳回唯一識別碼 (IDField
) :
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;
}
方法 ValueForIndex
會傳回指定下拉式清單索引上項目的值 (DisplayField
) :
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;
}
方法IDForValue
會針對指定的值傳回唯一標識碼 (IDField
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;
}
會ItemCount
傳回清單中預先計算的項目數,如 變更、 IDField
或 DisplayField
屬性時TableName
所計算:
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
方法 ObjectValueForItem
會提供指定下拉式清單項目索引的值 (DisplayField
:
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;
}
請注意,我們在 SQLite 命令中使用 LIMIT
和 OFFSET
語句來限制我們需要的一筆記錄。
方法會 IndexOfItem
傳回指定值 (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;
}
如果找不到值,則會 NSRange.NotFound
傳回值,且所有項目都會在下拉式清單中取消選取。
方法 CompletedString
會傳回部分類型專案的第一個相符值 (DisplayField
) :
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;
}
顯示數據和回應事件
若要將所有片段結合在一起,請編輯 SubviewSimpleBindingController
並使其看起來如下:
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
}
}
屬性 DataSource
提供附加至下拉式方塊之的 ComboBoxDataSource
快捷方式。
方法 LoadSelectedPerson
會從資料庫載入指定唯一標識碼的人員:
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
在方法覆寫中 AwakeFromNib
,首先我們會附加自定義下拉式方塊數據源的實例:
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
接下來,我們會尋找呈現和載入指定人員的相關唯一標識符,IDField
以回應使用者編輯下拉式方塊的文字值:
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));
SQLite.NET ORM
如上所述,藉由使用 開放原始碼 SQLite.NET 對象關聯性管理員 (ORM),我們可以大幅減少從 SQLite 資料庫讀取和寫入數據所需的程式代碼數量。 這在系結數據時可能不是最佳路由,因為索引鍵/值編碼和數據系結在物件上放置的數個需求。
根據 SQLite.Net 網站,“SQLite 是一個軟體連結庫, 可實作獨立、無伺服器、零設定、交易式 SQL 資料庫引擎。SQLite 是世界上最廣泛部署的資料庫引擎。SQLite 的原始碼位於公用網域中。
在下列各節中,我們將示範如何使用 SQLite.Net 來提供數據表檢視的數據。
包含 SQLite.net NuGet
SQLite.NET 會顯示為您在應用程式中所包含的 NuGet 套件。 我們必須先包含此套件,才能使用 SQLite.NET 新增資料庫支援。
執行下列動作以新增套件:
在 Solution Pad 中,以滑鼠右鍵按兩下 [套件] 資料夾,然後選取 [新增套件...
在搜尋方塊中輸入
SQLite.net
,然後選取 sqlite-net 專案:按兩下 [ 新增套件] 按鈕以完成。
建立數據模型
讓我們將新的類別新增至專案,並在 中 OccupationModel
呼叫 。 接下來,讓我們編輯 OccupationModel.cs 檔案,讓它看起來如下所示:
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
}
}
首先,我們會包含 SQLite.NET (using Sqlite
),然後公開數個 Properties,每一個屬性都會在儲存此記錄時寫入資料庫。 我們做為主鍵的第一個屬性,並將其設定為自動遞增,如下所示:
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
初始化資料庫
隨著數據模型變更,以支援讀取和寫入資料庫,我們需要開啟資料庫的連接,並在第一次執行時將其初始化。 讓我們新增下列程式代碼:
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;
}
首先,我們會取得資料庫的路徑(在此案例中為使用者桌面),並查看資料庫是否存在:
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);
接下來,我們會在上面建立的路徑建立資料庫連線:
var conn = new SQLiteConnection (db);
最後,我們會建立數據表,並新增一些默認記錄:
// 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);
新增數據表檢視
作為範例使用方式,我們會在 Xcode 的介面產生器中,將數據表檢視新增至 UI。 我們將透過輸出 (OccupationTable
) 公開此資料表檢視,以便我們可以透過 C# 程式代碼加以存取:
接下來,我們將新增自定義類別,以從 SQLite.NET 資料庫將數據填入此數據表。
建立數據表數據源
讓我們建立自定義數據源來提供數據表的數據。 首先,新增名為 TableORMDatasource
的新類別,使其看起來如下:
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
}
}
當我們稍後建立這個類別的實例時,我們會傳入開啟 SQLite.NET 資料庫連線。 方法 LoadOccupations
會查詢資料庫,並將找到的記錄複製到記憶體中(使用我們的 OccupationModel
數據模型)。
建立數據表委派
我們需要的最後一個類別是自定義數據表委派,以顯示我們從 SQLite.NET 資料庫載入的資訊。 讓我們將新的 TableORMDelegate
新增至專案,使其看起來如下:
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
}
}
在這裡,我們會使用數據源的 Occupations
集合(從 SQLite.NET 資料庫載入),透過方法覆寫填入數據表 GetViewForItem
的數據行。
填入數據表
所有專案都就緒后,藉由覆 AwakeFromNib
寫 方法並使其看起來像這樣,從 .xib 檔案擴充數據表時,讓我們填入數據表:
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);
}
首先,我們取得 SQLite.NET 資料庫的存取權,如果資料庫不存在,就會建立並填入資料庫。 接下來,我們會建立自定義數據表數據源的新實例、傳入資料庫連線,並將它附加至數據表。 最後,我們會建立自定義數據表委派的新實例、傳入數據源,並將它附加至數據表。
摘要
本文已詳細探討在 Xamarin.Mac 應用程式中使用 SQLite 資料庫的數據系結和索引鍵/值編碼。 首先,它會使用索引鍵/值編碼 (KVC) 和索引鍵/值觀察 (KVO) 將 C# 類別公開至 Objective-C 。 接下來,它示範如何使用 KVO 相容類別,並將它系結至 Xcode 介面產生器中的 UI 元素。 本文也涵蓋透過 SQLite.NET ORM 使用 SQLite 數據,並在數據表檢視中顯示該資料。