Bases de datos en Xamarin.Mac
En este artículo se describe el uso de la codificación de clave-valor y la observación de clave-valor para permitir el enlace de datos entre bases de datos de SQLite y elementos de interfaz de usuario en el Interface Builder de Xcode. También se describe el uso de SQLite.NET ORM para proporcionar acceso a los datos de SQLite.
Información general
Al trabajar con C# y .NET en una aplicación de Xamarin.Mac, tiene acceso a las mismas bases de datos de SQLite a las que puede acceder una aplicación Xamarin.iOS o Xamarin.Android.
En este artículo se tratarán dos maneras de acceder a los datos de SQLite:
- Acceso directo: al acceder directamente a una base de datos de SQLite, podemos usar datos de la base de datos para la codificación de clave-valor y el enlace de datos con elementos de interfaz de usuario creados en la base de datos de Xcode Interface Builder. Mediante el uso de técnicas de codificación de clave-valor y enlace de datos en la aplicación Xamarin.Mac, puede reducir considerablemente la cantidad de código que tiene que escribir y mantener para rellenar y trabajar con elementos de la interfaz de usuario. También tiene la ventaja de desacomanar aún más los datos de respaldo(modelode datos) de su front-end Interfaz de usuario(modelo-vista-controlador),lo que facilita el mantenimiento y el diseño de aplicaciones más flexible.
- SQLite.NET ORM: mediante el Administrador de relaciones de objetos (ORM) de SQLite.NET de código abierto, podemos reducir considerablemente la cantidad de código necesario para leer y escribir datos de una base de datos sqlite. Estos datos se pueden usar para rellenar un elemento de interfaz de usuario, como una vista de tabla.
Un ejemplo de la aplicación en
En este artículo, se tratarán los conceptos básicos de trabajar con codificación de clave-valor y enlace de datos con bases de datos sqlite en una aplicación de Xamarin.Mac. Se recomienda encarecidamente que trabaje primero en el artículo Hello, Mac, en concreto en las secciones Introduction to Xcode and Interface Builder and Outlets and Actions (Introducción a Xcode y Interface Builder y salidas y acciones), ya que trata los conceptos y técnicas clave que se usarán en este artículo.
Puesto que vamos a usar la codificación de clave-valor y el enlace de datos, trabaje primero con el enlace de datos y la codificación de clave-valor, ya que se tratarán técnicas y conceptos básicos que se usarán en esta documentación y en su aplicación de ejemplo.
También puede echar un vistazo a la sección del documento Xamarin.Mac Internals (Aspectos internos de Exposing C# classes / methods to Objective-CExposing C# classes / methods to Objective-C donde se explican los atributos y que se usan para conectar las clases de C# a objetos y elementos de interfaz de RegisterExportObjective-C usuario.
Acceso directo a SQLite
En el caso de los datos de SQLite que se van a enlazar a elementos de la interfaz de usuario en el Interface Builder de Xcode, se recomienda encarecidamente que acceda directamente a la base de datos de SQLite (en lugar de usar una técnica como un ORM), ya que tiene control total sobre la forma en que se escriben y leen los datos de la base de datos.
Como hemos visto en la documentación sobre enlace de datos y codificación de Key-Value, mediante el uso de técnicas de codificación de clave-valor y enlace de datos en la aplicación Xamarin.Mac, puede reducir considerablemente la cantidad de código que tiene que escribir y mantener para rellenar y trabajar con elementos de la interfaz de usuario. Cuando se combina con el acceso directo a una base de datos de SQLite, también puede reducir considerablemente la cantidad de código necesario para leer y escribir datos en esa base de datos.
En este artículo, modificaremos la aplicación de ejemplo del documento de codificación de enlace de datos y clave-valor para usar una base de datos sqlite como origen de respaldo para el enlace.
Inclusión de la compatibilidad con bases de datos de SQLite
Para poder continuar, es necesario agregar compatibilidad con bases de datos sqlite a la aplicación mediante la inclusión de referencias a un par de . Archivos DLL.
Haga lo siguiente:
En la Panel de solución, haga clic con el botón derecho en la carpeta Referencias y seleccione Editar referencias.
Seleccione los ensamblados Mono.Data.Sqlite y System.Data:
Haga clic en el botón Aceptar para guardar los cambios y agregar las referencias.
Modificación del modelo de datos
Ahora que hemos agregado compatibilidad para acceder directamente a una base de datos sqlite a la aplicación, es necesario modificar el objeto de modelo de datos para leer y escribir datos de la base de datos (así como proporcionar codificación de clave-valor y enlace de datos). En el caso de nuestra aplicación de ejemplo, editaremos la clase PersonModel.cs y haremos que se parezca a la siguiente:
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
}
}
Echemos un vistazo a las modificaciones en detalle a continuación.
En primer lugar, hemos agregado varias instrucciones using necesarias para usar SQLite y hemos agregado una variable para guardar la última conexión a la base de datos de SQLite:
using System.Data;
using System.IO;
using Mono.Data.Sqlite;
...
private SqliteConnection _conn = null;
Usaremos esta conexión guardada para guardar automáticamente los cambios en el registro en la base de datos cuando el usuario modifique el contenido de la interfaz de usuario a través del enlace de datos:
[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);
}
}
Los cambios realizados en las propiedades Name, La ocupación o isManager se enviarán a la base de datos si los datos se han guardado allí antes (por ejemplo, si la variable no es ). A continuación, echemos un vistazo a los métodos que hemos agregado a crear,actualizar,cargar y eliminar personas de la base de datos.
Crear un nuevo registro
Se agregó el código siguiente para crear un nuevo registro en la base de datos de 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;
}
Estamos usando para SQLiteCommand crear el nuevo registro en la base de datos. Se obtiene un nuevo comando del SQLiteConnection (conn) que hemos pasado al método mediante una llamada a CreateCommand . A continuación, establecemos la SQL para escribir realmente el nuevo registro, proporcionando parámetros para los valores reales:
command.CommandText = "INSERT INTO [People] (ID, Name, Occupation, isManager, ManagerID) VALUES (@COL1, @COL2, @COL3, @COL4, @COL5)";
Más adelante se establecen los valores de los parámetros mediante Parameters.AddWithValue el método en SQLiteCommand . Mediante el uso de parámetros, nos aseguramos de que los valores (como una comilla simple) se codifican correctamente antes de enviarse a SQLite. Ejemplo:
command.Parameters.AddWithValue ("@COL1", ID);
Por último, dado que una persona puede ser administrador y tener una colección de empleados bajo ellos, llamamos de forma recursiva al método en esas personas para guardarlos también en la base Create de datos:
// 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);
}
Actualizar un registro
Se agregó el código siguiente para actualizar un registro existente en la base de datos de 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;
}
Al igual que crear anteriormente, se obtiene un del pasado en y se establece el SQL para actualizar el registro SQLiteConnection (proporcionando parámetros):
command.CommandText = "UPDATE [People] SET Name = @COL2, Occupation = @COL3, isManager = @COL4, ManagerID = @COL5 WHERE ID = @COL1";
Rellenamos los valores de parámetro (ejemplo: ) y, de nuevo, llamamos a update de forma command.Parameters.AddWithValue ("@COL1", ID); recursiva en cualquier registro secundario:
// 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);
}
Carga de un registro
Se agregó el código siguiente para cargar un registro existente desde la base de datos de 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;
}
Dado que se puede llamar a la rutina de forma recursiva desde un objeto primario (por ejemplo, un objeto de administrador que carga el objeto employees), se agregó código especial para controlar la apertura y el cierre de la conexión a la base de datos:
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 ();
}
Como siempre, establecemos nuestras SQL para recuperar el registro y usar parámetros:
// Create new command
command.CommandText = "SELECT ID FROM [People] WHERE ManagerID = @COL1";
// Populate with data from the record
command.Parameters.AddWithValue ("@COL1", id);
Por último, usamos un Lector de datos para ejecutar la consulta y devolver los campos de registro (que copiamos en la instancia de la PersonModel clase ):
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];
}
}
Si esta persona es un administrador, también debemos cargar a todos sus empleados (de nuevo, llamando de forma recursiva a su Load método):
// 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);
}
}
}
}
Eliminación de un registro
Se agregó el código siguiente para eliminar un registro existente de la base de datos de 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;
}
Aquí se proporcionan los SQL eliminar tanto el registro de administradores como los registros de los empleados de ese administrador (mediante parámetros):
// 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);
Una vez quitado el registro, borramos la instancia actual de la PersonModel clase :
// Empty class
ID = "";
ManagerID = "";
Name = "";
Occupation = "";
isManager = false;
_people = new NSMutableArray();
Inicialización de la base de datos
Con los cambios realizados en nuestro modelo de datos para admitir la lectura y escritura en la base de datos, es necesario abrir una conexión a la base de datos e inicializarla en la primera ejecución. Vamos a agregar el código siguiente a nuestro archivo 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;
}
Echemos un vistazo más de cerca al código anterior. En primer lugar, seleccionamos una ubicación para la nueva base de datos (en este ejemplo, el escritorio del usuario), buscamos si la base de datos existe y, si no es así, la creamos:
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);
A continuación, establecemos la conexión a la base de datos mediante la ruta de acceso que creamos anteriormente:
var conn = new SqliteConnection("Data Source=" + db);
A continuación, creamos todas las SQL en la base de datos que necesitamos:
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 ();
Por último, usamos nuestro modelo de datos ( ) para crear un conjunto predeterminado de registros para la base de datos la primera vez que se ejecuta la aplicación o si falta la base PersonModel de datos:
// 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);
Cuando la aplicación se inicia y abre la ventana principal, establecemos una conexión a la base de datos con el código que agregamos anteriormente:
public override void AwakeFromNib ()
{
base.AwakeFromNib ();
// Get access to database
DatabaseConnection = GetDatabaseConnection ();
}
Carga de datos enlazados
Con todos los componentes para acceder directamente a los datos enlazados desde una base de datos de SQLite, podemos cargar los datos en las distintas vistas que proporciona la aplicación y se mostrarán automáticamente en nuestra interfaz de usuario.
Carga de un único registro
Para cargar un único registro donde se conozca el identificador, podemos usar el código siguiente:
Person = new PersonModel (Conn, "0");
Carga de todos los registros
Para cargar todas las personas, independientemente de si son administradores o no, use el código siguiente:
// 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 ();
En este caso, se usa una sobrecarga del constructor para que la PersonModel clase cargue la persona en la memoria:
var person = new PersonModel (_conn, childID);
También llamamos a la clase Data Bound para agregar la persona a nuestra colección de personas, lo que garantiza que nuestra interfaz de usuario reconozca el cambio AddPerson (person) y lo muestre:
[Export("addObject:")]
public void AddPerson(PersonModel person) {
WillChangeValue ("personModelArray");
isManager = true;
_people.Add (person);
DidChangeValue ("personModelArray");
}
Cargar solo registros de nivel superior
Para cargar solo administradores (por ejemplo, para mostrar datos en una vista esquema), usamos el código siguiente:
// 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 ();
La única diferencia real en la instrucción in SQL (que solo carga administradores), pero funciona igual que la sección anterior en caso command.CommandText = "SELECT ID FROM [People] WHERE isManager = 1" contrario.
Bases de datos y cuadros combinados
Los controles de menú disponibles para macOS (como el cuadro combinado) se pueden establecer para rellenar la lista desplegable desde una lista interna (que se puede definir previamente en Interface Builder o rellenarse mediante código) o proporcionando su propio origen de datos externo personalizado. Vea Proporcionar datos de control de menús para obtener más detalles.
Por ejemplo, edite el ejemplo de enlace simple anterior en Interface Builder, agregue un cuadro combinado y exponga mediante una salida denominada EmployeeSelector :
Exposición de una salida de cuadro
En el Inspector de atributos, active las propiedades Autocompletar y Usa el origen de datos:
Configuración de los atributos de cuadro
Guarde los cambios y vuelva a Visual Studio para Mac para sincronizar.
Proporcionar datos de cuadro combinado
A continuación, agregue una nueva clase al proyecto denominado ComboBoxDataSource y haga que se parezca a la siguiente:
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
}
}
En este ejemplo, vamos a crear un nuevo NSComboBoxDataSource elemento que puede presentar elementos de cuadro combinado desde cualquier origen de datos de SQLite. En primer lugar, definimos las siguientes propiedades:
Conn: obtiene o establece una conexión a la base de datos de SQLite.TableName: obtiene o establece el nombre de la tabla.IDField: obtiene o establece el campo que proporciona el identificador único para la tabla determinada. El valor predeterminado esID.DisplayField: obtiene o establece el campo que se muestra en la lista desplegable.RecordCount: obtiene el número de registros de la tabla dada.
Cuando creamos una nueva instancia del objeto , pasamos la conexión, el nombre de la tabla, opcionalmente el campo id. y el campo para mostrar:
public ComboBoxDataSource (SqliteConnection conn, string tableName, string displayField)
{
// Initialize
this.Conn = conn;
this.TableName = tableName;
this.DisplayField = displayField;
}
El GetRecordCount método devuelve el número de registros de la tabla dada:
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;
}
Se llama cada vez que se TableName cambia el valor de las propiedades , o IDFieldDisplayField .
El IDForIndex método devuelve el identificador único ( ) del registro en el índice de elemento de lista desplegable IDField especificado:
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;
}
El ValueForIndex método devuelve el valor ( ) del elemento en el índice de lista desplegable DisplayField dado:
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;
}
El IDForValue método devuelve el identificador único ( ) para el valor especificado ( 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;
}
devuelve ItemCount el número precalcalado de elementos de la lista tal como se calcula cuando se cambian las TableName propiedades o IDFieldDisplayField :
public override nint ItemCount (NSComboBox comboBox)
{
return RecordCount;
}
El ObjectValueForItem método proporciona el valor ( ) para el índice de elemento de lista desplegable DisplayField especificado:
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;
}
Tenga en cuenta que estamos usando las instrucciones y en nuestro comando de SQLite para limitar al LIMIT único registro que se OFFSET necesita.
El IndexOfItem método devuelve el índice de elemento desplegable del valor ( ) DisplayField dado:
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;
}
Si no se encuentra el valor, el valor se devuelve y todos los elementos se NSRange.NotFound deseleccionan en la lista desplegable.
El CompletedString método devuelve el primer valor de coincidencia ( ) para una entrada con tipo DisplayField parcial:
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;
}
Mostrar datos y responder a eventos
Para reunir todas las piezas, edite y SubviewSimpleBindingController haga que se parezca a la siguiente:
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
}
}
La DataSource propiedad proporciona un acceso directo al ComboBoxDataSource (creado anteriormente) asociado al cuadro combinado.
El LoadSelectedPerson método carga la persona de la base de datos para el identificador único especificado:
private void LoadSelectedPerson (string id)
{
// Found?
if (id != "") {
// Yes, load requested record
Person = new PersonModel (Conn, id);
}
}
En la AwakeFromNib invalidación del método, primero adjuntamos una instancia de nuestro origen de datos de cuadro combinado personalizado:
EmployeeSelector.DataSource = new ComboBoxDataSource (Conn, "People", "Name");
A continuación, respondemos al usuario que edita el valor de texto del cuadro combinado mediante la búsqueda del identificador único asociado ( ) de los datos que presentan y cargan la persona dada si se IDField encuentran:
EmployeeSelector.Changed += (sender, e) => {
// Get ID
var id = DataSource.IDForValue (EmployeeSelector.StringValue);
LoadSelectedPerson (id);
};
También cargamos una nueva persona si el usuario selecciona un nuevo elemento en la lista desplegable:
EmployeeSelector.SelectionChanged += (sender, e) => {
// Get ID
var id = DataSource.IDForIndex (EmployeeSelector.SelectedIndex);
LoadSelectedPerson (id);
};
Por último, rellenamos automáticamente el cuadro combinado y se muestra la persona con el primer elemento de la lista:
// Auto select the first person
EmployeeSelector.StringValue = DataSource.ValueForIndex (0);
Person = new PersonModel (Conn, DataSource.IDForIndex(0));
SQLite.NET ORM
Como se indicó anteriormente, mediante el uso del Administrador de relaciones de objetos (ORM) de SQLite.NET de código abierto, podemos reducir considerablemente la cantidad de código necesario para leer y escribir datos de una base de datos de SQLite. Puede que esta no sea la mejor ruta para enlazar datos debido a varios de los requisitos que la codificación de clave-valor y el enlace de datos tienen lugar en un objeto.
Según el sitio web de SQLite.Net, "SQLite es una biblioteca de software que implementa un motor de base de datos independiente, sin servidor, de configuración cero SQL y transaccional. SQLite es el motor de base de datos más implementado del mundo. El código fuente de SQLite está en el dominio público".
En las secciones siguientes, mostraremos cómo usar SQLite.Net para proporcionar datos para una vista de tabla.
Incluir el SQLite.net NuGet
SQLite.NET se presenta como un paquete NuGet que se incluye en la aplicación. Para poder agregar compatibilidad con bases de datos SQLite.NET, es necesario incluir este paquete.
Haga lo siguiente para agregar el paquete:
En la Panel de solución, haga clic con el botón derecho en la carpeta Paquetes y seleccione Agregar paquetes...
Escriba
SQLite.neten el cuadro deSQLite.nety seleccione la entrada sqlite-net:Haga clic en el botón Agregar paquete para finalizar.
Creación del modelo de datos
Vamos a agregar una nueva clase al proyecto y llamar a en OccupationModel . A continuación, vamos a editar el archivo DesplomeModel.cs y hacer que sea parecido al siguiente:
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
}
}
En primer lugar, se SQLite.NET ( ) y luego se exponen varias propiedades, cada una de las cuales se escribirá en la base de datos cuando se using Sqlite guarde este registro. La primera propiedad que hacemos como clave principal y la establecemos en incremento automático como se muestra a continuación:
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
Inicialización de la base de datos
Con los cambios realizados en nuestro modelo de datos para admitir la lectura y escritura en la base de datos, es necesario abrir una conexión a la base de datos e inicializarla en la primera ejecución. Vamos a agregar el código siguiente:
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;
}
En primer lugar, se obtiene una ruta de acceso a la base de datos (el escritorio del usuario en este caso) y se ve si la base de datos ya existe:
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);
A continuación, establecemos una conexión a la base de datos en la ruta de acceso que creamos anteriormente:
var conn = new SQLiteConnection (db);
Por último, creamos la tabla y agregamos algunos registros predeterminados:
// 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);
Agregar una vista de tabla
Como ejemplo de uso, agregaremos una vista de tabla a nuestra interfaz de usuario en el generador de interfaces de Xcode. Expondremos esta vista de tabla a través de una salida ( ) para que podamos acceder a OccupationTable ella a través de código de C#:
Exposición de una salida de vista de
A continuación, agregaremos las clases personalizadas para rellenar esta tabla con datos de la base de SQLite.NET datos.
Creación del origen de datos de tabla
Vamos a crear un origen de datos personalizado para proporcionar datos para nuestra tabla. En primer lugar, agregue una nueva clase TableORMDatasource denominada y haga que se parezca a la siguiente:
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
}
}
Cuando cree una instancia de esta clase más adelante, pasaremos la conexión de base de datos abierta SQLite.NET datos. El LoadOccupations método consulta la base de datos y copia los registros encontrados en la memoria (mediante nuestro OccupationModel modelo de datos).
Creación del delegado de tabla
La clase final que necesitamos es un delegado de tabla personalizado para mostrar la información que hemos cargado desde la base de SQLite.NET datos. Vamos a agregar un nuevo al TableORMDelegate proyecto y hacer que sea parecido al siguiente:
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
}
}
Aquí usamos la colección del origen de datos (que cargamos desde la base de datos SQLite.NET) para rellenar las columnas de la tabla a través de la invalidación OccupationsGetViewForItem del método .
Rellenar la tabla
Con todas las piezas en su lugar, vamos a rellenar la tabla cuando se infle desde el archivo .xib invalidando el método y haciendo que parezca lo AwakeFromNib siguiente:
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);
}
En primer lugar, se obtiene acceso a SQLite.NET base de datos, y se crea y se rellenará si aún no existe. A continuación, se crea una nueva instancia de nuestro origen de datos de tabla personalizado, se pasa la conexión de base de datos y se adjunta a la tabla. Por último, creamos una nueva instancia de nuestro delegado de tabla personalizado, pasamos nuestro origen de datos y lo adjuntamos a la tabla.
Resumen
En este artículo se ha realizado un análisis detallado del trabajo con enlace de datos y codificación de clave-valor con bases de datos SQLite en una aplicación xamarin.mac. En primer lugar, se ha visto la exposición de una clase de C# a mediante la codificación de clave-valor (KVC) y la observación Objective-C de clave-valor (KVO). A continuación, se mostró cómo usar una clase compatible con KVO y enlazarla a los elementos de la interfaz de usuario en el Interface Builder de Xcode. En el artículo también se ha abordado cómo trabajar con datos de SQLite a través SQLite.NET ORM y mostrar los datos en una vista de tabla.
Vínculos relacionados
- MacDatabase (ejemplo)
- Hello, Mac
- Enlace de datos y codificación de clave-valor
- Controles estándar
- Vistas de tabla
- Vistas de esquema
- Vistas de colecciones
- Guía de programación de codificación de clave-valor
- Introducción a los temas de programación de enlaces de Cocoa
- Introducción a la referencia de enlaces de Cocoa
- NSCollectionView
- Directrices de la interfaz humana de macOS

