Xamarin.Mac의 데이터베이스
이 문서에서는 키-값 코딩 및 키-값 관찰을 사용하여 Xcode의 인터페이스 작성기에서 SQLite 데이터베이스와 UI 요소 간의 데이터 바인딩을 허용하는 방법을 설명합니다. 또한 SQLite.NET ORM을 사용하여 SQLite 데이터에 대한 액세스를 제공하는 방법을 다룹니다.
개요
Xamarin.Mac 애플리케이션에서 C# 및 .NET을 사용하는 경우 Xamarin.iOS 또는 Xamarin.Android 애플리케이션에서 액세스할 수 있는 것과 동일한 SQLite 데이터베이스에 액세스할 수 있습니다.
이 문서에서는 SQLite 데이터에 액세스하는 두 가지 방법을 다룹니다.
- 직접 액세스 - SQLite 데이터베이스에 직접 액세스하여 Xcode의 인터페이스 작성기에서 만든 UI 요소를 사용하여 키-값 코딩 및 데이터 바인딩에 데이터베이스의 데이터를 사용할 수 있습니다. Xamarin.Mac 애플리케이션에서 키-값 코딩 및 데이터 바인딩 기술을 사용하면 UI 요소를 채우고 작업하기 위해 작성하고 기본 코드의 양을 크게 줄일 수 있습니다. 또한 프런트 엔드 사용자 인터페이스(Model-View-Controller)에서 백업 데이터(데이터 모델)를 추가로 분리하여 보다 기본 더 유연하고 유연한 애플리케이션 디자인을 얻을 수 있습니다.
- SQLite.NET ORM - 오픈 소스 SQLite.NET ORM(개체 관계 관리자)을 사용하여 SQLite 데이터베이스에서 데이터를 읽고 쓰는 데 필요한 코드의 양을 크게 줄일 수 있습니다. 그런 다음 이 데이터를 사용하여 테이블 뷰와 같은 사용자 인터페이스 항목을 채울 수 있습니다.
이 문서에서는 Xamarin.Mac 애플리케이션에서 SQLite 데이터베이스를 사용하여 키-값 코딩 및 데이터 바인딩을 사용하는 기본 사항을 설명합니다. 이 문서에서 사용할 주요 개념과 기술을 다루므로 Hello, Mac 문서, 특히 Xcode 및 인터페이스 작성기 및 콘센트 및 작업 소개 섹션을 통해 작업하는 것이 좋습니다.
키-값 코딩 및 데이터 바인딩을 사용하므로 먼저 데이터 바인딩 및 키-값 코딩을 진행하세요. 이 설명서 및 샘플 애플리케이션에서 사용되는 핵심 기술 및 개념이 다루어질 예정입니다.
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 파일.
다음을 수행하십시오:
솔루션 패드에서 참조 폴더를 마우스 오른쪽 단추로 클릭하고 참조 편집을 선택합니다.
Mono.Data.Sqlite 및 System.Data 어셈블리를 모두 선택합니다.
확인 단추를 클릭하여 변경 내용을 저장하고 참조를 추가합니다.
데이터 모델 수정
이제 애플리케이션에 SQLite 데이터베이스에 직접 액세스하기 위한 지원을 추가했으므로 데이터베이스에서 데이터를 읽고 쓰도록 데이터 모델 개체를 수정해야 합니다(키-값 코딩 및 데이터 바인딩 제공). 샘플 애플리케이션의 경우 PersonModel.cs 클래스를 편집하고 다음과 같이 만듭니다.
using System;
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
using Foundation;
using AppKit;
namespace MacDatabase
{
[Register("PersonModel")]
public class PersonModel : NSObject
{
#region Private Variables
private string _ID = "";
private string _managerID = "";
private string _name = "";
private string _occupation = "";
private bool _isManager = false;
private NSMutableArray _people = new NSMutableArray();
private SqliteConnection _conn = null;
#endregion
#region Computed Properties
public SqliteConnection Conn {
get { return _conn; }
set { _conn = value; }
}
[Export("ID")]
public string ID {
get { return _ID; }
set {
WillChangeValue ("ID");
_ID = value;
DidChangeValue ("ID");
}
}
[Export("ManagerID")]
public string ManagerID {
get { return _managerID; }
set {
WillChangeValue ("ManagerID");
_managerID = value;
DidChangeValue ("ManagerID");
}
}
[Export("Name")]
public string Name {
get { return _name; }
set {
WillChangeValue ("Name");
_name = value;
DidChangeValue ("Name");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("Occupation")]
public string Occupation {
get { return _occupation; }
set {
WillChangeValue ("Occupation");
_occupation = value;
DidChangeValue ("Occupation");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isManager")]
public bool isManager {
get { return _isManager; }
set {
WillChangeValue ("isManager");
WillChangeValue ("Icon");
_isManager = value;
DidChangeValue ("isManager");
DidChangeValue ("Icon");
// Save changes to database?
if (_conn != null) Update (_conn);
}
}
[Export("isEmployee")]
public bool isEmployee {
get { return (NumberOfEmployees == 0); }
}
[Export("Icon")]
public NSImage Icon {
get {
if (isManager) {
return NSImage.ImageNamed ("group.png");
} else {
return NSImage.ImageNamed ("user.png");
}
}
}
[Export("personModelArray")]
public NSArray People {
get { return _people; }
}
[Export("NumberOfEmployees")]
public nint NumberOfEmployees {
get { return (nint)_people.Count; }
}
#endregion
#region Constructors
public PersonModel ()
{
}
public PersonModel (string name, string occupation)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (string name, string occupation, bool manager)
{
// Initialize
this.Name = name;
this.Occupation = occupation;
this.isManager = manager;
}
public PersonModel (string id, string name, string occupation)
{
// Initialize
this.ID = id;
this.Name = name;
this.Occupation = occupation;
}
public PersonModel (SqliteConnection conn, string id)
{
// Load from database
Load (conn, id);
}
#endregion
#region Array Controller Methods
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
[Export("insertObject:inPersonModelArrayAtIndex:")]
public void InsertPerson(PersonModel person, nint index) {
WillChangeValue ("personModelArray");
_people.Insert (person, index);
DidChangeValue ("personModelArray");
}
[Export("removeObjectFromPersonModelArrayAtIndex:")]
public void RemovePerson(nint index) {
WillChangeValue ("personModelArray");
_people.RemoveObject (index);
DidChangeValue ("personModelArray");
}
[Export("setPersonModelArray:")]
public void SetPeople(NSMutableArray array) {
WillChangeValue ("personModelArray");
_people = array;
DidChangeValue ("personModelArray");
}
#endregion
#region SQLite Routines
public void Create(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Create new record ID?
if (ID == "") {
ID = Guid.NewGuid ().ToString();
}
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Save manager ID and create the sub record
Person.ManagerID = ID;
Person.Create (conn);
}
// Save last connection
_conn = conn;
}
public void Update(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
command.Parameters.AddWithValue ("@COL2", Name);
command.Parameters.AddWithValue ("@COL3", Occupation);
command.Parameters.AddWithValue ("@COL4", isManager);
command.Parameters.AddWithValue ("@COL5", ManagerID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Save children to database as well
for (nuint n = 0; n < People.Count; ++n) {
// Grab person
var Person = People.GetItem<PersonModel>(n);
// Update sub record
Person.Update (conn);
}
// Save last connection
_conn = conn;
}
public void Load(SqliteConnection conn, string id) {
bool shouldClose = false;
// Clear last connection to prevent circular call to update
_conn = null;
// Is the database already open?
if (conn.State != ConnectionState.Open) {
shouldClose = true;
conn.Open ();
}
// Execute query
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT * FROM [People] WHERE ID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Pull values back into class
ID = (string)reader [0];
Name = (string)reader [1];
Occupation = (string)reader [2];
isManager = (bool)reader [3];
ManagerID = (string)reader [4];
}
}
}
// Is this a manager?
if (isManager) {
// Yes, load children
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Load child and add to collection
var childID = (string)reader [0];
var person = new PersonModel (conn, childID);
_people.Add (person);
}
}
}
}
// Should we close the connection to the database
if (shouldClose) {
conn.Close ();
}
// Save last connection
_conn = conn;
}
public void Delete(SqliteConnection conn) {
// Clear last connection to prevent circular call to update
_conn = null;
// Execute query
conn.Open ();
using (var command = conn.CreateCommand ()) {
// Create new command
command.CommandText = "DELETE FROM [People] WHERE (ID = @COL1 OR ManagerID = @COL1)";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", ID);
// Write to database
command.ExecuteNonQuery ();
}
conn.Close ();
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
// Save last connection
_conn = conn;
}
#endregion
}
}
아래의 수정 사항을 자세히 살펴보겠습니다.
먼저 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
사용하여 데이터베이스에 새 레코드를 만듭니다. 호출하여 메서드CreateCommand
에 SQLiteConnection
전달한 (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
를 사용하여 노출합니다.
특성 검사기에서 자동 완성 및 데이터 원본 속성을 검사.
변경 내용을 저장하고 동기화할 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;
}
또는 속성 값이 TableName
IDField
DisplayField
변경될 때마다 호출됩니다.
메서드는 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(IDField
DisplayField
)를 반환합니다.
public string IDForValue (string value)
{
NSString result = new NSString ("");
bool shouldClose = false;
// Has a Table, ID and display field been specified?
if (TableName != "" && IDField != "" && DisplayField != "") {
// Is the database already open?
if (Conn.State != ConnectionState.Open) {
shouldClose = true;
Conn.Open ();
}
// Execute query
using (var command = Conn.CreateCommand ()) {
// Create new command
command.CommandText = $"SELECT {IDField} FROM [{TableName}] WHERE {DisplayField} = @VAL";
// Populate parameters
command.Parameters.AddWithValue ("@VAL", value);
// Get the results from the database
using (var reader = command.ExecuteReader ()) {
while (reader.Read ()) {
// Read the display field from the query
result = new NSString ((string)reader [0]);
}
}
}
// Should we close the connection to the database
if (shouldClose) {
Conn.Close ();
}
}
// Return results
return result;
}
또는 ItemCount
속성이 변경될 때 TableName
IDField
DisplayField
계산된 대로 목록의 미리 계산된 항목 수를 반환합니다.
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 사용하여 데이터베이스 지원을 추가하려면 이 패키지를 포함해야 합니다.
패키지를 추가하려면 다음을 수행합니다.
Solution Pad에서 패키지 폴더를 마우스 오른쪽 단추로 클릭하고 패키지 추가...를 선택합니다.
검색 상자에 입력
SQLite.net
하고 sqlite-net 항목을 선택합니다.패키지 추가 단추를 클릭하여 완료합니다.
데이터 모델 만들기
프로젝트에 OccupationModel
새 클래스를 추가하고 호출해 보겠습니다. 다음으로, OccupationModel.cs 파일을 편집하고 다음과 같이 만들어 보겠습니다.
using System;
using SQLite;
namespace MacDatabase
{
public class OccupationModel
{
#region Computed Properties
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set;}
public string Description { get; set;}
#endregion
#region Constructors
public OccupationModel ()
{
}
public OccupationModel (string name, string description)
{
// Initialize
this.Name = name;
this.Description = description;
}
#endregion
}
}
먼저 SQLite.NET(using Sqlite
)를 포함한 다음, 이 레코드가 저장될 때 각각 데이터베이스에 기록되는 여러 속성을 노출합니다. 기본 키로 만들고 다음과 같이 자동 증가로 설정하는 첫 번째 속성입니다.
[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
)를 통해 이 테이블 뷰를 노출합니다.
다음으로, 사용자 지정 클래스를 추가하여 이 테이블을 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 데이터로 작업하고 해당 데이터를 테이블 뷰에 표시하는 내용도 설명했습니다.