Xamarin.Mac의 데이터베이스

이 문서에서는 키-값 코딩 및 키-값 관찰을 사용하여 Xcode의 인터페이스 작성기에서 SQLite 데이터베이스와 UI 요소 간의 데이터 바인딩을 허용하는 방법을 설명합니다. 또한 SQLite.NET ORM을 사용하여 SQLite 데이터에 대한 액세스를 제공하는 방법을 다룹니다.

개요

Xamarin.Mac 애플리케이션에서 C# 및 .NET을 사용하는 경우 Xamarin.iOS 또는 Xamarin.Android 애플리케이션에서 액세스할 수 있는 것과 동일한 SQLite 데이터베이스에 액세스할 수 있습니다.

이 문서에서는 SQLite 데이터에 액세스하는 두 가지 방법을 다룹니다.

  1. 직접 액세스 - SQLite 데이터베이스에 직접 액세스하여 Xcode의 인터페이스 작성기에서 만든 UI 요소를 사용하여 키-값 코딩 및 데이터 바인딩에 데이터베이스의 데이터를 사용할 수 있습니다. Xamarin.Mac 애플리케이션에서 키-값 코딩 및 데이터 바인딩 기술을 사용하면 UI 요소를 채우고 작업하기 위해 작성하고 기본 코드의 양을 크게 줄일 수 있습니다. 또한 프런트 엔드 사용자 인터페이스(Model-View-Controller)에서 백업 데이터(데이터 모델)를 추가로 분리하여 보다 기본 더 유연하고 유연한 애플리케이션 디자인을 얻을 수 있습니다.
  2. SQLite.NET ORM - 오픈 소스 SQLite.NET ORM(개체 관계 관리자)을 사용하여 SQLite 데이터베이스에서 데이터를 읽고 쓰는 데 필요한 코드의 양을 크게 줄일 수 있습니다. 그런 다음 이 데이터를 사용하여 테이블 뷰와 같은 사용자 인터페이스 항목을 채울 수 있습니다.

An example of the running app

이 문서에서는 Xamarin.Mac 애플리케이션에서 SQLite 데이터베이스를 사용하여 키-값 코딩 및 데이터 바인딩을 사용하는 기본 사항을 설명합니다. 이 문서에서 사용할 주요 개념과 기술을 다루므로 Hello, Mac 문서, 특히 Xcode 및 인터페이스 작성기 및 콘센트 및 작업 소개 섹션을 통해 작업하는 것이 좋습니다.

키-값 코딩 및 데이터 바인딩을 사용하므로 먼저 데이터 바인딩 및 키-값 코딩을 진행하세요. 이 설명서 및 샘플 애플리케이션에서 사용되는 핵심 기술 및 개념이 다루어질 예정입니다.

Xamarin.Mac Internals 문서의 섹션Objective-C대한 C# 클래스/메서드 노출을 살펴보고 C# 클래스 Objective-C 를 개체 및 UI 요소에 연결하는 데 사용되는 특성과 Export 특성을 설명 Register 합니다.

직접 SQLite 액세스

Xcode의 인터페이스 작성기에서 UI 요소에 바인딩될 SQLite 데이터의 경우 데이터베이스에서 데이터를 쓰고 읽는 방식을 완전히 제어할 수 있으므로 ORM과 같은 기술을 사용하는 대신 SQLite 데이터베이스에 직접 액세스하는 것이 좋습니다.

데이터 바인딩 및 키-값 코딩 설명서에서 볼 수 있듯이 Xamarin.Mac 애플리케이션에서 키-값 코딩 및 데이터 바인딩 기술을 사용하여 UI 요소를 채우고 작업하기 위해 작성하고 기본 코드의 양을 크게 줄일 수 있습니다. SQLite 데이터베이스에 대한 직접 액세스와 결합하면 해당 데이터베이스에 데이터를 읽고 쓰는 데 필요한 코드의 양을 크게 줄일 수도 있습니다.

이 문서에서는 데이터 바인딩 및 키-값 코딩 문서에서 샘플 앱을 수정하여 SQLite 데이터베이스를 바인딩의 지원 원본으로 사용합니다.

SQLite 데이터베이스 지원 포함

계속하려면 몇 가지 참조를 포함하여 애플리케이션에 SQLite 데이터베이스 지원을 추가해야 합니다. DLL 파일.

다음을 수행하십시오:

  1. 솔루션 패드에서 참조 폴더를 마우스 오른쪽 단추로 클릭하고 참조 편집을 선택합니다.

  2. Mono.Data.SqliteSystem.Data 어셈블리를 모두 선택합니다.

    Adding the required references

  3. 확인 단추를 클릭하여 변경 내용을 저장하고 참조를 추가합니다.

데이터 모델 수정

이제 애플리케이션에 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
    }
}

아래의 수정 사항을 자세히 살펴보겠습니다.

먼저 SQLite를 사용하는 데 필요한 몇 가지 using 문을 추가했으며 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);
    }
}

이름, 직업 또는 isManager 속성에 대한 변경 내용은 이전에 데이터가 저장된 경우 데이터베이스로 전송됩니다(예: 변수가 아닌 null경우_conn). 다음으로 데이터베이스에서 사용자 만들기, 업데이트, 로드삭제추가한 메서드를 살펴보겠습니다.

새 레코드 만들기

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

A를 SQLiteCommand 사용하여 데이터베이스에 새 레코드를 만듭니다. 호출하여 메서드CreateCommandSQLiteConnection 전달한 (conn)에서 새 명령을 가져옵니다. 다음으로, 실제 값에 대한 매개 변수를 제공하여 실제로 새 레코드를 작성하도록 SQL 명령을 설정합니다.

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

나중에 .의 메서드SQLiteCommand를 사용하여 매개 변수에 Parameters.AddWithValue 대한 값을 설정합니다. 매개 변수를 사용하여 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;
}

위의 코드를 좀 더 자세히 살펴보겠습니다. 먼저 새 데이터베이스의 위치(이 예제에서는 사용자의 데스크톱)를 선택하고, 데이터베이스가 존재하는지 확인하고, 데이터베이스가 없는 경우 만듭니다.

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에 자동으로 표시됩니다.

단일 레코드 로드

ID가 알고 있는 단일 레코드를 로드하려면 다음 코드를 사용할 수 있습니다.

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

또한 Data Bound 클래스를 호출하여 사용자 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를 사용하여 노출합니다.

Exposing a combo box outlet

특성 검사기에서 자동 완성데이터 원본 속성을 검사.

Configuring the combo box attributes

변경 내용을 저장하고 동기화할 Mac용 Visual Studio 돌아갑니다.

콤보박스 데이터 제공

다음으로, 호출 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
    }
}

이 예제에서는 모든 SQLite 데이터 원본에서 콤보 상자 항목을 표시할 수 있는 새 NSComboBoxDataSource 항목을 만듭니다. 먼저 다음 속성을 정의합니다.

  • Conn - SQLite 데이터베이스에 대한 연결을 가져오거나 설정합니다.
  • TableName - 테이블 이름을 가져오거나 설정합니다.
  • IDField - 지정된 테이블에 대한 고유 ID를 제공하는 필드를 가져오거나 설정합니다. 기본값은 ID입니다.
  • DisplayField - 드롭다운 목록에 표시되는 필드를 가져오거나 설정합니다.
  • RecordCount - 지정된 테이블의 레코드 수를 가져옵니다.

개체의 새 인스턴스를 만들 때 연결, 테이블 이름, 선택적으로 ID 필드 및 표시 필드를 전달합니다.

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

또는 속성 값이 TableNameIDFieldDisplayField 변경될 때마다 호출됩니다.

메서드는 IDForIndex 지정된 드롭다운 목록 항목 인덱스에서 레코드에 대한 고유 ID(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 지정된 값()에 대한 고유 ID(IDFieldDisplayField)를 반환합니다.

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 속성이 변경될 때 TableNameIDFieldDisplayField 계산된 대로 목록의 미리 계산된 항목 수를 반환합니다.

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 지정된 고유 ID에 대해 데이터베이스에서 사용자를 로드합니다.

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

다음으로, 지정된 사람을 표시하고 로드하는 데이터의 연결된 고유 ID(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의 소스 코드는 public do기본."에 있습니다.

다음 섹션에서는 SQLite.Net 사용하여 테이블 뷰에 대한 데이터를 제공하는 방법을 보여 드리겠습니다.

SQLite.net NuGet 포함

SQLite.NET 애플리케이션에 포함하는 NuGet 패키지로 표시됩니다. SQLite.NET 사용하여 데이터베이스 지원을 추가하려면 이 패키지를 포함해야 합니다.

패키지를 추가하려면 다음을 수행합니다.

  1. Solution Pad에서 패키지 폴더를 마우스 오른쪽 단추로 클릭하고 패키지 추가...를 선택합니다.

  2. 검색 상자에 입력 SQLite.net 하고 sqlite-net 항목을 선택합니다.

    Adding the SQLite NuGet package

  3. 패키지 추가 단추를 클릭하여 완료합니다.

데이터 모델 만들기

프로젝트에 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)를 포함한 다음, 이 레코드가 저장될 때 각각 데이터베이스에 기록되는 여러 속성을 노출합니다. 기본 키로 만들고 다음과 같이 자동 증가로 설정하는 첫 번째 속성입니다.

[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에 테이블 뷰를 추가합니다. C# 코드를 통해 액세스할 수 있도록 콘센트(OccupationTable)를 통해 이 테이블 뷰를 노출합니다.

Exposing a table view outlet

다음으로, 사용자 지정 클래스를 추가하여 이 테이블을 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
    }
}

여기서는 데이터 원본의 컬렉션(SQLite.NET 데이터베이스에서 로드)을 사용하여 메서드 재정의 Occupations 를 통해 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 데이터로 작업하고 해당 데이터를 테이블 뷰에 표시하는 내용도 설명했습니다.