Bagikan melalui


Database di Xamarin.Mac

Artikel ini membahas penggunaan pengkodean nilai kunci dan pengamatan nilai kunci untuk memungkinkan pengikatan data antara database SQLite dan elemen UI di Penyusun Antarmuka Xcode. Ini juga mencakup penggunaan orm SQLite.NET untuk menyediakan akses ke data SQLite.

Gambaran Umum

Saat bekerja dengan C# dan .NET dalam aplikasi Xamarin.Mac, Anda memiliki akses ke database SQLite yang sama yang dapat diakses oleh aplikasi Xamarin.iOS atau Xamarin.Android.

Dalam artikel ini, kami akan membahas dua cara untuk mengakses data SQLite:

  1. Akses Langsung - Dengan langsung mengakses Database SQLite, kita dapat menggunakan data dari database untuk pengkodean nilai kunci dan pengikatan data dengan elemen UI yang dibuat di Penyusun Antarmuka Xcode. Dengan menggunakan teknik pengodean kunci dan pengikatan data dalam aplikasi Xamarin.Mac, Anda dapat sangat mengurangi jumlah kode yang harus Anda tulis dan pertahankan untuk mengisi dan bekerja dengan elemen UI. Anda juga memiliki manfaat untuk memisahkan lebih lanjut data cadangan Anda (Model Data) dari Antarmuka Pengguna ujung depan Anda (Model-View-Controller), yang mengarah ke desain aplikasi yang lebih mudah dipertahankan dan lebih fleksibel.
  2. SQLite.NET ORM - Dengan menggunakan sumber terbuka SQLite.NET Object Relationship Manager (ORM) kami dapat sangat mengurangi jumlah kode yang diperlukan untuk membaca dan menulis data dari database SQLite. Data ini kemudian dapat digunakan untuk mengisi item antarmuka pengguna seperti Tampilan Tabel.

Contoh aplikasi yang sedang berjalan

Dalam artikel ini, kita akan membahas dasar-dasar bekerja dengan pengkodian nilai kunci dan pengikatan data dengan SQLite Databases dalam aplikasi Xamarin.Mac. Sangat disarankan agar Anda bekerja melalui artikel Hello, Mac terlebih dahulu, khususnya bagian Pengenalan Xcode dan Penyusun Antarmuka dan Outlet dan Tindakan , karena mencakup konsep dan teknik utama yang akan kita gunakan dalam artikel ini.

Karena kita akan menggunakan pengkodan kunci-nilai dan pengikatan data, silakan bekerja melalui pengikatan data dan pengkodian nilai kunci terlebih dahulu, karena teknik dan konsep inti akan tercakup yang akan digunakan dalam dokumentasi ini dan aplikasi sampelnya.

Anda mungkin ingin melihat kelas /metode Exposing C# keObjective-Cbagian dari dokumen Xamarin.Mac Internals juga, ini menjelaskan Register atribut dan Export yang digunakan untuk menghubungkan kelas C# Anda ke Objective-C objek dan elemen UI.

Akses SQLite langsung

Untuk data SQLite yang akan terikat ke elemen UI di Penyusun Antarmuka Xcode, sangat disarankan agar Anda mengakses database SQLite secara langsung (dibandingkan dengan menggunakan teknik seperti ORM), karena Anda memiliki kontrol total atas cara data ditulis dan dibaca dari database.

Seperti yang telah kita lihat dalam dokumentasi Pengikatan Data dan Pengkodean Nilai Kunci, dengan menggunakan teknik pengkodean nilai kunci dan pengikatan data di aplikasi Xamarin.Mac Anda, Anda dapat sangat mengurangi jumlah kode yang harus Anda tulis dan pertahankan untuk mengisi dan bekerja dengan elemen UI. Ketika dikombinasikan dengan akses langsung ke database SQLite, itu juga dapat sangat mengurangi jumlah kode yang diperlukan untuk membaca dan menulis data ke database tersebut.

Dalam artikel ini, kami akan memodifikasi aplikasi sampel dari dokumen pengikatan data dan pengkodian nilai kunci untuk menggunakan SQLite Database sebagai sumber cadangan untuk pengikatan.

Termasuk dukungan database SQLite

Sebelum dapat melanjutkan, kita perlu menambahkan dukungan database SQLite ke aplikasi kita dengan menyertakan Referensi ke beberapa . File DLL.

Lakukan:

  1. Di Pad Solusi, klik kanan pada folder Referensi dan pilih Edit Referensi.

  2. Pilih rakitan Mono.Data.Sqlite dan System.Data :

    Menambahkan referensi yang diperlukan

  3. Klik tombol OK untuk menyimpan perubahan Anda dan menambahkan referensi.

Memodifikasi model data

Sekarang setelah kami menambahkan dukungan untuk mengakses database SQLite secara langsung ke aplikasi kami, kami perlu memodifikasi Objek Model Data kami untuk membaca dan menulis data dari database (serta menyediakan pengkodian dan pengikatan data bernilai kunci). Dalam kasus aplikasi sampel kami, kita akan mengedit kelas PersonModel.cs dan membuatnya terlihat seperti berikut:

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

Mari kita lihat modifikasi secara rinci di bawah ini.

Pertama, kami telah menambahkan beberapa menggunakan pernyataan yang diperlukan untuk menggunakan SQLite dan kami telah menambahkan variabel untuk menyimpan koneksi terakhir kami ke database SQLite:

using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...

private SqliteConnection _conn = null;

Kami akan menggunakan koneksi tersimpan ini untuk menyimpan perubahan apa pun secara otomatis ke rekaman ke database saat pengguna memodifikasi konten di UI melalui pengikatan data:

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

Setiap perubahan yang dilakukan pada properti Nama, Pekerjaan, atau isManager akan dikirim ke database jika data telah disimpan di sana sebelumnya (misalnya jika _conn variabel tidak null). Selanjutnya, mari kita lihat metode yang telah kita tambahkan ke Buat, Perbarui, Muat, dan Hapus orang dari database.

Buat catatan baru

Kode berikut ditambahkan untuk membuat rekaman baru di database 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;
}

Kami menggunakan SQLiteCommand untuk membuat rekaman baru dalam database. Kami mendapatkan perintah baru dari SQLiteConnection (conn) yang kami teruskan ke metode dengan memanggil CreateCommand. Selanjutnya, kami mengatur instruksi SQL untuk benar-benar menulis rekaman baru, menyediakan parameter untuk nilai aktual:

command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";

Kemudian kita mengatur nilai untuk parameter menggunakan Parameters.AddWithValue metode pada SQLiteCommand. Dengan menggunakan parameter, kami memastikan bahwa nilai (seperti tanda kutip tunggal) dikodekan dengan benar sebelum dikirim ke SQLite. Contoh:

command.Parameters.AddWithValue ("@COL1", ID);

Akhirnya, karena seseorang dapat menjadi manajer dan memiliki kumpulan karyawan di bawah mereka, kami secara rekursif memanggil metode pada orang-orang tersebut Create untuk menyimpannya ke database juga:

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

Memperbarui rekaman

Kode berikut ditambahkan untuk memperbarui rekaman yang sudah ada di database 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;
}

Seperti Buat di atas, kami mendapatkan SQLiteCommand dari yang diteruskan di SQLiteConnection, dan mengatur SQL kami untuk memperbarui catatan kami (memberikan parameter):

command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";

Kami mengisi nilai parameter (misalnya: command.Parameters.AddWithValue ("@COL1", ID);) dan sekali lagi, secara rekursif memanggil pembaruan pada catatan anak apa pun:

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

Memuat rekaman

Kode berikut ditambahkan untuk memuat rekaman yang sudah ada dari database 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;
}

Karena rutinitas dapat dipanggil secara rekursif dari objek induk (seperti objek manajer yang memuat objek karyawan mereka), kode khusus ditambahkan untuk menangani pembukaan dan penutupan koneksi ke database:

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

Seperti biasa, kami mengatur SQL kami untuk mengambil catatan dan menggunakan parameter:

// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";

// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);

Terakhir, kita menggunakan Pembaca Data untuk menjalankan kueri dan mengembalikan bidang rekaman (yang kita salin ke dalam instans PersonModel kelas):

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

Jika orang ini adalah manajer, kita juga perlu memuat semua karyawan mereka (sekali lagi, dengan memanggil metode mereka Load secara rekursif):

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

Menghapus rekaman

Kode berikut ditambahkan untuk menghapus rekaman yang sudah ada dari database 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;
}

Di sini kami menyediakan SQL untuk menghapus catatan manajer dan catatan karyawan mana pun di bawah manajer tersebut (menggunakan parameter):

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

Setelah rekaman dihapus, kami menghapus instans PersonModel kelas saat ini:

// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();

Menginisialisasi database

Dengan perubahan pada Model Data kami untuk mendukung pembacaan dan penulisan ke database, kita perlu membuka koneksi ke database dan menginisialisasinya pada eksekusi pertama. Mari kita tambahkan kode berikut ke file MainWindow.cs kita:

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

Mari kita lihat lebih dekat kode di atas. Pertama, kita memilih lokasi untuk database baru (dalam contoh ini, Desktop pengguna), lihat untuk melihat apakah database ada, dan jika tidak, buat:

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

Selanjutnya, kami membuat koneksi ke database menggunakan jalur yang kami buat di atas:

var conn = new SqliteConnection("Data Source=" + db);

Kemudian kita membuat semua tabel SQL dalam database yang kita butuhkan:

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

Terakhir, kami menggunakan Model Data (PersonModel) kami untuk membuat sekumpulan rekaman default untuk database saat pertama kali aplikasi dijalankan atau jika database hilang:

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

Ketika aplikasi dimulai dan membuka Jendela Utama, kami membuat koneksi ke database menggunakan kode yang kami tambahkan di atas:

public override void AwakeFromNib ()
{
    base.AwakeFromNib ();

    // Get access to database
    DatabaseConnection = GetDatabaseConnection ();
}

Memuat data terikat

Dengan semua komponen untuk mengakses data terikat secara langsung dari database SQLite, kita dapat memuat data dalam tampilan berbeda yang disediakan aplikasi kita dan secara otomatis akan ditampilkan di UI kita.

Memuat satu rekaman

Untuk memuat satu rekaman di mana ID diketahui, kita dapat menggunakan kode berikut:

Person = new PersonModel (Conn, "0");

Memuat semua rekaman

Untuk memuat semua orang, terlepas dari apakah mereka adalah manajer atau tidak, gunakan kode berikut:

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

Di sini, kita menggunakan kelebihan beban konstruktor agar PersonModel kelas memuat orang tersebut ke dalam memori:

var person = new PersonModel (_conn, childID);

Kami juga memanggil kelas Terikat Data untuk menambahkan orang ke kumpulan orang AddPerson (person)kami , ini memastikan bahwa UI kami mengenali perubahan dan menampilkannya:

[Export("addObject:")]
public void AddPerson(PersonModel person) {
    WillChangeValue ("personModelArray");
    isManager = true;
    _people.Add (person);
    DidChangeValue ("personModelArray");
}

Memuat rekaman tingkat atas saja

Untuk memuat hanya manajer (misalnya, untuk menampilkan data dalam Tampilan Kerangka), kami menggunakan kode berikut:

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

Satu-satunya perbedaan nyata dalam pernyataan SQL (yang hanya memuat manajer command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1") tetapi berfungsi sama dengan bagian di atas jika tidak.

Database dan kotak kombo

Kontrol Menu yang tersedia untuk macOS (seperti Kotak Kombo) dapat diatur untuk mengisi daftar dropdown baik dari daftar internal (yang dapat ditentukan sebelumnya di Penyusun Antarmuka atau diisi melalui kode) atau dengan menyediakan sumber data eksternal kustom Anda sendiri. Lihat Menyediakan Data Kontrol Menu untuk detail selengkapnya.

Sebagai contoh, edit contoh Pengikatan Sederhana di atas di Interface Builder, tambahkan Combo Box dan ekspos menggunakan outlet bernama EmployeeSelector:

Mengekspos outlet kotak kombo

Di Pemeriksa Atribut, periksa properti Autocompletes dan Uses Data Source:

Mengonfigurasi atribut kotak kombo

Simpan perubahan Anda dan kembali ke Visual Studio untuk Mac untuk disinkronkan.

Menyediakan data combobox

Selanjutnya, tambahkan kelas baru ke proyek yang dipanggil ComboBoxDataSource dan buat terlihat seperti berikut ini:

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

Dalam contoh ini, kami membuat baru NSComboBoxDataSource yang dapat menyajikan Item Combo Box dari Sumber Data SQLite apa pun. Pertama, kita menentukan properti berikut:

  • Conn - Mendapatkan atau mengatur koneksi ke database SQLite.
  • TableName - Mendapatkan atau mengatur nama tabel.
  • IDField - Mendapatkan atau mengatur bidang yang menyediakan ID unik untuk Tabel yang diberikan. Nilai defaultnya adalah ID.
  • DisplayField - Mendapatkan atau mengatur bidang yang ditampilkan dalam daftar dropdown.
  • RecordCount - Mendapatkan jumlah rekaman dalam Tabel yang diberikan.

Saat kita membuat instans baru objek, kita meneruskan koneksi, nama tabel, secara opsional bidang ID dan bidang tampilan:

public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
    // Initialize
    this.Conn = conn;
    this.TableName = tableName;
    this.DisplayField = displayField;
}

Metode GetRecordCount mengembalikan jumlah rekaman dalam Tabel yang diberikan:

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

Ini dipanggil setiap kali TableNamenilai properti , IDField atau DisplayField diubah.

Metode IDForIndex mengembalikan ID unik (IDField) untuk rekaman di indeks item daftar dropdown yang diberikan:

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

Metode ValueForIndex mengembalikan nilai (DisplayField) untuk item di indeks daftar dropdown yang diberikan:

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

Metode IDForValue mengembalikan ID unik (IDField) untuk nilai yang diberikan (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;
}

mengembalikan ItemCount jumlah item yang telah dikomputasi sebelumnya dalam daftar sebagaimana dihitung saat TableNameproperti , IDField atau DisplayField diubah:

public override nint ItemCount (NSComboBox comboBox)
{
    return RecordCount;
}

Metode ini ObjectValueForItem menyediakan nilai (DisplayField) untuk indeks item daftar dropdown yang diberikan:

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

Perhatikan bahwa kami menggunakan LIMIT pernyataan dan OFFSET dalam perintah SQLite kami untuk membatasi satu catatan yang diperlukan.

Metode mengembalikan IndexOfItem indeks item dropdown dari nilai (DisplayField) yang diberikan:

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

Jika nilai tidak dapat ditemukan, NSRange.NotFound nilai dikembalikan dan semua item tidak dipilih dalam daftar dropdown.

Metode mengembalikan CompletedString nilai pencocokan pertama (DisplayField) untuk entri yang ditik sebagian:

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

Menampilkan data dan merespons peristiwa

Untuk menyatukan semua bagian, edit SubviewSimpleBindingController dan buatlah terlihat seperti berikut ini:

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

Properti DataSource menyediakan pintasan ke ComboBoxDataSource (dibuat di atas) yang dilampirkan ke Kotak Kombo.

Metode memuat LoadSelectedPerson orang dari database untuk ID Unik yang diberikan:

private void LoadSelectedPerson (string id)
{

    // Found?
    if (id != "") {
        // Yes, load requested record
        Person = new PersonModel (Conn, id);
    }
}

Dalam penimpaan AwakeFromNib metode, pertama-tama kita melampirkan instans Sumber Data Combo Box kustom kami:

EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");

Selanjutnya, kami menanggapi pengguna yang mengedit nilai teks Kotak Kombo dengan menemukan ID unik terkait (IDField) dari data yang menyajikan dan memuat orang yang diberikan jika ditemukan:

EmployeeSelector.Changed += (sender, e) => {
    // Get ID
    var id = DataSource.IDForValue (EmployeeSelector.StringValue);
    LoadSelectedPerson (id);
};

Kami juga memuat orang baru jika pengguna memilih item baru dari daftar dropdown:

EmployeeSelector.SelectionChanged += (sender, e) => {
    // Get ID
    var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
    LoadSelectedPerson (id);
};

Terakhir, kami mengisi Kotak Kombo secara otomatis dan menampilkan orang dengan item pertama dalam daftar:

// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));

SQLite.NET ORM

Seperti yang dinyatakan di atas, dengan menggunakan sumber terbuka SQLite.NET Object Relationship Manager (ORM) kami dapat sangat mengurangi jumlah kode yang diperlukan untuk membaca dan menulis data dari database SQLite. Ini mungkin bukan rute terbaik yang harus diambil saat mengikat data karena beberapa persyaratan yang mengkoding nilai kunci dan tempat pengikatan data pada objek.

Menurut situs web SQLite.Net, "SQLite adalah pustaka perangkat lunak yang mengimplementasikan mesin database SQL yang mandiri, tanpa server, tanpa konfigurasi, transaksi. SQLite adalah mesin database yang paling banyak disebarkan di dunia. Kode sumber untuk SQLite ada di domain publik."

Di bagian berikut, kita akan memperlihatkan cara menggunakan SQLite.Net untuk menyediakan data untuk Tampilan Tabel.

Menyertakan SQLite.net NuGet

SQLite.NET disajikan sebagai Paket NuGet yang Anda sertakan dalam aplikasi Anda. Sebelum kita dapat menambahkan dukungan database menggunakan SQLite.NET, kita perlu menyertakan paket ini.

Lakukan hal berikut untuk menambahkan paket:

  1. Di Pad Solusi, klik kanan folder Paket dan pilih Tambahkan Paket...

  2. Masukkan SQLite.net di Kotak Pencarian dan pilih entri sqlite-net :

    Menambahkan paket SQLite NuGet

  3. Klik tombol Tambahkan Paket untuk menyelesaikannya.

Membuat model data

Mari kita tambahkan kelas baru ke proyek dan panggil di OccupationModel. Selanjutnya, mari kita edit file OccupationModel.cs dan membuatnya terlihat seperti berikut ini:

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

Pertama, kami menyertakan SQLite.NET (using Sqlite), kemudian kami mengekspos beberapa Properti, yang masing-masing akan ditulis ke database ketika rekaman ini disimpan. Properti pertama yang kita buat sebagai kunci utama dan mengaturnya ke kenaikan otomatis sebagai berikut:

[PrimaryKey, AutoIncrement]
public int ID { get; set; }

Menginisialisasi database

Dengan perubahan pada Model Data kami untuk mendukung pembacaan dan penulisan ke database, kita perlu membuka koneksi ke database dan menginisialisasinya pada eksekusi pertama. Mari kita tambahkan kode berikut:

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

Pertama, kita mendapatkan jalur ke database (Desktop Pengguna dalam hal ini) dan melihat apakah database sudah ada:

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

Selanjutnya, kami membuat koneksi ke database di jalur yang kami buat di atas:

var conn = new SQLiteConnection (db);

Terakhir, kita membuat tabel dan menambahkan beberapa rekaman default:

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

Menambahkan tampilan tabel

Sebagai contoh penggunaan, kita akan menambahkan Tampilan Tabel ke UI kita di penyusun Antarmuka Xcode. Kami akan mengekspos Tampilan Tabel ini melalui outlet (OccupationTable) sehingga kami dapat mengaksesnya melalui kode C#:

Mengekspos outlet tampilan tabel

Selanjutnya, kita akan menambahkan kelas kustom untuk mengisi tabel ini dengan data dari database SQLite.NET.

Membuat sumber data tabel

Mari kita buat Sumber Data kustom untuk menyediakan data untuk tabel kita. Pertama, tambahkan kelas baru yang disebut TableORMDatasource dan buat terlihat seperti berikut ini:

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

Ketika kita membuat instans kelas ini nanti, kita akan meneruskan koneksi database SQLite.NET terbuka. Metode ini LoadOccupations meminta database dan menyalin rekaman yang ditemukan ke dalam memori (menggunakan model data kami OccupationModel ).

Membuat delegasi tabel

Kelas akhir yang kita butuhkan adalah Delegasi Tabel kustom untuk menampilkan informasi yang telah kita muat dari database SQLite.NET. Mari kita tambahkan yang baru TableORMDelegate ke proyek kita dan membuatnya terlihat seperti berikut:

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

Di sini kita menggunakan koleksi Sumber Occupations Data (yang kita muat dari database SQLite.NET) untuk mengisi kolom tabel kita melalui penimpaan GetViewForItem metode.

Mengisi tabel

Dengan semua potongan di tempat, mari kita isi tabel kita ketika dilampirkan dari file .xib dengan mengambil alih AwakeFromNib metode dan membuatnya terlihat seperti berikut:

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

Pertama, kita mendapatkan akses ke database SQLite.NET kita, membuat dan mengisinya jika belum ada. Selanjutnya, kami membuat instans baru Sumber Data Tabel kustom kami, meneruskan koneksi database kami dan kami melampirkannya ke Tabel. Terakhir, kami membuat instans baru Delegasi Tabel kustom kami, meneruskan Sumber Data kami dan melampirkannya ke tabel.

Ringkasan

Artikel ini telah melihat secara rinci tentang bekerja dengan pengikatan data dan pengkodean nilai kunci dengan database SQLite dalam aplikasi Xamarin.Mac. Pertama, ia melihat mengekspos kelas C# dengan Objective-C menggunakan pengkodean nilai kunci (KVC) dan pengamatan nilai kunci (KVO). Selanjutnya, ini menunjukkan cara menggunakan kelas yang sesuai dengan KVO dan Data Bind ke elemen UI di Penyusun Antarmuka Xcode. Artikel ini juga membahas bekerja dengan data SQLite melalui orm SQLite.NET dan menampilkan data tersebut dalam Tampilan Tabel.