Libro para desarrolladores en Mono:Capítulo 7
De Mono Hispano
Contents |
Bases de datos en Mono
ADO.NET
Firebird
Oracle
Sybase ASE
PostgreSQL
Conceptos básicos
Para acceder a PosgreSQL es necesario tener npgsql; npgsql es un data provider para .Net que permite desarrollar aplicaciones que puedan usar esta base de datos. Npgsql funciona con las versiones Postgresql 7.x y posteriores. Npgsql puede ser descargado desde este enlace
Los Namespace que deben agregarse a los proyectos son
using System.Data; using Npgsql;
Este es un ejemplo de una clase que establece una conexion con un servidor en la ip 127.0.0.1, en el puerto 5432, como usuario joe, con la contraseña secret, usando la base de datos joedata.
public class Example01 {
public static void Main(String[] args) {
string strCon = "Server=127.0.0.1;"+
"Port=5432;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
conn.Close();
}
}
Cadena de conexión
En la cadena de conexión existen un serie de parametros que pueden adecuarse según las necesidades, esta es una breve descripción de dichos parametros
Server: Direccion/Nombre del servidor Postgresql; Port: Puerto para conectarse; Protocol: Version del protocolo a usar, en vez de automatico; Integer 2 o 3; Database: Nombre de la base de datos. Por defecto el nombre del usuario; User Id: Nombre de usuario; Password: Contraseña para la autenticacion; SSL: Boolean. Controls whether to attempt a secure connection. Default = False; Pooling: Boolean. Controls whether connection pooling is used. Default = True; MinPoolSize: Min size of connection pool; MaxPoolSize: Max size of connection pool; Encoding: Codificacion a usar; Timeout: Tiempo a esperar para conexiones abiertas en segundos.
Consultas simples
La siguiente clase añade un registro a la tabla table1 capturando excepciones si es que estas se lanzan
/* CREATE TABLE table1 (a INT, b INT); */
public class Example02
{
public static void Main()
{
string strCon =
"Server=127.0.0.1;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
string strCommand = "INSERT INTO table1 (a,b) VALUES (1, 1)";
NpgsqlCommand command = new NpgsqlCommand(strCommand, conn);
Int32 rowsaffected;
try
{
rowsaffected = command.ExecuteNonQuery();
Console.WriteLine("Se añadieron {0} fila/s.", rowsaffected);
}
finally
{
conn.Close();
}
}
}
Cuando las sentencias que se ejecutan devuelven un valor único es posible usar el método ExecuteScalar() del objeto Command:
public class Example03
{
public static void Main()
{
string strCon =
"Server=127.0.0.1;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
string strCommand = "SELECT version()";
NpgsqlCommand command = new NpgsqlCommand(strCommand, conn);
string serverversion;
try
{
serverversion = (String)command.ExecuteScalar();
Console.WriteLine("Version del servidor PostgreSQL: {0}",
serverversion);
}
finally
{
conn.Close();
}
}
}
Para trabajar con un conjunto de datos en memoria, necesitamos el método ExecuteReader() del objeto NpgsqlCommand, como se muestra en el siguiente ejemplo que devuelve todos los registros de la tabla table1 y los imprime en consola.
public class Example04
{
public static void Main()
{
string strCon =
"Server=127.0.0.1;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
string strCommand = "SELECT * FROM table1";
NpgsqlCommand command = new NpgsqlCommand(strCommand, conn);
try
{
NpgsqlDataReader dr = command.ExecuteReader();
while(dr.Read())
{
for (int i = 0; i < dr.FieldCount; i++)
Console.Write("{0} \t", dr[i]);
Console.WriteLine();
}
}
finally
{
conn.Close();
}
}
Procedimientos almacenados
Para utilizar procedimientos almacenados se modifica la propiedad CommandType del objeto NpgsqlCommand con el valor CommandType.StoredProcedure
/*
Este ejemplo usa una funcion llamada funcC() con
la siguiente definicion:
CREATE FUNCTION funcC() RETURNS int8 AS '
SELECT COUNT(*) FROM table1;
' LANGUAGE 'sql';
Observa que el tipo devuelto por SELECT COUNT(*) cambio de
int4 a int8 en la version 7.3+. Para usar esta funcion en
un servidor <=7.2, cambia el tipo devuelto de int8 a int4.
*/
public class Example08
{
public static void Main()
{
string strCon =
"Server=127.0.0.1;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
try
{
NpgsqlCommand command = new NpgsqlCommand("funcC()", conn); //Procedimiento almacenado
command.CommandType = CommandType.StoredProcedure;
Object result = command.ExecuteScalar();
Console.WriteLine(result);
}
finally
{
conn.Close();
}
}
}
Asimismo, para añadir parámetros a los procedimientos almacenados con la propiedad Parameters del objeto NpgsqlCommand
public class Example09
{
public static void Main()
{
string strCon =
"Server=127.0.0.1;"+
"User Id=joe;"+
"Password=secret;"+
"Database=joedata;";
NpgsqlConnection conn = new NpgsqlConnection(strCon);
conn.Open();
try
{
NpgsqlCommand command = new NpgsqlCommand("funcC(:a)",conn);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add(new NpgsqlParameter("a", DbType.Int32));
command.Parameters[0].Value = 1;
Object result = command.ExecuteScalar();
Console.WriteLine(result);
}
finally
{
conn.Close();
}
}
}
SQLite
Microsoft SQL Server
MySQL
Actualmente existen varios drivers que se pueden utilizar para acceder a MySQL. Entre ellos podemos encontrar los siguientes MySQL Connector/Net ó MySQLDriverCS que se pueden descargar desde http://dev.mysql.com/downloads/dotnet.html.
Usando MySQLDriverCS:
Ejemplo práctico realizado con MySQLDriverCS:
using System;
using System.Data;
using MySQLDriverCS;
namespace EjemploConexion
{
public class MySQLConexion
{
private MySQLConnection Conexion;
public MySQLConexion()
{
}
public static void Main(args[])
{
/* Se crea la conexión mediante una cadena de conexión. */
Conexion = new MySQLConnection(new MySQLConnectionString("localhost",
"Schema",
"Usuario",
"Pass").AsString);
/* Se abre la conexión. */
Conexion.Open();
/* Comando SELECT. */
Datatable dt = new MySQLSelectCommand(Conexion,
new string[] {"NOMBRE","APELLIDOS"}, // Campos
new string[] {"miembros"}, // Tabla
null,
null,
null).Table;
/* Se recorren todas las filas. */
foreach(DataRow row in dt.Rows)
{
Console.Write(row["APELLIDOS"].ToString() + "\n");
Console.Write(row["NOMBRE"].ToString() + "\n\n");
}
/* Se cierra la conexión. */
Conexion.Close();
}
}
}
Usando MySQL Connector:
Este ejemplo supone que existe una base de datos tmp y una tabla table tbl_names en MySQL.
Creamos un script que se llama tbl_names.sql y el cual define lo siguiente:
CREATE TABLE tbl_names (name varchar(20),age smallint.description varchar(100));
Ahora para crear la base de datos tmp y cargar la tabla en una base de datos en MySql, hacemos lo siguiente:
$mysql -u usuario -p mysql> create database tmp; mysql> exit
y para cargar la tabla en la base de datos tmp usando nuestro script, hacemos lo siguiente:
$mysql -u usuario_mysql -p tmp < /ruta/a/script/tbl_names.sql
Ahora hay que descargar el ensamblado desde la siguiente liga http://dev.mysql.com/downloads/connector/net/5.0.html
Posteriormente hay que instalar en el GAC de Mono el ensamblado de la siguiente forma:
#gacutil -i MySql.Data.dll
Deberá quedar así dependiendo de la versión que haya descargado:
/usr/lib/mono/gac/MySql.Data/5.0.8.1__c5687fc88969c44d/MySql.Data.dll
Posteriormente cambie los permisos del propietario del ensamblado para que su aplicación pueda acceder y cargar su ensamblado a tiempo de ejecución
#chown usuario:grupo /usr/lib/mono/gac/MySql.Data/5.0.8.1__c5687fc88969c44d/MySql.Data.dll pruebe con chmod para no afectar el acceso a los demás usuarios.
Para compilar usando el ensamblado use:
$gmcs -r:System.Data.dll -r:/usr/lib/mono/gac/MySql.Data/5.0.8.1__c5687fc88969c44d/MySql.Data.dll archivo.cs
Ejemplo práctico realizado con MySQL Connector:
using System;
using System.Data;
using MySql.Data.MySqlClient;
public class TestMysql
{
public static void Main()
{
//declaramos la variables para la conexion y el comando
IDbConnection dbcon;
IDbCommand dbc;
//declaramos la variable para almacenar la cadena de conexion
string constring = "Server=localhost;Database=tmp;User ID=user;Password=pass;Pooling=false";
//declaramos la variables que almacena la cadena de la sentencia SQL de insercion
string sql = "INSERT INTO table VALUES('name',24,null)";
//una nueva instancia de la clase MySqlConnection con el parametro de la cadena de conexion
dbcon = new MySqlConnection(constring);
//abrimos la conexion
dbcon.Open();
//creamos el comando
dbc = dbcon.CreateCommand();
//asignamos la variable de cadena de la sentencia sql para el atributo del comando
dbc.CommandText = sql;
//ejecutamos el comando y regresa el numero total de renglones afectados en la insercion
int rows = dbc.ExecuteNonQuery();
//escribimos el resultado
Console.WriteLine("rows affected: " + rows);
//limpiamos variables
dbc.Dispose();
dbc = null;
dbcon.Close();
dbcon = null;
}
}
ORM (Object-Relational Mapping)
Introducción
ORM son las siglas de "Mapeado de Objetos-Relaciones" y consiste en una técnica de programación con el fin de transformar los conceptos de bases de datos relacionales a entidades de objetos y conseguir facilitar el acceso a datos desde aplicaciones programadas con el paradigma de Programación Orientada a Objetos. Así nos será mucho mas fácil manejar nuestros datos como objetos que como tablas. No simboliza toda la capa de datos, pero su utilización puede ahorrar hasta un 90% de desarrollo de esta. Entre algunas de sus posibilidades se encuentra la recuperación de objetos desde la base de datos, persistencia de estos, abstracción del RDBMS, cache, y otros.
Algunos de los motores de persistencia ORM que podemos utilizar, son:
NHibernate
Un Port del famoso Hibernate, sin duda uno de los más importantes ORM's libres que hay para Java, ha sido desarrollado para la arquitectura .Net y compatible con Mono. Podemos acceder a él desde la página web: http://www.nhibernate.org
Instalación
Ejemplo práctico
Veamos a NHibernate en funcionamiento. Para ello vamos a realizar un ejemplo de agenda. Siempre vamos a necesitar crear tres ficheros: el esquema SQL de la tabla, la clase que define el objeto y un fichero XML donde define la relación entre campos de la tabla y los atributos de la clase.
Nuestra tabla contactos alojará datos de nombre, apellidos, teléfono y fecha de nacimiento.
Estructura de la tabla (SQL)
contactos.sql
CREATE TABLE `contactos` ( `id_contacto` int(8) NOT NULL AUTO_INCREMENT, `nombre` varchar(25) NOT NULL DEFAULT '', `apellidos` varchar(50) NOT NULL DEFAULT '', `email` varchar(50) NOT NULL DEFAULT '', `fecha_nacimiento` DateTime NOT NULL DEFAULT '', PRIMARY KEY `id_contacto` (`id_contacto`) );
Clase que define nuestro objeto (C#)
La clase Contacto define toda la información de cada contacto encapsulada en un objecto.
Contacto.cs
using System;
class Contacto {
private int _id_contacto;
private string _nombre;
private string _apellidos;
private string _email;
private DateTime _fecha_nacimiento;
/* protected para que NHibernate pueda acceder */
protected int id_contacto {
get { return this._id_contacto; }
set { this._id_contacto = value; }
}
protected string nombre {
get { return this._nombre; }
set { this._nombre = value; }
}
protected string apellidos {
get { return this._apellidos; }
set { this._apellidos = value; }
}
protected string email{
get { return this._email; }
set { this._email = value; }
}
protected DateTime fecha_nacimiento {
get { return this._fecha_nacimiento; }
set { this._fecha_nacimiento = value; }
}
/* public */
public int IdContacto {
get { return this._id_contacto; }
}
public string Nombre {
get { return this._nombre; }
set { this._nombre = value; }
}
public string Apellidos {
get { return this._apellidos; }
set { this._apellidos = value; }
}
public string Email{
get { return this._email; }
set { this._email = value; }
}
public DateTime FechaNacimiento {
get { return this._fecha_nacimiento; }
set { this._fecha_nacimiento = value; }
}
/* Constructor */
public Contacto() {
}
}
Mapeando la tabla con la clase (XML)
Por último construiremos nuestro fichero de mapeado, el encargado de relacionar las variables de los objetos con los datos de las tablas. Es importante que el fichero tenga como extensión .hbm.xml, NHibernate buscará automáticamente los ficheros con dicha extensión y los procesará.
nhibernate.hbm.xml
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Contacto" table="contactos">
<id name="id_contacto" column="id_contacto" type="Int32" length="10">
<generator class="increment" />
</id>
<property name="nombre" column="nombre" type="String" length="25"/>
<property name="apellidos" type="String" length="50"/>
<property name="email" type="String" length="50"/>
<property name="fecha_nacimiento" type="Ticks" />
</class>
</hibernate-mapping>
Analicemos la estructura del fichero. Cada class que definamos hace referencia al mapeado de una clase vinculada con la tabla que le asignemos, en este caso la clase "Contacto" con la tabla "contactos". Dentro de ella deberemos especificar las variables que vamos a usar de dicha clase y vincularlo con la columna específica de la tabla que estamos trabajando. Usaremos property para vincular cada atributo de la clase con cada columna, usando "name" para el nombre de la variable de la clase y "column" el nombre de la columna de la tabla, véase que si el nombre de la columna es el mismo que el de la variable, no es necesario especificar nada, pues NHibernate usará el mismo nombre especificado en "name" como columna, y por último especificar el tipo de dato con "type" (Puede obtener una lista de los tipos usados en NHibernate y los tipos de datos con que se corresponde en: http://nhibernate.sourceforge.net/nh-docs/en/html/single/reference.html#mapping-types ) Si el esquema(tabla) utiliza algún tipo de columna clave, deberemos especificarlo con "id" de uso similar a "property" con el añadido de que podemos asignar un modelo de generación de claves para el campo, en este caso incrementado.
Haciendo uso de NHibernate
Para hacer uso de nuestro objeto crearemos un pequeño programa de consola:
Agenda.cs
using System;
using NHibernate;
using NHibernate.Cfg;
class Agenda {
public static void Main(string[] args)
{
Console.WriteLine("Iniciando Agenda...");
Configuration cfg = new Configuration();
cfg.AddAssembly("Agenda"); // Carga los *.hbm.xml
Console.WriteLine("Configuracion cargada");
ISessionFactory factory = cfg.BuildSessionFactory();
ISession session = factory.OpenSession();
ITransaction transaction = session.BeginTransaction();
Contacto miContacto = new Contacto();
miContacto.Nombre = "Jaime";
miContacto.Apellidos = "C";
miContacto.Email = "jaime.caceres at hazent.com";
miContacto.FechaNacimiento = new DateTime(1982,3,13);
// Realizamos la persistencia del objeto
session.Save(miContacto);
Console.WriteLine ("Contacto añadido, id_contacto: {0}", miContacto.IdContacto);
// Cierra la transacción y la conexión con la base de datos.
transaction.Commit();
session.Close();
}
}
Como ve, es realmente sencillo insertar objetos y manipularlos. Podríamos crear un manejador de Contactos y así crear nuestra capa de datos de la aplicación. Nos bastaría unas pocas líneas para generar un ContactosManager para la insercción, recuperacion, actualización o búsqueda de objetos Contacto.
Compilando
mcs -t:exe -r:System,System.Data,log4net,NHibernate -warn:4 -out:Agenda.exe -main:Agenda -resource:nhibernate.hbm.xml Contacto.cs Agenda.cs
Configurando
Agenda.exe.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<nhibernate>
<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />
<add key="hibernate.dialect" value="NHibernate.Dialect.MySQLDialect" />
<add key="hibernate.connection.driver_class" value="NHibernate.Driver.MySqlDataDriver" />
<add key="hibernate.connection.connection_string" value="Server=localhost;Database=monohispano;User ID=root;Password=;" />
</nhibernate>
</configuration>
Ejecutando
mono Agenda.exe
Salida en pantalla:
$ mono Agenda.exe Iniciando Agenda... Configuracion cargada Contacto añadido, id_contacto: 1
Gentle.Net
http://www.mertner.com/projects/gentle
OJB.Net
http://ojb-net.sourceforge.net/
DB4O
Conceptos básicos
Db4o es una base de datos orientada a objetos open source para java y .Net que permite tener persistencia de sus objetos de forma nativa. Esta base de datos soporta aplicaciones standalone, así como cliente/servidor en base a una dll que no tiene referencias externas.
Puede descargarse la versión para mono desde aquí
Ejemplo de un alta simple
using Db4objects.Db4o;
public class Prueba
{
public void Alta()
{
IObjectContainer dbcliente = Db4oFactory.OpenFile(“file.yap”);
Foo _foo=new Foo();
_foo.Entero=21;
_foo.Cadena="Texto";
dbcliente.Set(_foo); //Almaceno en la BD
dbcliente.Commit();
dbcliente.Close();
}
}
public class Foo
{
private int val1;
private string val2;
public int Entero
{
set { val1=value; }
get { return val1; }
}
public string Cadena
{
set { val2=value; }
get { return val2; }
}
}
Búsquedas en db4o
Db4o soporta tres tipos de búsquedas para recuperar los objetos almacenados:
- Query By Example (QBE)
- Consultas nativas (Native Querys)
- Consultas SODA (Simple Object Data Access)
QBE
Las consultas QBE son las más simples, y se utilizan cuando se requieren operadores lógicos o consultas muy sofisticadas.
Ejemplo de búsqueda QBE
public class Prueba
{
public void Busqueda()
{
IObjectContainer dbcliente = Db4oFactory.OpenFile("File.yap");
Foo _foo = new Foo();
_foo.Entero = 21;
IObjectSet _resultado=dbcliente.Get(_foo); //Devuelve todos los objetos que
foreach (Foo _fooRes in _resultado) //tengan 21 en 'Entero'
{
dbcliente.Delete(_fooRes);//Borro el objeto de la BD
}
dbcliente.Close();
}
}
Native Querys
Las consultas nativas se llevan a cabo directamente con el lenguaje de programación que desarrollamos (en este caso C#) en base a una expresión que se ejecutara por medio de un delegado. Esta consulta será analizada y optimizada para posteriormente ser convertida a SODA para su ejecución
Ejemplo de una consulta nativa simple
public class Prueba
{
public void BusquedaNativa()
{
IObjectContainer dbcliente = Db4oFactory.OpenFile("File.yap");
IList<Foo> ConjuntoFoos = dbcliente.Query<Foo>(delegate(Foo _foo)
{
return _foo.Entero >= 21 && //Devuelve todos los objetos que
_foo.Cadena.Trim().Length > 0;//sean mayores o igual a 21 en 'Entero'
//Y contengan algun texto en 'Cadena'
});
foreach (Foo _fooRes in ConjuntoFoos)
{
_fooRes.Entero = 0;
dbcliente.Set(_fooRes);//Actualizo el objeto
}
dbcliente.Close();
}
}
Consultas SODA
Las consultas SODA se basan en grafos de consulta, que expresan las clases o los campos de las clases, y como se menciono anteriormente son la base de las consultas nativas. SODA utiliza cadenas para identificar los campos, lo que las hace propensas a errores que no serán detectados al momento de compilar (al contrario de las native querys) sin embargo pueden lograr ser más optimizadas que las consultas nativas.
Ejemplo de una consulta SODA
public class Prueba
{
public void BusquedaSODA()
{
IObjectContainer dbcliente = Db4oFactory.OpenFile("File.yap");
Db4objects.Db4o.Query.IQuery _Consulta = dbcliente.Query();
_Consulta.Constrain(typeof(Foo));
_Consulta.Descend("Numero").Constrain(21).Greater(); //Devuelve todos los objetos
//que son mayores a 21 en 'Numero'
IObjectSet ConjuntoFoos = _Consulta.Execute();
foreach (Foo _fooRes in ConjuntoFoos)
{
_fooRes.Entero = 0;
dbcliente.Set(_fooRes);//Actualizo el objeto
}
dbcliente.Close();
}
}

Powered by MediaWiki