viernes, 24 de mayo de 2013

Esqueleto de aplicación con pestañas en vez de formularios en C#

Están muy de moda las pestañas en las aplicaciones. Estas nos permiten tener bastante ordenadas todas las ventanas de la aplicación con las que estemos trabajando simultáneamente.

La idea consiste en usar "Controles de Usuarios" (UserControl) en vez de formularios tradicionales y abrirlos y crearlos en tiempo de ejecución en pestañas (TabPage) de un TabControl.

Empezamos por crear un nuevo proyecto de Windows Forms y le añadimos un menú (MenuStrip) con todas las opciones que necesitemos. Añadimos un TabControl para ir abriendo las distintas pestañas y un botón para cerrarlas.




Para crear los "Controles de Usuario" haremos botón derecho sobre el proyecto, luego Agregar / Nuevo Elemento y seleccionamos "Control de Usuario".



La función que se propone para abrir las pestañas es:
01 private void nuevaPestana(UserControl uc, String titulo)
02 {
03         int indice = indicePestanaAbierta(titulo);
04         if (indice < 0) {
05                 TabPage tab = new TabPage();
06                 tab.Text = titulo;
07                 tab.Controls.Add(uc);
08                 tabMain.TabPages.Add(tab);
09                 tabMain.SelectedTab = tab;
10         } else {
11                 //si ya existe vamos a limpiar el usercontrol y volverlo a cargar en la misma pestaña
12                 tabMain.TabPages[indice].Controls.Clear();
13                 tabMain.TabPages[indice].Controls.Add(uc);
14                 tabMain.SelectedTab = tabMain.TabPages[indice];
15         }
16 }
Con esta función controlamos también que si ya existe esa pestaña, nos la refresque en vez de abrir otra.
01 private int indicePestanaAbierta(String titulo)
02 {
03         Boolean enc = false;
04         int i = 0;
05         while ((i < tabMain.TabPages.Count) && (enc == false)) {
06                 if (tabMain.TabPages[i].Text == titulo)
07                         enc = true;
08                 else
09                         i++;
10         }
11         if (enc)
12                 return i;
13         else
14                 return -1;
15 }
El evento del botón para cerrar las pestañas sería algo así.
01 private void btnCerrar_Click(object sender, EventArgs e)
02 {
03         int indice = tabMain.SelectedIndex;
04         if (indice > 0) { //no queremos cerrar nunca la inicial.
05                 tabMain.TabPages.Remove(tabMain.SelectedTab);
06                 tabMain.SelectedIndex = indice - 1;
07                 GC.Collect();
08         }
09 }
El evento de cualquiera de las opciones de menú para abrir las pestañas sería:
01 private void configuracionGeneralToolStripMenuItem_Click(object sender, EventArgs e)
02 
03 {
04         UCconfiguracionGeneral uc = new UCconfiguracionGeneral();
05         nuevaPestana(uc, "Configuración");
06 
07 }
El ejemplo completo, se puede descargar desde aquí.

martes, 21 de mayo de 2013

Quitar estado "Suspect" de Bases de Datos de SQL Server 2008

Hay veces que nuestras bases de datos pueden aparecer "sin previo aviso" en estado Suspect... normalmente esto ocurre porque el dispositivo físico donde están los ficheros de la base de datos no se ha inicializado antes que el propio servicio de SQL Server 2008. Por tanto la base de datos se marca de esta forma para impedir su utilización. Si este es el motivo, normalmente basta con reiniciar el servicio de SQL Server y la base de datos aparecerá disponible de nuevo. 

Muchas veces esto no es suficiente, o realmente lo que provoca el estado Suspect es una corrupción de alguno de los ficheros de la base de datos, para estos casos podemos seguir el siguiente proceso.


Ponemos la base de datos en estado de emergencia
01 ALTER DATABASE miBaseDeDatos SET EMERGENCY; 
Ponemos la base de datos en modo de usuario único para asegurarnos que solo nosotros trabajamos en ella
01 ALTER DATABASE miBaseDeDatos SET SINGLE_USER; 
Realizamos la reparación de la base de datos permitiendo la pérdida de datos (el asumir pérdida de datos puede no ser necesario, para no permitirlo usaremos el parametro REPAIR_REBUILD)
01 DBCC checkdb ('miBaseDeDatos', REPAIR_ALLOW_DATA_LOSS);
Volvemos a poner a poner la base de datos disponible.

01 ALTER DATABASE miBaseDeDatos SET ONLINE; 
Por último permitimos multiples conexiones.
01 ALTER DATABASE miBaseDeDatos SET MULTI_USER;

viernes, 17 de mayo de 2013

Ejemplo de uso de exepciones personalizadas en SQL Server

A veces es interesante crear excepciones personalizadas en una base de datos para que se disparen cuando realicemos una acción que no queramos permitir. Para explicarlo, propongo el siguiente ejemplo:
01 create database BDExcepciones 
02 GO
03 use BDExcepciones 
04 GO
05 create table Coches
06 ( 
07  id int identity primary key, 
08  marca varchar(20),   
09  descripcion varchar(100), 
10  matricula varchar(20) 
11 ) 
12 GO
Introducimos unos datos en la tabla
01 insert into Coches (marca, descripcion, matricula)  
02  VALUES ('Mercedes', 'El Coche del Roman Azul Cielo', 'NOTEPEGA'); 
03 insert into Coches (marca, descripcion, matricula)  
04  VALUES ('Opel Corsa', 'El corsita', 'PALABODA');  
05 insert into Coches (marca, descripcion, matricula)  
06  VALUES ('Rover 25', 'A ver lo que dura', 'ROTO');  
07 insert into Coches (marca, descripcion, matricula)         
08  VALUES ('Ford Focus', 'El coche nuevo', 'COCHENUEVO');  
Creamos excepciones.Los identificadores deben ser a partir del 50001 y la severidad debe ser 16 para que se trate como una excepción. Siempre hay que definir el mensaje en ingles y luego en español.
01 use master go
02 sp_addmessage 50002, 11, 'Ya existe un coche con esa matricula', 'us_english'; 
03 go
04 sp_addmessage 50002, 11, 'Ya existe un coche con esa matricula', 'spanish'; 
05 go
06 sp_addmessage 50003, 16, 'Ya existe un coche con esa matricula', 'us_english'; 
07 go
08 sp_addmessage 50003, 16, 'Ya existe un coche con esa matricula', 'spanish'; 
09 go
10 use BDExcepciones go 
Para lanzarla usaremos: RAISERROR (50002, 11, 1). Creamos un procedimiento que inserta un coche y si su matricula existe nos devuelve una excepcion. 
01 ALTER PROCEDURE InsertarCoche
02  @marca varchar(20),
03  @descripcion varchar(100),
04  @matricula varchar(20) AS
05 BEGIN
06  SELECT * FROM Coches WHERE matricula = @matricula 
07  if @@ROWCOUNT = 0 
08  BEGIN
09   INSERT INTO Coches (marca, descripcion, matricula) VALUES (@marca, @descripcion, @matricula);
10  END 
11  ELSE 
12  BEGIN
13   RAISERROR (50002, 11, 1)
14  END
15 END; 
16 -- lo probamos 
17 execute InsertarCoche 'cochenuevo', 'para cuando', 'ROTO'; 
Ahora vamos a hacer el mismo ejemplo pero en el trigger antes de insertar.
01 ALTER PROCEDURE InsertarCoche
02  @marca varchar(20),
03  @descripcion varchar(100),
04  @matricula varchar(20) AS
05 BEGIN
06  SELECT * FROM Coches WHERE matricula = @matricula 
07  if @@ROWCOUNT = 0 
08  BEGIN
09   INSERT INTO Coches (marca, descripcion, matricula) VALUES (@marca, @descripcion, @matricula);
10  END 
11  ELSE 
12  BEGIN
13   RAISERROR (50002, 11, 1)
14  END
15 END; 
16 -- lo probamos 
17 execute InsertarCoche 'cochenuevo', 'para cuando', 'ROTO'; 
Aunque personalmente, a estas alturas prefiero usar la base de datos tan solo como almacén de datos, y delegar a la capa de negocio toda la programación que de otra forma iría en ella, puede que en algún momento nos sea de utilidad.

miércoles, 15 de mayo de 2013

Ejemplo de Aplicación sobre diferentes motores de base de datos con Entity Framework

Uno de las características que más me gusta el Entity Framework es la abstracción del motor de base de datos y que nos permite crear aplicaciones sin atarnos a un motor especifico, pudiendo (teniendo en cuenta ciertas limitaciones) que la misma aplicación funcione sobre distintos motores de bases de datos.


Para el ejemplo vamos a seguir el enfoque "model first", es decir primero crearemos el modelo la herramienta de visual studio y luego generaremos las instrucciones DDL para crear la base de datos en SQLSERVER.


Creamos un proyecto de Visual estudio nuevo como aplicación de consola. Yo uso Visual Studio 2012 premium y compilando la aplicación para el .Net Framework 4. Pulsamos botón derecho sobre el proyecto, y Agregar nuevo modelo. Seleccionamos ADO.NET Entity data model y seleccionamos la opción Empty Model ya que vamos a usar el enfoque "model first".




Una vez creemos el modelo como en la imagen, vamos a generar el código DDL para SQLServer 2008. Deberemos tener instalados los conectores apropiados, yo uso los de Devart (podéis descargarlos aquí  Hay que tener en cuenta a la hora de crear el modelo las limitaciones de tipos de datos que tengan cada motor.


Para ello pulsamos botón derecho y en propiedades vemos que tenemos seleccionada en DDL Generation Template la de SQLServer (SSDLToSQL10.tt), y luego botón derecho de nuevo en el modelo y le damos a Generate database from model y seguimos el asistente. repetiremos este proceso para los tres motores, cambiando en las propiedades del modelo la template DDL que queremos usar, de esta forma conseguimos los script de las bases de datos para los motores que queramos, en mi caso: SQLServer, MySQL, y SQLite.

Ahora vamos a crear los ficheros ssdl que mapean cada motor de base de datos, para ello agregamos un nuevo elemento al proyecto de tipo XML y lo llamamos por ejemplo "modeloMySQL.ssdl" y le activamos la propiedad de copiar siempre al directorio de destino. hacemos lo mismo con los distintos motores de bases de datos.


Hacemos botón derecho sobre el modelo (el .edmx) y le damos a "abrir con" y seleccionamos Editor XML(texto), tenemos que copiar el nodo completo que se llama "Schema" y pegarlo en los .ssdl que creamos en el paso anterior. Por ultimo buscamos el atributo provider del nodo Schema y cambiamos su valor por el provider necesario. Los atributos Schema de los nodos EntitySet los dejaremos vacíos para MySQL y SQLite.
<Schema Namespace="ModeloSQLServer.Store" Alias="Self" Provider="Devart.Data.MySql" ProviderManifestTok ... 
Por ultimo para terminar de configurar las conexiones, creamos las cadenas de conexión en el app.config de la siguiente manera:
01 <connectionStrings>
02 <add name="ModeloSQLServerContainer"
03 
04 connectionString="metadata=res://*/ModeloSQLServer.csdl|res://*/ModeloSQLServer.ssdl|res://*/ModeloSQLServer.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=pc-matos\sqlexpress;initial catalog=ejEFmultiDB;persist security info=True;user id=matos;password=blabla;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
05 <add name="ModeloMySQLContainer"
06 
07 connectionString="metadata=res://*/ModeloSQLServer.csdl|ModeloMySQL.ssdl|res://*/ModeloSQLServer.msl;provider=Devart.Data.MySql;provider connection string=&quot;user id=root;host=localhost;database=ejEFmultiDB;persist security info=True&quot;" providerName="System.Data.EntityClient" />
08 
09 <add name="ModeloSQLiteContainer"
10 
11 connectionString="metadata=res://*/ModeloSQLServer.csdl|ModeloSQLite.ssdl|res://*/ModeloSQLServer.msl;provider=Devart.Data.SQLite;provider connection string=&quot;data source=C:\SQLite\ejEFmultiDB.db&quot;" providerName="System.Data.EntityClient" />
12 
13 </connectionStrings>
Vamos a empezar a escribir código y lo primero que haremos es incluir el ensamblado System.Configuration a las referencias y pondremos su respectivo using.

Para crear el contexto en función de la conexión a cada motor de base de datos, lo hacemos de la siguiente forma:
01 string conexionMySql = ConfigurationManager.ConnectionStrings["ModeloMySQLContainer"].ConnectionString;
02 using (ModeloSQLServerContainer context = new ModeloSQLServerContainer(conexionMySql))
03 {
04         //bloque de código que queramos realizar.
05 }
El ejemplo completo, se puede descargar desde aquí.