miércoles, 25 de junio de 2008

Generacion automatica de backup en MSSQL 2005 Express con un Windows Service (Parte II)

La instalacion del servicio tiene como requerimiento que creemos una clase del tipo InstallerClass como el que creamos anteriormente llamado "InstallerService.cs", para la implementacion sigamos los siguientes pasos.

1er paso: Debemos importar la libreria System.ServiceProcess, esta nos permite definir los parametros de instalacion de nuestro servicio.
using System.ServiceProcess
2do paso: Sobre escribimos el metodo InstallerService y definimos los parametros de instalacion de nuestro service.
public InstallerService()
{
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
ServiceInstaller serviceInstaller = new ServiceInstaller();

//# Service Account Information

serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Password = null;

//# Service Information

serviceInstaller.DisplayName = "Backup Service";
serviceInstaller.StartType = ServiceStartMode.Automatic;

//# This must be identical to the WindowsService.ServiceBase name

//# set in the constructor of WindowsService.cs

serviceInstaller.ServiceName = "BackupService";

this.Installers.Add(serviceProcessInstaller);
this.Installers.Add(serviceInstaller);
}
Con esto tenemos listo nuestra clase instaladora que nos permitira instalar el service en el sistema.

3er paso: la instalacion y desintalacion del service se hace a travez de comandos por lo que usamos la consola de comandos (CMD). Para instalar usamos "i" y para desinstalar la "u"
InstallUtil /i BackupService.exe
InstallUtil /u BackupService.exe
Lo que hice para facilitar la instalacion y desinstalacion del service fue crear 2 archivo bat donde ingreso dichos comandos.

Aqui te adjunto el codigo del service para la creacion de backup

Generacion automatica de backup en MSSQL 2005 Express con un Windows Service (Parte I)

La creación de backup de una base de datos es un proceso importante y al mismo tiempo monotomo para los que se encargan de hacerlos, en el caso de MSSQL 2005 este proceso puede ser realizado por tareas programadas, pero en el caso de MSSQL 2005 Express Edition que no posee este gestionador de tareas automaticas solo queda hacerlo manualmente cada cierto tiempo :(.

Para no tener que hacer esta tediosa tarea cada cierto periodo ( cosa que tenia que hacerlo yo :S ) , hice un pequeña aplicacion que en si es un Windows Service que genera un backup automaticamente cada dia.

1er paso: Crear un nuevo proyectollamado BackupServicio del tipo "Windows Service" con C#, esto creara 2 items por defecto el primero llamado Program.cs y Service1.cs lo renombramos BackupService.cs, ademas agregamos un item del tipo "InstallerClass" llamandolo InstallerService.cs. La clase BackupService.cs es donde ira la logica para generar el backup y en la clase InstallerService.cs ira la logica para la instalacion del servicio en el sistema.

2do paso: Agregamos 2 nuevas referencias a nuestro proyecto llamados Microsoft.SqlServer.Smo y Microsoft.SqlServer.ConnectionInfo, estos son importantes para poder hacer el backup de la base de datos y poder llamar a estas librerias.
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo
3er paso: Crearemos el metodo que realizara el trabajo de generar la base de datos, primero indicamos el nombre del archivo de backup para ello se toma como referencia la fecha actual. Segundo, indicamos el dispositivo donde se generara el backup por lo que indicamos "DeviceType.File" para crearlo como archivo, este se crea por defecto en la carpeta de backup del MSSQL. Por ultimo, indicamos la base de datos que queremos crearle su backup que esta representado por el valor de "this.database" y el nombre del servidor cuyo valor es representado por "this.server".
private void backupDB()
{
try
{
string fileBackup = "BACKUP_" + DateTime.Now.ToString("yyyyMMdd") + ".bak";

BackupDeviceItem bdi = new BackupDeviceItem(fileBackup, DeviceType.File);

Backup bu = new Backup();

bu.Database = this.database;
bu.Devices.Add(bdi);
bu.Initialize = true;

Server server = new Server(this.server);
bu.SqlBackup(server);
}
catch (Exception ex)
{
throw ex;
}
}
Este metodo solo se llamara cuando la hora señalada sea identica a la hora del sistema, para ello el servicio verifica cada segundo dicha condición.

4to paso: Creamos un Timer para que cada segundo verifique si la hora para generar el backup.
using System.Timers;

Timer timer = new Timer();

private void OnElapsedTime(object source, ElapsedEventArgs e)
{
try
{
String tm1 = DateTime.Now.Hour.ToString() + ':' + DateTime.Now.Minute.ToString() + ':' + DateTime.Now.Second.ToString();
String tm2 = this.hour + ':' + this.minute + ':' + this.second;

if (tm1.Equals(tm2))
{
this.backupDB();
EventLog.WriteEntry(this.ServiceName, "Se genero el backup con fecha " + DateTime.Now.ToShortDateString(), System.Diagnostics.EventLogEntryType.Information);
}
}
catch (Exception ex)
{
EventLog.WriteEntry(this.ServiceName, "Error: " + ex.ToString(), System.Diagnostics.EventLogEntryType.Error);
}
}
5to paso: Inicializamos el Timer con la configuraciones para que valide cada segundo la condicion de la hora. Para ello usamos el metodo "OnStart" que es el metodo que se ejecuta al momento de inicial el servicio. Ademas, vemos que usamos ElapsedEventHandler este metodo no sirve para decirle que en cada intervalo de tiempo ejecute el metodo OnElapsedTime que contiene la condicion para verificar la hora de sistema.
protected override void OnStart(string[] args)
{
try
{
this.loadConfiguration();

timer.Elapsed += new ElapsedEventHandler(OnElapsedTime);

timer.Interval = 1000;

timer.Enabled = true;
}
catch (Exception ex)
{
EventLog.WriteEntry(this.ServiceName, "Error: " + ex.ToString(), System.Diagnostics.EventLogEntryType.Error);
this.Stop();
}
}
Con esta breve descripcion puedo definir el funcionamiento del programa que aunque ya esta terminado no se puede probar hasta que se intale el servicio en el sistema.

Para instalacion del servicio sigamos sigamo con la segunda parte Aqui

viernes, 6 de junio de 2008

Error al devolver, eliminar o actualizar con Propel

Problema
La forma normal y la mas usada para devolver, eliminar o actualizar un registro usando PROPEL

Devolver un registro:
<?php
$product = ProductosPeer::retrieveByPK($id);
print $product->getNombre();
?>
Eliminar un registro:
<?php
$product = ProductosPeer::retrieveByPK($id);
$product->delete();
?>
Actualizar un registro:
<?php
$product = ProductosPeer::retrieveByPK($id);
$product->setNombre('TESTING');
$product->save();
?>
Funcionan siempre en la mayoria de casos, pero existe un error que se produce cuando se define en la base de datos un campo de fecha que acepte valores '0000-00-00' o que contenga este valor.

Para especificar como se produce este error veremos la primera linea de codigo donde obtenemos todo el registro segun su ID
<?php
$product = ProductosPeer::retrieveByPK($id);
?>
Es justo en este momento donde se produce un error, ya que lo que quiere es asignar al objeto todos los campos del registro obtenido. Esto no seria ningun problema si es que se uno de los campos del registro es del tipo date (fecha) y tiene asignado el valor '0000-00-00' . Esto produce un error y detiene la operacion.

Uno similar a este:

Fatal error: Uncaught exception 'PropelException' with message 'Error populating Productos object [wrapped: Unable to convert value at column 6 to timestamp: 0000-00-00 00:00:00]' in /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/om/BaseProductos.php:383 Stack trace: #0 /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/om/BaseProductosPeer.php(317): BaseProductos->hydrate(Object(MySQLResultSet)) #1 /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/om/BaseProductosPeer.php(265): BaseProductosPeer::populateObjects(Object(MySQLResultSet)) #2 /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/om/BaseProductosPeer.php(548): BaseProductosPeer::doSelect(Object(Criteria), Object(MySQLConnection)) #3 /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/Productos.php(100): BaseProductosPeer::retrieveByPK('73') #4 /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/index.php(165): Productos->editVisita('73') #5 {main} in /nfs/c02/h03/mnt/27113/domains/qtregalo.com/html/demo/libs/model/classes/om/BaseProductos.php on line 383

Solucion
Encontre una pequeña solucion para evitar (no arreglar) este problema. Para ello, no asigno todos los campos del registro obtenido desde la base de dato. Si no, utilizo el objeto CRITERIA que me servira para listarlo en un objeto de tipo mas generico del tipo RECORD

Devolver un registro:
<?php
// Genero una consulta para ver si existe el producto con ID especificado
$c = new Criteria();
$c->addSelectColumn(ProductosPeer::NOMBRE);
$c->add(ProductosPeer::ID_PRODUCTO,$id, Criteria::EQUAL);
$rs = ProductosPeer::doSelectRS($c);

// Verifica si existe algun registro y si es asi solo devolvera un registro
if($rs->next()){
print $rs->getString(1)
}
Eliminar un registro:
<?php
// Genero una consulta para ver si existe el producto con ID especificado
$c = new Criteria();
$c->add(ProductosPeer::ID_PRODUCTO,$id, Criteria::EQUAL);

// Llama al metodo eliminar para que elimine el registro devuelto por la consulta
ProductosPeer::doDelete($c);
?>
Actualizar un registro:
<?php
// Variable de conexion del propel
$conn = Propel::getConnection('proyecto');

// Criteria para elegir el registro que se desea modificar
$c1 = new Criteria();
$c1->add(ProductosPeer::ID_PRODUCTO,$id, Criteria::EQUAL);

// Criteria para actualizar el campo del registro
$c2 = new Criteria();
$c2->add(ProductosPeer::NOMBRE,$data['productName']);

// Llamar al metodo para actualizar
BasePeer::doUpdate($c1, $c2, $conn);
?>

miércoles, 4 de junio de 2008

Retornar registros separada por comas con MYSQL

La otra vez buscando una solucion optimizada para poder listar todos los registros de un campo obtenidas de una consulta de base de datos, pero que se muestren separadas con comas (,)

Bueno ya lo habia solucionado usando PHP como lenguaje y que consistia en retonar todas los registros desde la base de datos MYSQL 5
SELECT nombre
FROM FILTROS
WHERE id_filtroPadre = 169
Posteriormente hice un bucle por cada fila y la unia en una cadena (string) separando cada fila con una coma(,).

Problema
Tenia que hacer 2 pasos para poder obtener la lista de registros separados por comas y que hacia mas lento el proceso. Ya que, tenia que obtener los datos de la bd y posteriormente recorrerlos uno por uno, asignarlos a una cadena y separarlas por comas.

Esto dependiento de la cantidad de registros obtenidos iba a causarme un problema de performace sobre el sistema.

Solucion
Investigando encontre una forma de que el mismo MYSQL me devuelva los registros separados por comas y mostrados en un solo registro.

Esta funcion se llama GROUP_CONCAT y lo que hace es agrupar todos los registros obtenidos de la consulta y concatenarlos (juntarlos) en un registro
SELECT GROUP_CONCAT(nombre SEPARATOR ', ') 'nombres'
FROM FILTROS
WHERE id_filtroPadre = 169
Esta forma mejoro la performace del sistema considerablemente, ya que no dependia de hacer otro paso para poderla mostrar de la forma que deseaba.

Link oficial
http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat