Mostrando entradas con la etiqueta programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta programación. Mostrar todas las entradas

jueves, 4 de noviembre de 2010

Enviar ficheros por FTP usando Powershell

Con este script de Powershell podemos automatizar el envío por FTP de todos los ficheros que existan en un directorio y cumplan con un determinado filtro (por ejemplo, todos los .txt).

Me he basado en este post para hacerlo: How to upload a file, the PowerShell way

Actualización 17/11/2010: añado $R.proxy = $null para que no de problemas con proxys HTTP :)


#Directorio del que obtenemos los ficheros
$source_folder="c:\temp\"

#Filtro que deben cumplir los ficheros que se envían
$files_filter=$source_folder + "*.txt"

#Archivo donde se guardará el log del proceso
$log_file="c:\FTP.log"

#URL de nuestro servidor FTP
$FTP_url="ftp://myserver.ftp/destino/"
$FTP_user="usuario"
$FTP_password="password"

# Obtenemos todos los ficheros del directorio que cumplan con el filtro
foreach ($filename in Get-Childitem $files_filter)
{
#Path completo del fichero de origen que se enviará por FTP
$Source=$source_folder + $filename.name

#Guardamos una línea en el archivo de logs
$DATE=Get-Date
Add-Content $log_file "[INFO]: $DATE - $Source"

#Path completo del fichero de destino
$Destination=$FTP_url + $filename.name

#Aquí comienza el control de errores, se verá más abajo con el "trap"
&{
#Usamos el metodo estático Create para crear una petición web.
#Pasamos el destino como argumento y lo convertimos
#en un objeto FtpWebRequest.
$R=[System.Net.FtpWebRequest][System.Net.WebRequest]::Create($Destination)

#Especificamos a la petición cómo nos loguearemos
#(usando un objeto NetworkCredential)
$R.Credentials = New-Object System.Net.NetworkCredential($FTP_user, $FTP_password)
$R.proxy = $null
#... y qué tipo de método se usará. Un file upload.
$R.Method = "STOR"

#Aquí se usa el método más simple para obtener los bytes
#de un fichero a un array de bytes.
$FileContens = [System.IO.File]::ReadAllBytes($Source)

#Finalmente, se ponen el array de bytes en la petición web.
$S = $R.GetRequestStream()
$S.Write($FileContens, 0, $FileContens.Length)

#Se cierran el stream y se libera el objeto
$S.Close()
$S.Dispose()
}
trap [SystemException]
{
#Si se produce un error, lo guardamos en el fichero de logs
#y continuamos con el siguiente fichero
$ERROR=$_
$DATE=Get-date
Add-Content $log_file "[ERROR]: $DATE - $ERROR"
continue
}
}



Ahora lo suyo es guardarnos el script en un fichero .ps1 y crear una tarea programada que lo ejecute, por ejemplo, que cada media hora compruebe el directorio y envíe los ficheros.

Y ya que estamos, podemos crear la tarea programada con Powershell:

SCHTASKS /Create /RL HIGHEST /SC MINUTE /MO 30 /TN "Enviar ficheros por FTP" /NP /TR 'powershell.exe –Command "c:\enviar_ficheros_FTP.ps1"'

Bola extra
Tal vez os interese también obtener los ficheros de un FTP: How to download a file, the PowerShell way

Happy codding ;)

martes, 11 de mayo de 2010

Movimiento NoSQL y MongoDB

Os dejo un interesante artículo de Ted Neward sobre MongoDB:

Going NoSQL with MongoDB

Y sobre dicho artículo, una reflexión:

¿Consideráis el movimiento NoSQL como un paso adelante o un paso atrás?

Yo lo veo más como algo que cubre una necesidad. No siempre necesitaremos una base de SGDB como SQL Server, Oracle o MySQL, de la que escasamente vamos a aprovechar un 10 o 20% de sus funcionalidades. Así que siempre está bien tener opciones como ésta, que estará más limitada, pero se puede adaptar mucho mejor a nuestros requerimientos.

¿Qué opináis?

miércoles, 9 de diciembre de 2009

Imprimir el contenido de un DataGridView con PrintDocument (en VB.NET)

Dada la cantidad de comentarios (más de 10 :P) que se produjeron en el post en el que explicaba como imprimir el contenido de un datagrid, me veo en la obligación a hacer el mismo ejemplo en VB.NET, sobre todo porque el código que puse en los comentarios tenía algún error.

Recordando el ejemplo, se trata de un formulario que contiene:
- un datagrid llamado DataGridView1
- un botón llamado Button1
- un PrintDocument llamado PrintDocument1

Y aquí tenéis el código:



Public Class Form1

' Variable a nivel de clase para recordar en qué punto nos hemos quedado
Dim i As Integer = 0

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Fill_DataGrid()
End Sub

Private Sub Fill_DataGrid()

' TODO: rellenar con el código que obtiene los datos de donde sea necesario
' Por ejemplo:

Me.DataGridView1.DataSource = dataSet
Me.DataGridView1.DataMember = "Table"
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Me.PrintDocument1.Print()
End Sub

Private Sub printDocument1_PrintPage(ByVal sender As Object, ByVal e As System.Drawing.Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
' Definimos la fuente que vamos a usar para imprimir
' en este caso Arial de 10
Dim printFont As System.Drawing.Font = New Font("Arial", 10)
Dim topMargin As Double = e.MarginBounds.Top
Dim yPos As Double = 0
Dim linesPerPage As Double = 0
Dim count As Integer = 0
Dim texto As String = ""
Dim row As System.Windows.Forms.DataGridViewRow

' Calculamos el número de líneas que caben en cada página
linesPerPage = e.MarginBounds.Height / printFont.GetHeight(e.Graphics)

' Imprimimos las cabeceras
Dim header As DataGridViewHeaderCell
For Each column As DataGridViewColumn In DataGridView1.Columns
header = column.HeaderCell
texto += vbTab + header.FormattedValue.ToString()
Next

yPos = topMargin + (count * printFont.GetHeight(e.Graphics))
e.Graphics.DrawString(texto, printFont, System.Drawing.Brushes.Black, 10, yPos)
' Dejamos una línea de separación
count += 2

' Recorremos las filas del DataGridView hasta que llegemos
' a las líneas que nos caben en cada página o al final del grid.
While count < linesPerPage AndAlso i < DataGridView1.Rows.Count
row = DataGridView1.Rows(i)
texto = ""
For Each celda As System.Windows.Forms.DataGridViewCell In row.Cells
'Comprobamos que la celda tenga algún valor, en caso de
'permitir añadir filas esto es muy importante
If celda.Value IsNot Nothing Then
texto += vbTab + celda.Value.ToString()
End If
Next

' Calculamos la posición en la que se escribe la línea
yPos = topMargin + (count * printFont.GetHeight(e.Graphics))

' Escribimos la línea con el objeto Graphics
e.Graphics.DrawString(texto, printFont, System.Drawing.Brushes.Black, 10, yPos)
' Incrementamos los contadores
count += 1
i += 1
End While

' Una vez fuera del bucle comprobamos si nos quedan más filas
' por imprimir, si quedan saldrán en la siguente página
If i < DataGridView1.Rows.Count Then
e.HasMorePages = True
Else
' si llegamos al final, se establece HasMorePages a
' false para que se acabe la impresión
e.HasMorePages = False
' Es necesario poner el contador a 0 porque, por ejemplo si se hace
' una impresión desde PrintPreviewDialog, se vuelve disparar este
' evento como si fuese la primera vez, y si i está con el valor de la
' última fila del grid no se imprime nada
i = 0
End If
End Sub

End Class




Happy coding ;)

miércoles, 30 de septiembre de 2009

Que gran verdad!!

Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.

Alguna gente, cuando se enfrentan a un problema piensan: "Ya sé, usaré expresiones regulares." Ahora tienen 2 problemas.


Jaime Zawinski



Yo diría más, si encuentras expresiones regulares en el código, busca al que lo haya programado y dale una colleja (y otra de mi parte).

miércoles, 26 de agosto de 2009

Novedades .NET Framework 4.0

Aún está en beta pero ya tenemos un documento de MSDN con las novedades que traerá el Framework 4.0:

What's New in the .NET Framework 4.0


Habrá que seguirlo con atención porque trae novedades jugosas :)

viernes, 29 de mayo de 2009

Truco rápido: Histórico de cambios en una tabla usando un trigger

No me voy a enrollar explicando que es un trigger ni para que sirve, tenéis información de sobra en este link: CREATE TRIGGER (Transact-SQL)

Lo que voy a explicar aquí es como usar un trigger para que nos guarde en una tabla de históricos los cambios (INSERT y UPDATE) que se producen en una tabla. Para ello supongamos que tenemos una tabla con un identificador (campo Id) y un nombre (campo nombre), y queremos almacenar en la tabla Historico (Id,nombre_antiguo,nombre_nuevo), los cambios que se van produciendo.

Dado que se pueden ejecutar updates sobre varios registros a la vez, será necesario que nuestro trigger obtenga todos los cambios a la vez y los procese uno a uno, eso lo haremos con un cursor.

El truco está en cómo distinguiremos que se trata de un INSERT o de un UPDATE, es muy sencillo, en caso de ser un INSERT el motor de base de datos almacena un registro en la tabla temporal "INSERTED", y en el caso de un update, habrá 2 registros: uno en la tabla "DELETED" y otro en "INSERTED", es como si para actualizarlo, primero se borrase y después se volviese a insertar, cosas del SQL Server :P

Vamos con el código:


CREATE TRIGGER [dbo].[prueba_insert_update]
ON [dbo].[Pruebas]
AFTER INSERT, UPDATE
AS
BEGIN

SET NOCOUNT ON;


-- Identificador del registro
DECLARE @Id int


-- Variable para almacenar el nombre anterior
DECLARE @nombre_antiguo varchar(20)


-- Declaramos el cursor "Cursorito" para que contenga únicamente
-- los ID de la tabla temporal "INSERTED"
DECLARE Cursorito CURSOR FORWARD_ONLY
FOR
SELECT Id FROM INSERTED


-- Abrimos el cursor
OPEN Cursorito


-- Movemos el puntero al primer registro del cursor
FETCH NEXT FROM Cursorito into @Id


-- Recorremos el cursor, cuando @@Fetch_Status sea diferente de 0
-- habremos llegado al final
WHILE @@Fetch_Status = 0
BEGIN


SET @nombre_antiguo = ''


-- Buscamos en la tabla temporal "DELETED" si hay un registro con
-- el Id que toca en esta pasada, lo que significaría que
-- se ha producido un UPDATE
SELECT @nombre_antiguo = ISNULL(nombre,'')
FROM DELETED
WHERE Id = @Id


-- Si no hay ningún registro que cumpla la condición la
-- variable @nombre_antiguo contendrá '' y se tratará
-- de una inserción


-- Añadimos un registro en la tabla de históricos
INSERT INTO Historico
(Id,nombre_antiguo,nombre_nuevo)
SELECT @Id, @nombre_antiguo, nombre
FROM INSERTED
WHERE Id = @Id


-- Nos movemos al siguiente registro
FETCH NEXT FROM Cursorito into @Id

END


-- Cerramos el cursor y liberamos el recurso
CLOSE Cursorito
DEALLOCATE Cursorito

END



Happy coding ;)

viernes, 8 de mayo de 2009

Buscar en campos de texto con T-SQL: mucho más que LIKE

Una de las primeras cosas que se aprende cuando se empieza con T-SQL es a usar la cláusula LIKE para hacer comparaciones en campos de tipo texto (char, varchar o text). Pero esta cláusula está bastante limitada y no ofrece un buen rendimiento, así que ¿qué podemos utilizar en vez de LIKE?

Una muy buena opción es CONTAINS (que para ser sincero, descubrí de casualidad :P), que nos permite afinar bastante más las comparaciones y ofrece mucho mejor rendimiento.

Para poner un ejemplo del uso de CONTAINS voy a usar la base de datos PUBS (que ofrece Microsoft como ejemplo con SQL Server y se puede descargar desde aquí) y vamos a buscar registros en la tabla jobs filtrando por el campo job_desc.

Para que os hagáis una idea (y si no os apetece descargaros las bases de datos de ejemplo) la tabla jobs tiene los siguientes registros:


job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10
2 Chief Executive Officer 200 250
3 Business Operations Manager 175 225
4 Chief Financial Officier 175 250
5 Publisher 150 250
6 Managing Editor 140 225
7 Marketing Manager 120 200
8 Public Relations Manager 100 175
9 Acquisitions Manager 75 175
10 Productions Manager 75 165
11 Operations Manager 75 150
12 Editor 25 100
13 Sales Representative 25 100
14 Designer 25 100


Para poder usar CONTAINS primero debemos crear un índice FULLTEXT en la tabla, que se hace con las siguientes instrucciones:


CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON Jobs(job_desc)
KEY INDEX PK__jobs__173876EA
GO


Siendo PK__jobs__173876EA el índice principal (PRIMARY KEY) de la tabla.

Una vez hecho esto podemos empezar con los ejemplos:

Buscar palabra exacta

Obtendremos todos los registros que contengas ‘manager’ en el campo job_desc. Fijaros que no distingue mayúsculas y minúsculas:


select * from jobs where CONTAINS( job_desc, 'manager' )

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
3 Business Operations Manager 175 225
7 Marketing Manager 120 200
8 Public Relations Manager 100 175
9 Acquisitions Manager 75 175
10 Productions Manager 75 165
11 Operations Manager 75 150


Además se pueden buscar frases (más de 1 palabra) y en las búsquedas se omiten los separadores.


select * from jobs where CONTAINS (job_desc, '"hire job"');
job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
1 New Hire - Job not specified 10 10



Buscar por aproximación con varias opciones

Buscamos los registros que contengan alguna palabra que empiece por ‘pub’ o por ‘edit’.


select * from jobs where CONTAINS(job_desc, '"pub*" OR "edit*"');

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
5 Publisher 150 250
6 Managing Editor 140 225
8 Public Relations Manager 100 175
12 Editor 25 100


CONTAINS admite los operadores AND, AND NOT, OR.

Buscar palabra aproximada cerca de otra

Obtenemos registros que contengan una palabra que empiece por ‘pub’ y que esa palabra esté cerca de ‘manager’.


select * from jobs where CONTAINS (job_desc, '"pub*" NEAR manager')

job_id job_desc min_lvl max_lvl
-------- -------------------------------------------------- ------- -------
8 Public Relations Manager 100 175



CONTAINS permite además buscar por palabras derivadas y sinónimos, pero para eso es necesario usar un diccionario de sinónimos.

Happy codding ;)

Bola extra
Además de CONTAINS tenemos otras opciones para este tipo de búsquedas como FREETEXT
y CONTAINSTABLE

viernes, 27 de marzo de 2009

3 formas de obtener la versión de SQL Server desde T-SQL

En un mundo perfecto todas nuestras instalaciones tendrían las mismas versiones de las aplicaciones, motores de bases de datos y librerías auxiliares, pero como este mundo es una utopía, a veces tenemos que averiguar con qué versión del motor de base de datos está trabajando la aplicación.


En el caso de SQL Server he descubierto 3 formas de averiguar la versión desde T-SQL, con cualquiera obtendremos la versión completa pero en la mayoría de los casos únicamente nos interesará la major release para ejecutar o no características de las nuevas versiones o que están obsoletas en la última versión pero siguen funcionando en versiones antiguas.


Vamos con las 3 opciones:


@@VERSION
http://msdn.microsoft.com/es-es/library/ms177512.aspx


Como es una variable de sistema sirve para cualquier versión de SQL Server, pero a partir de la versión 2005 devuelve un nvarchar en vez del varchar de las versiones anteriores.





declare @version char(2)

select @version = substring(@@version, charindex('- ', @@version) + 2, charindex('.',@@version) - charindex('- ', @@version)-2)



SERVERPROPERTY(‘ProductVersion’)


http://msdn.microsoft.com/es-es/library/ms174396.aspx

Esta opción sólo está disponible a partir de SQL Server 2000 y devuelve un valor de tipo sqlvariant, por lo que habrá que convertirlo a varchar o nvarchar para sacar la versión principal.





declare @productversion varchar(50)

declare @version char(2)

select @productversion = CAST(SERVERPROPERTY('productversion') AS VARCHAR(50))

set @version = SUBSTRING(@productversion,1,CHARINDEX('.',@productversion)-1)




xp_msver ‘ProductVersion’

http://technet.microsoft.com/es-es/library/ms187372.aspx

Quizás sea el método más complicado porque devuleve un conjunto de resultados con la siguiente forma:

Index Name Internal_Value Character_Value
------ -------------------------------- -------------- ----------------------
2 ProductVersion 589824 9.00.1406.00

(1 filas afectadas)

Pero también se puede obtener la versión con algo como esto:





CREATE TABLE #VersionTable(
[Index] int,
Name varchar(30),
Internal_Value int,
Character_Value varchar(50))
GO

INSERT INTO #VersionTable
EXEC master..xp_msver 'ProductVersion'

DECLARE @Version char(2)

SELECT @Version = (
SELECT SUBSTRING(Character_Value,1,CHARINDEX('.', Character_Value)-1)
FROM #VersionTable)

drop table #VersionTable



Con cualquiera de las 3 opciones obtendremos lo siguiente en la variable @Version:

‘7’ -> SQL Server 7
‘8’ -> SQL Server 2000 y MSDE
‘9’ -> SQL Server 2005
‘10’ -> SQL Server 2008

Happy coding ;)

martes, 10 de marzo de 2009

Truco rápido: todos los mensajes de error de SQL Server a mano

Cuando trabajas con SQL Server estás acostumbrado a que los mensajes de error que aparecen en el fichero ERRORLOG o en el visor de sucesos sean bastante crípticos y lo normal es acabar tirando de [ponga aquí su buscador favorito] para saber de qué se trata.

Pues bien, hace tiempo que descubrí ,dentro de la enormidad que son los libros en pantalla de SQL Server, una página con los códigos de error organizados por número, y desde entonces es mi primera opción a la hora de buscar, así que aquí la tenéis:

http://technet.microsoft.com/es-es/library/cc645603.aspx (en castellano)

http://technet.microsoft.com/en-us/library/cc645603.aspx (en inglés)


Espero que os sirva de ayuda ;)

miércoles, 25 de febrero de 2009

Recursos Silverlight

Estoy empezando un proyecto personal con Silverlight (sí, es que no me llega con el trabajo, necesito más :P) y como siempre que se empieza con una nueva tecnología es bueno tener buen material para aprender, así que iré poniendo los tutoriales que vaya encontrando y todo lo que vea interesante para comenzar a trastear con Silverlight :)

Por supuesto, se agradecerá cualquier aportación a "la causa" :D


- Tutoriales paso a paso de Jesse Liberty (en inglés) descargable en PDF
http://silverlight.net/learn/tutorials.aspx


- Blog de Jesse Liberty (también en inglés)
http://silverlight.net/blogs/jesseliberty/


- Tutorial por Scott Guthrie (este en castellano)
http://thinkingindotnet.wordpress.com/tutorial-de-silverlight/

- Silverlight Toolkit en Codeplex, que incluye controles, themes, proyectos de ejemplo, tests unitarios, ...
http://www.codeplex.com/Silverlight/Release/ProjectReleases.aspx?ReleaseId=19172


Actualización 03/03/2009: Añado unas cuantas cosas más :)

- Cómo crear temas para Silverlight

http://www.nikhilk.net/Silverlight-Themes.aspx


http://weblogs.asp.net/visualwebgui/archive/2008/02/13/creating-silverlight-themes.aspx

- Galerías de controles Silverlight

http://www.webresourcesdepot.com/free-silverlight-controls-and-tools-for-brighter-websites/


http://silverlight.net/themes/silverlight/community/gallerydetail.aspx?cat=sl2

Actualización 29/04/2009

Curso Online de Silverlight 2.0 por Marino Posadas

- 1ª parte

- 2ª parte


Happy coding!! ;)

lunes, 19 de enero de 2009

He leído: C# 3.0 Coobook de Jay Hilyard y Stephen Teilhet


Título: C# 3.0 CookBook
Autores: Jay Hilyard y Stephen Teilhet
Editorial: O'Reilly
Idioma: Inglés
ISBN-10: 059651610X
ISBN-13: 978-0596516109
Páginas: 886
En la web de O’reilly
En Amazon

Me gustó: este no es un libro de programación al uso, no habla sobre el CLR, la orientación a objetos y los ensamblados, sino que es un libro de “recetas”, sí, recetas como las de cocina de toda la vida. Por ejemplo, pongamos que tenemos que enviar ficheros por FTP usando C# y no tenemos ni idea, pues lo buscamos en el libro y con un poquito de suerte lo tendremos explicado, con su ejemplo de código y en muchos casos, diferentes formas de hacerlo.


Alguno diréis, “pues si no sé hacer algo lo busco en Internet”, y posiblemente lo encontréis mejor o peor explicado pero lo encontraréis. Así pues, ¿qué ofrece este libro? Básicamente que está muy bien explicado, abarca muchos temas y está muy bien organizado. Además, siempre te puede salvar de un problema si te quedas sin conexión :P


No me gustó: últimamente estoy teniendo mucha suerte con los libros que compro porque no les suelo encontrar muchas pegas, y eso cuando te gastas 40 o 50 € en algo se agradece y mucho :) A este libro tampoco le puedo poner muchos peros, igual podrían profundizar más en las explicaciones, pero creo que se perdería la esencia del libro.


Le falta: tratar temas como WCF (Windows Communication Foundation) y WF (Windows Workflow Foundation), pero tampoco es algo muy habitual y hay otros libros que los tratan en profundidad.


Lo recomiendo para: tenerlo siempre a mano porque nos puede sacar de más de un apuro. Pero ojo, no es un libro para aprender, es más para desarrolladores con una cierta experiencia que necesitan una solución para un problema puntual.

miércoles, 14 de enero de 2009

He leído: Pro C# 2008 and the .NET 3.5 Platform de Andew Troelsen


Título: Pro C# 2008 and the .NET 3.5 Platform
Autor: Andrew Troelsen
Editorial: Apress
ISBN10: 1-59059-884-9
ISBN13: 978-1-59059-884-9
Páginas: 1370


En la web de Apress
En Amazon


Lo cierto es que no lo he leído en su totalidad, pero es que son más de 1300 páginas y 4 apéndices extra descargables desde la web de la editorial... :S Pero sí que leí las partes que más me interesaban y creo que ya tengo suficientes elementos de juicio para valorar este libro.

Me gustó: Este sí que es un libro realmente completo, abarca prácticamente todo lo que un desarrollador tiene que saber sobre .NET, C# y las novedades que introduce el Framework 3.5 (LINQ, expresiones lambda, tipos anónimos, novedades en WCF, WF, WPF, ASP.NET ...). Además está todo muy bien explicado, con multitud de ejemplos (también se pueden descargar los proyectos de ejemplo de la web de Apress) e imágenes que ayudan a la comprensión.


El tono del libro es bastante ameno y está bien organizado. Antes de cada capítulo el autor hace una pequeña introducción de lo que se tratará, por lo que si no te interesa puedes pasar directamente al siguiente. Y también hay un resumen al final, que está muy bien para aclarar los conceptos del capítulo.

Para que os hagáis una idea de lo completo que es este libro, tiene 2 apéndices, uno sobre interoperabilidad con componentes COM y el otro sobre MONO :o

No me gustó: Ufff, es difícil encontrarle una pega a este libro, está realmente bien y es muy completo ... por decir algo, que es un tocho de más de 1300 páginas y pesa un montón :P Pero bueno, siempre viene bien para hacer un poco de brazo en la oficina o en casa :D

Le falta: que alguien lo traduzca al castellano, porque leerlo en inglés es un poquito duro, pero bueno, ya estamos acostumbrados y el autor no se hace pesado en ningún momento.

Lo recomiendo para: todo el que quiera profundizar en el desarrollo de aplicaciones en .NET con C# y el Framework 3.5, es un gran libro tanto de aprendizaje como para tenerlo de referencia (además, fardas un montón con eso del “Pro C#” :P )

lunes, 12 de enero de 2009

Obtener instancias de SQL Server y bases de datos disponibles desde .NET (VB.NET)

Aquí os dejo el código en VB.NET del formulario con los orígenes de datos de SQL Server disponibles, vamos, lo mismo que este ejemplo pero para los que no os gusta el ";" :P





Imports System.Data
Imports System.Data.Sql
Imports System.Data.SqlClient

Public Class frmConexiones
Private servidores As SqlDataSourceEnumerator
Private tablaServidores As DataTable
Private servidor As String


Public Sub New()

' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()

' Agregue cualquier inicialización después de la llamada a InitializeComponent().
servidores = SqlDataSourceEnumerator.Instance
tablaServidores = New DataTable()
End Sub



Private Sub cmbServidores_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbServidores.Click


' Comprobamos que no se haya cargado ya el combobox
If tablaServidores.Rows.Count = 0 Then
' Obtenemos un dataTable con la información sobre
' las instancias disponibles de SQL Server 2000 y 2005
tablaServidores = servidores.GetDataSources()

' Creamos una lista para que sea el origen de datos
' del combobox
Dim listaServidores As List(Of String) = New List(Of String)

' Recorremos el dataTable y añadimos un valor nuevo la
' lista con cada fila
For Each rowServidor As DataRow In tablaServidores.Rows

' La instancia de SQL Server puede tener nombre
' de instancia o únicamente el nombre del servidor,
' comprobamos si hay nombre de instancia para
' mostrarlo
If String.IsNullOrEmpty(rowServidor("InstanceName").ToString()) Then
listaServidores.Add(rowServidor("ServerName").ToString())
Else
listaServidores.Add(rowServidor("ServerName") & "\\" & rowServidor("InstanceName"))
End If

Next

'Asignamos la lista de servidores como origen de datos
' del combobox
Me.cmbServidores.DataSource = listaServidores
End If

End Sub


Private Sub cmbBasesdeDatos_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbBasesdeDatos.Click

Dim listaBasesDatos As List(Of String) = New List(Of String)
Dim cadenaConexion As String
Dim selectSQL As String

' Se comprueba que haya un servidor seleccionado para
' poder conectarnos
If Me.cmbServidores.Text = "" Then
MsgBox("Debe seleccionar un servidor")
Return
End If

servidor = Me.cmbServidores.Text

'Componemos la cadena de conexión con el servidor seleccionado y
' seguridad integrada
' si la autenticación se hace con usuario y password hay
' que cambiar la cadena
cadenaConexion = "Data Source=" & servidor & " ;Integrated Security=True;Initial Catalog=master"


Using con As New SqlConnection(cadenaConexion)

' Abrimos la conexión
con.Open()

'Obtenemos los nombres de las bases de datos que
' haya en el servidor
selectSQL = "select name from sys.databases;"

Dim com As SqlCommand = New SqlCommand(selectSQL, con)
Dim dr As SqlDataReader = com.ExecuteReader()

' Recorremos el dataReader
While (dr.Read())
listaBasesDatos.Add(dr(0).ToString())
End While

'Asignamos la lista de bases de datos como origen
'de datos del combobox
Me.cmbBasesdeDatos.DataSource = listaBasesDatos


End Using

End Sub

End Class




Happy codding ;)

Obtener instancias de SQL Server y bases de datos disponibles desde .NET (C#)

Por si alguna vez se os ha planteado la necesidad de mostrar un formulario con los orígenes de datos de SQL Server disponibles (algo como el formulario Agregar conexión de Visual Studio) voy a plantear un pequeño ejemplo.

Creamos un nuevo proyecto de Windows Forms al que llamaremos Conexiones, y en él un formulario frmConexiones en el que añadimos 2 combobox: cmbServidores y cmbBasesdeDatos (esta vez me estoy currando los nombres, no os quejaréis :P).

En el código del formulario añadimos lo siguiente:




using System;
using System.Windows.Forms;
using System.Data;
using System.Data.Sql;
using System.Collections.Generic;

namespace Conexiones
{
public partial class frmConexiones : Form
{
SqlDataSourceEnumerator servidores;
System.Data.DataTable tablaServidores;
String servidor;

public frmConexiones()
{
InitializeComponent();
servidores = SqlDataSourceEnumerator.Instance;
tablaServidores = new DataTable();
}
}
}



Ahora hay que añadir el código para el evento click de cada uno de los combobox:
Para obtener la lista de instancias de SQL Server disponibles usaremos el objeto SqlDataSourceEnumerator.Instance, concretamente el método GetDataSources() que nos devuelve un DataTable con las características de las instancias: nombre del servidor, nombre de instancia, si forma parte de un clúster y la versión (8.00.x para SQL Server 2000 y 9.00.x para SQL Server 2005):




private void cmbServidores_Click(object sender, EventArgs e)
{

// Comprobamos que no se haya cargado ya el combobox
if (tablaServidores.Rows.Count == 0)
{
// Obtenemos un dataTable con la información sobre las instancias visibles
// de SQL Server 2000 y 2005
tablaServidores = servidores.GetDataSources();


// Creamos una lista para que sea el origen de datos del combobox
List listaServidores = new List();


// Recorremos el dataTable y añadimos un valor nuevo a la lista con cada fila
foreach (DataRow rowServidor in tablaServidores.Rows)
{

// La instancia de SQL Server puede tener nombre de instancia
//o únicamente el nombre del servidor, comprobamos si hay
//nombre de instancia para mostrarlo
if (String.IsNullOrEmpty(rowServidor["InstanceName"].ToString()))
listaServidores.Add(rowServidor["ServerName"].ToString());
else
listaServidores.Add(rowServidor["ServerName"] + "\\" + rowServidor["InstanceName"]);
}

// Asignamos al origen de datos del combobox la lista con
// las instancias de servidores
this.cmbServidores.DataSource = listaServidores;
}
}



Y para el que muestra las bases de datos tenemos que ejecutar una consulta sobre la vista sys.databases:




private void cmbBasesdeDatos_Click(object sender, EventArgs e)
{
string select;
string cadenaConexion;
List listaBasesdatos = new List();

this.servidor = this.cmbServidores.Text;

// Componemos la cadena de conexión con el servidor seleccionado
// con seguridad integrada
// Si se conecta con usuario de SQL Server hay que cambiar
// la cadena de conexión
cadenaConexion = "Data Source=" + this.servidor + ";Integrated Security=True;Initial Catalog=master";

using (System.Data.SqlClient.SqlConnection con = new System.Data.SqlClient.SqlConnection(cadenaConexion))
{
// Abrimos la conexión
con.Open();

// Obtenemos los nombres de las bases de datos que haya en el servidor
// se pueden filtrar para no mostrar las bases de datos de sistema
select = "select name from sys.databases;";

// Obtenemos un dataReader con el resultado
System.Data.SqlClient.SqlCommand com =
new System.Data.SqlClient.SqlCommand(select, con);
System.Data.SqlClient.SqlDataReader dr = com.ExecuteReader();

// Recorremos el dataReader y añadimos un elemento nuevo
// por cada registro
while (dr.Read())
{
listaBasesdatos.Add(dr[0].ToString());
}

// Asignamos la lista de bases de datos como origen de datos del combobox
this.cmbBasesdeDatos.DataSource = listaBasesdatos;
}
}



Y ya está, con esto tenemos un formulario que permite seleccionar la instancia de SQL Server y la base de datos a la que nos queremos conectar :)

Si en vez de seguridad integrada usamos un usuario y password de SQL Server sólo hay que cambiar la cadena de conexión.

Happy codding ;)

PD: Si no os gusta C# tenéis el mismo código en VB.NET aquí.

jueves, 8 de enero de 2009

Imprimir el contenido de un DataGridView con PrintDocument

Igual alguna vez os habéis encontrado en la situación de querer imprimir el contenido de un DataGridView y poder presentárselos a alguien o tenerlos a mano sin necesidad de tener la aplicación en pantalla.

Está claro que hay formas muy sencillas de hacerlo, por ejemplo ese botón “Imprimir pantalla” que hay en el teclado y que mucha gente no sabe para qué sirve, crear informes con Crystal Reports u otro creador de informes, apuntarlos a mano en un papel, memorizarlos ...

Algunas de estas opciones nos mostrarán datos de más, otras de menos y otras dependerán de la cantidad de neuronas que nos queden disponibles en ese momento (bueno, y de las que cada uno traiga de fábrica :P) pero hay una opción bastante sencilla que nos puede sacar de más de un apuro: usar PrintDocument con el contenido del DataGridView.

Supongamos que tenemos un formulario con un DataGridView que se llama dataGridView1 (original, ¿a qué sí?) que tiene como origen de datos ... lo que sea, nos da igual porque lo que nos interesa es el contenido no si el origen de datos es un DataSet, un fichero XML o hemos metido los datos por código.

Bien, añadimos al formulario un objeto de tipo PrintDocument (está en el Cuadro de Herramientas en el apartado Impresión) con el nombre printDocument1 (para ser coherentes con la nomenclatura :P).

Ahora añadimos un botón (¿a qué no adivináis como se llama?) pues sí, button1 y en su evento click escribimos lo siguiente:


printDocument1.Print();



En el evento PrintPage de printDocument1 vamos a recorrer el contenido de dataGridView1 e imprimir una línea por cada fila del grid. Es muy importante saber que el evento se dispara por cada página que se vaya a imprimir, esto quiere decir que si tenemos 5 páginas el evento se disparará 5 veces y por tanto hay que saber en qué punto nos hemos quedado para continuar desde ahí y no volver a empezar.

// Variable a nivel de clase para recordar en qué punto nos hemos quedado
// la inicializamos a -1 para imprimir también las cabeceras de las columnas
private int i = -1;

private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// La fuente que vamos a usar para imprimir.
Font printFont = new Font("Arial", 10);
float topMargin = e.MarginBounds.Top;
float yPos = 0;
float linesPerPage = 0;
int count = 0;
string texto = "" ;
DataGridViewRow row;

// Calculamos el número de líneas que caben en cada página.
linesPerPage = e.MarginBounds.Height / printFont.GetHeight(e.Graphics);


// Recorremos las filas del DataGridView hasta que llegemos
// a las líneas que nos caben en cada página o al final del grid.
while (count < linesPerPage && i < this.dataGridView1.Rows.Count)
{
row = dataGridView1.Rows[i];
texto = "";

foreach (DataGridViewCell celda in row.Cells)
{
texto += "\t" + celda.Value.ToString();
}

// Calculamos la posición en la que se escribe la línea
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));

// Escribimos la línea con el objeto Graphics
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);

count++;
i++;
}

// Una vez fuera del bucle comprobamos si nos quedan más filas
// por imprimir, si quedan saldrán en la siguente página
if (i < this.dataGridView1.Rows.Count)
e.HasMorePages = true;
else
{
// si llegamos al final, se establece HasMorePages a
// false para que se acabe la impresión
e.HasMorePages = false;

// Es necesario poner el contador a 0 porque, por ejemplo si se hace
// una impresión desde PrintPreviewDialog, se vuelve disparar este
// evento como si fuese la primera vez, y si i está con el valor de la
// última fila del grid no se imprime nada
i = 0;
}

}




Con esto conseguimos imprimir todos los datos que hay en un grid, incluidas las cabeceras de las columnas, pero ¿habría forma de imprimir únicamente lo que seleccionemos en el grid?

La respuesta es sí y es bastante sencillo, simplemente hay que cambiar:


texto += "\t" + celda.Value.ToString();



por este otro:


if (celda.Selected)
{
texto += "\t" + celda.Value.ToString();
}
if (String.IsNullOrEmpty(seleccion) == false)
{
yPos = topMargin + (count * printFont.GetHeight(e.Graphics));
e.Graphics.DrawString(texto, printFont, Brushes.Black, 10, yPos);
count++;
}



Con esto sólo imprimimos las celdas que se hayan seleccionado, que pueden ser, varias filas, varias columnas o celdas sueltas, y además evitamos que se impriman filas en blanco.

Advertencia: que nadie piense que con esto vamos a sacar unos informes espectaculares con las columnas perfectamente alineadas y todo precioso, NO NO NO, esto es únicamente para hacer una impresión rápida de los valores que aparecen en un DataGridView, sin muchas florituras pero también sin muchas complicaciones.


Happy codding ;)


Bola extra: PrintDocument imprime directamente por la impresora predeterminada, para cambiar la impresiona, su configuración y la configuración del papel echarle un vistazo a PrintDialog y PageSetudDialog. Y para sacar la vista previa: PrintPreviewDialog.

miércoles, 7 de enero de 2009

Pasar filas a columnas con T-SQL: PIVOT() y las cosas que te alegran el día :)

English version

A veces las cosas que parecen complicadas tienen una solución muy fácil pero que no siempre es la primera que se nos ocurre (no es exactamente el principio de la navaja de Ockham, pero se le parece).

Veamos, el caso es que hay una tabla de logs en la que varios procesos van insertando el resultado de su paso por diferentes estados, algo como esto:



Proceso Estado Timestamp

Proceso1 10 2008/12/21 12:22:05.320
Proceso2 10 2008/12/21 12:22:05.358
Proceso1 11 2008/12/21 12:22:06.210
Proceso1 12 2008/12/21 12:22:06.588
Proceso2 11 2008/12/21 12:22:10.100
Proceso2 12 2008/12/21 12:22:10.048
Proceso3 10 2008/12/21 14:30:05.358
Proceso3 11 2008/12/21 14:30:20.052



Para poder hacer un seguimiento de los procesos y así identificar problemas de rendimiento en ciertos momentos: entran muchos procesos a la vez, se hacen copias de seguridad, el servidor se queda sin memoria, ... sacamos una consulta agrupando por proceso y hora.

SELECT proceso, count(*) as peticiones, datepart(hh,Timestamp) as hora
FROM MiLOG with (nolock)
GROUP BY proceso, datepart(hh,Timestamp)
ORDER BY 1,3

Con esta consulta teníamos un resultado parecido a este:


Proceso Peticiones Hora
Proceso1 10 12
Proceso1 2 13
Proceso2 5 12
Proceso2 3 13
Proceso3 25 14
Proceso3 4 15



Pero claro, esta salida es un poco complicada de tratar porque aunque estén agrupados por el proceso y la hora cuando hay muchos datos se vuelve muy farragoso comprobarlos, y acabamos llevando los resultados a una hoja de cálculo para que se viesen más claro. Entonces me pregunté, ¿no habrá una forma de hacerlo desde T-SQL para que me convierta las filas en columnas? Vamos, que haya una columna por cada hora y una única fila para cada proceso.

Como siempre la primera solución en una consulta complicada es una sub-consulta (no sé porqué pero es así, alguien tendría que estudiarlo :P) pero en este caso no se trataba de una sub-consulta, sino más bien de 24 sub-consultas, todas atacando a la misma tabla y todas usando la función datepart(hh,Timestamp). Seguro que con eso obtendríamos en resultado esperado, pero a costa de un rendimiento muy muy pobre.

Como esta situación es muy común tenía que haber una forma fácil que solucionarlo, así que en una primera búsqueda de Google no me sorprendió los primeros resultados mostrasen la misma solución de las sub-consultas (por el principio de que la primera solución en una consulta complicada es una sub-consulta :P) ya me veía haciendo una mega-consulta con sub-consultas, JOINs sobre la misma tabla y demás, pero ya en la primera página de resultados encontré una solución mucho más simple: la cláusula PIVOT (que es una novedad SQL Server 2005, así que no lo intentéis con versiones anteriores).

Así que con una consulta como esta:

WITH Procesos( proceso, peticiones, hora)
AS(
SELECT proceso, count(*) as peticiones, datepart(hh,Timestamp) as hora
FROM MiLOG with (nolock)
GROUP BY proceso, datepart(hh,Timestamp)
)
SELECT * FROM Procesos PIVOT(SUM(peticiones) FOR Hora IN ([0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23])) AS Resultado
order by proceso

sí que tenemos el resultado esperado:


Proceso 0 1 2 ... 21 22 23

Proceso1 0 10 23 4 5 21
Proceso2 12 11 15 22 17 8
Proceso3 9 7 2 19 9 13


Como curiosidad decir que la cláusula PIVOT() únicamente acepta funciones de agregado (COUNT, SUM, MAX, AVG, ...), lo cual es totalmente lógico.

Otra cosa, igual os extraña ese

WITH Procesos( proceso, peticiones, hora) AS(...)

se trata de una expresión de tabla común (CTE) que es una forma de especificar un conjunto de resultados temporal con un nombre que puede ser usado en otras consultas.

Bueno, para empezar el año creo que está bien :D

Happy codding ;)



Bola extra: después de pensar en las sub-consultas y antes de descubrir la cláusula PIVOT, pensé en usar CUBE, que también podía servir, pero no era exactamente lo que buscaba, aquí tenéis un muy buen tutorial sobre CUBE.

jueves, 6 de noviembre de 2008

Try ...Catch y RAISERROR en T-SQL: ¿es un bug o una feature?

No voy a entrar a explicar cómo se usan los bloques Try...Catch en T-SQL, que para eso ya está el MSDN: http://msdn.microsoft.com/es-es/library/ms179296(SQL.90).aspx

si no que os voy a contar un caso curioso que nos ha pasado estos últimos días:

Tenemos una aplicación en VB 6.0 (sí, tiene que ser en VB 6.0 por narices, así que es lo que hay) que tiene que llamar a store procedures de una base de datos SQL Server 2005 usando ADO (sí, el clásico no el .NET), bueno, el caso es que en algunos store procedures tenemos algo parecido a esto:


CREATE PROCEDURE Mi_Procedure
AS
BEGIN
BEGIN TRY
... Código antes del RAISERROR
RAISERROR(...) -- Este RAISERROR provoca que se vaya al bloque CATCH
... Código después del RAISERROR que no se ejecuta
END TRY
BEGIN CATCH
Exec Otro_Procedure @parametro1, @parametro2
RAISERROR(...) -- Este RAISERROR debería propagar el error al que ha llamado al store procedure
END CATCH
END


Bien, es algo bastante básico, pero lo curioso del tema es que tal y como está, desde ADO no se captura el error, ni se dispara un error interceptable ni está en la colección de errores de la conexión.

Investigando un poco descubrí que en Otro_Procedure que se llama desde aquí había una llamada a sp_executesql y esto provoca que por algún motivo se genere un entorno de ejecución diferente, lo que a su vez provoca que el ADO no capture el error.

¿Cuál puede ser la solución?
Teniendo en cuenta que no podíamos obviar la llamada a Otro_Procedure y tampoco a sp_executesql se me ocurrió la idea de darle la vuelta al bloque CATCH y hacer el RAISERROR antes de la llamada a Otro_Procedure. La idea parece un poco absurda si pensamos que el bloque TRY...CATCH funcionase como, por ejemplo, en C#, pero aquí viene el origen del post, el bloque TRY...CATCH en T-SQL te permite ejecutar cualquier instrucción después de hacer un RAISERROR.

O_o

¿¿¿ Eing???

Pues sí, la lógica dice que en un CATCH cuando hagas un RAISERROR este se propagará hacia arriba hasta llegar al llamador del procedure, pero la implementación en T-SQL hace que esto no sea así y que no se finalice la ejecución hasta llegar al END CATCH, con lo que después del RAISERROR podemos poner la/s instrucciones que queramos que se van a ejecutar.

Por supuesto, haciendo la misma llamada al store procedure desde .NET con ADO.NET siempre se intercepta el error, esté el RAISERROR del CATCH antes o después de la llamada a Otro_Procedure, con lo que +1 para ADO.NET :)

Happy coding ;)

jueves, 9 de octubre de 2008

Con ofertas como esta dan ganas de dejar de trabajar

Iba a empezar el post diciendo algo como “Leo sorprendido en Halón Disparado una oferta de trabajo...” pero la verdad es que me di cuenta que no me sorprendió tanto, debe ser que de ver cosas como esta te acabas curtiendo.


Para el que no vea bien la imagen os lo transcribo, se trata de una oferta para un desarrollo en Argentina con las siguientes condiciones:


“Busco desarrollar lo siguiente:
Un Panel de control para un sitio web de una inmobiliaria, solamente el desarrollo del panel y la base de datos ya que el diseño corre por nuestra cuenta:


- Debe poder soportar múltiples usuarios (con diferentes perfiles – administrador y empleados-)
- Se debe poder cargar los inmuebles de las propiedades conectando con una base de datos mysql
- Las imágenes de los inmuebles se deben guardar en una carpeta del servidor
- Se debe poder destacar las imágenes para que puedan aparecer en el index.php como imágenes destacadas
- Debe poseer diversos parámetros: cantidad de habitaciones, superficie, precio, dirección, provincia, ciudad y observaciones adicionales.
- Debe poseer una interfaz de carga ágil, dinámica y sencilla de entender.
- Debe poseer un buscador interno de propiedades con varios parámetros de búsqueda.
- Debe poseer un ABM de propiedades, entendible.
- Se debe confeccionar manual de operaciones del panel de control
”

Bien, son una serie de requerimientos bastante imprecisos para un desarrollo de algo tan complejo, pero lo mejor viene con las condiciones de la oferta:

Tiempo de entrega: 15 días
Presupuesto: 200,00 ARS (46,72 EUR)

Ante esto sólo me vino una cosa a la mente WTF!!! Es que estamos tontos o qué es lo que pasa. Vale que haya una acuciante crisis mundial (sinceramente yo ni me he enterado, pero bueno es lo que tiene no tener dinero para invertir :P) pero es que ese presupuesto no se le puede considerar un sueldo, es poco menos que una propina :(

¿En serio piensan que alguien en su sano juicio desperdiciará 15 días de su vida por menos de 50 €? 46,72 € /15 días = 3,11 €/día!!!!! Vamos, que no te da ni para pagarte un desayuno.

¿En qué clase de mundo vive el que hace esta oferta? Seguro que piensa que hay algún desarrollador web por ahí que tiene algo parecido ya hecho y no le importa venderlo, pero ¿alguien vendería su trabajo por esa miseria?


Mientras escribo el post me he vuelto a leer la oferta y me llaman la atención un par de cosas más:

- Al principio de la oferta dicen que ya se encargarán ellos del diseño (¿buscarán un diseñador que se lo haga por 20 €?) pero también piden que tenga “una interfaz de carga ágil, dinámica y sencilla de entender”. Y digo yo, la interfaz y el diseño como que van bastante de la mano, así que la mayor parte de SU trabajo ya estaría hecho, ¿no?


- Me encanta como describen lo que tiene que convertirse en el diseño de la base de datos: “Debe poseer diversos parámetros: cantidad de habitaciones, superficie, ...” Y el resto te lo inventas que para eso te pagamos un pastón.


- Dicen que “las imágenes se deben guardar en una carpeta en el servidor”, que seguro que llegan allí por arte de magia y se relacionan con los inmuebles por telepatía.


- Buscador interno, múltiples usuarios, ... vamos lo normal, pero lo mejor es que “Se debe confeccionar manual de operaciones”, claro que sí, y en varios idiomas y todo. Y aún querrán los fuentes y que les des mantenimiento de la aplicación 24 horas/365 días, como si lo viera.

Tengo que reconocer que en mientras estudiaba aproveché que teníamos que hacer un proyecto de tienda virtual (o e-commerce que mola más) para una asignatura y con un compañero se lo “vendimos” a una tienda real (de cómics, juegos de rol, y frikadas varias) y no le cobramos nada.

Pero es que a mí personalmente me sirvió de mucho ya que teníamos que hacerlo de todas formas para la asignatura y así aprendimos lo que es una implantación real y los problemas que plantea. Pero lo de esta oferta es de juzgado de guardia, no sólo quieren que algún pobre desgraciado les haga un proyecto bastante complejo (porque son muy listos y la mitad de las cosas obvias que hay que hacer no las ponen como requerimientos) en 15 días, si no que quieren pagar una auténtica miseria por ello.

Espero que todo esto se trate de un error al poner el presupuesto, porque de ser real esta gente piensa que los desarrolladores somos completamente estúpidos :( Lo malo es que se trate o no de un error existe gente que piensa así, y que nuestro trabajo no vale el precio que le ponemos porque total, ya está todo hecho.

PD: No sé qué es un ABM, si alguien lo sabe por favor que me lo diga por si acaso algún cliente me lo pide algún día y así no me quedo con cara de tonto.

PD2: He puesto esta entrada en la categoría de Humor porque es mejor no tomarse estas cosas en serio.

miércoles, 1 de octubre de 2008

.NET Framework 4.0 y Visual Studio 10: que paren que yo me bajo

Leo en varios blogs como este, o este otro (en inglés) o la nota de prensa oficial (también en inglés) la noticia de que los chicos de Microsoft ya tienen bastante definidos lo que será el próximo .NET Framework (y ya vamos por el 4.0) y la nueva versión de Visual Studio (que como siempre van de la mano).

No me voy a parar en comentar las novedades que prometen porque ya hay un montón de gente que sabe más que yo de esto que lo están haciendo, pero el hecho de que salga la noticia me plantea un par de preguntas que lanzo al aire:

  • ¿Por qué pasa tan poco tiempo entre una versión y la siguiente?
  • ¿Son imprescindibles los cambios que aportan las nuevas versiones? ¿Los vamos a usar al 100%? ¿Alguien los va a llegar a usar todos o una gran parte de ellos?
  • ¿Acabamos los desarrollos que tenemos entre manos con la versión actual o nos conviene esperar a las novedades de la nueva? ¿Sacamos una nueva versión cuando salga el nuevo Framework?
  • ¿Alguien es capaz de seguir este ritmo? ¿Estamos locos los desarrolladores?


Cuando mucha gente aún no se ha pasado a la versión 3.5 del .NET Framework, se encuentran con que ya está prevista la 4.0, cosa bastante natural con la forma de trabajar de Microsoft. Pero es que hay que recordar que el .NET Framework sólo lleva entre nosotros desde 2002, con lo que tenemos que en 6 años han aparecido 5 versiones (1.0, 1.1, 2.0, 3.0 y 3.5), casi a versión por año (y eso sin contar con los Service Packs).


¿Qué aportaron estas nuevas versiones en su momento? Recordando así por encima, la 2.0 aportó bastantes mejoras en el Framework, sobre todo en ADO.NET y ASP.NET. La 3.0 nos introdujo WCF, WF, WPF y esa cosa que llamaron CardSpace pero que no he visto funcionando en ningún sitio. Y la 3.5 es una revisión de la 3.0 y además nos trae LINQ y en paralelo apareció Entity Framework.


Tanto la nueva versión del .NET Framework como la del Visual Studio prometen lo de siempre: mejorar la vida de los desarrolladores para que podamos crear aplicaciones de gran calidad en el menor tiempo posible. Vaya, pues si es así lo quiero YA!!!


Pero es esto que se llama la Vida Real™ los pobres curritos-pica-código nos encontramos con que la mayoría de “mejoras” no nos servirán absolutamente para nada cuando tenemos el plazo de finalización del proyecto echándonos el aliento en la nuca (que mal rollo que da esa frase :P) y los pocos que nos servirían implican cambiar tantas cosas que tendríamos que modificar la mitad del proyecto o empezarlo casi de 0, porque, reconozcámoslo, nunca se hacen las cosas tan bien que sean totalmente reutilizables.


Después están los que lo prueban todo, desde las versiones alfa, y ya empiezan los nuevos proyectos con las nuevas versiones cuando no han ni empezado a asomarse. Pero yo creo que esa gente no es de este planeta, porque entre el tiempo que dedican al trabajo, el de probar las nuevas versiones, escribir en diferentes blogs, leer lo que escriben los demás, preparar podcast, webcast y presentaciones,... a mí no me quedaría tiempo para casi nada, así que me imagino que o no comen, o no duerme o tienen un reloj que les permite viajar en el tiempo. O tal vez sus horas tienen 99 minutos porque encontraron un bug en el sistema horario.


Menos mal que estos son 4 elegidos por el ente supremo (llámalo Dios o el monstruo espagueti volador) porque si no todos los que nos dedicamos a esto y no tenemos sus superpoderes nos quedaríamos en el paro. Porque últimamente es muy pero que muy complicado ser desarrollador en .NET y no morir bajo la avalancha de novedades que nos llega casi sin avisar.

Así que creo que para los simples mortales nos quedan 3 opciones claras:



  • Dejar la programación y dedicarnos al cultivo del tomate (así por lo menos tendremos algo que comer)

  • Detenernos en una versión y meternos en una cueva aislada del mundo hasta finalizar nuestros proyectos.

  • Echarle ... narices e intentar mantenernos al día de todas las novedades, pero sin perder la perspectiva de que hay que acabar los proyectos porque hay jefes/clientes que esperan las cosas para ayer. O casi mejor al revés (más que nada para que no nos despidan), finalizar nuestros proyectos pero sin perder de vista las novedades y las futuras versiones.

Ahora es decisión vuestra como desarrolladores elegir la mejor opción, yo por mi parte ya tengo con quien abrir una plantación de tomates :P


PD: debo dar las gracias al compañero Ipas por los links y a Santi por inspiarar este post.

miércoles, 3 de septiembre de 2008

Poison Messages en SQL Server Service Broker, ¿qué son y cómo evitarlos?

En varios proyectos en los que estoy trabajando estamos utilizando una característica que se introdujo en SQL Server 2005, Service Broker, SSB para los amigos. Brevemente diré que SSB proporciona una plataforma de comunicaciones basada en mensajes que forma parte del motor de base de datos, si queréis saber algo más tenéis información detallada aquí y aquí.


El caso es que cuando empezamos a hacer pruebas de nuestra aplicación nos encontramos que a veces se detenía el servicio sin motivo aparente, y buscando información nos encontramos con los poison messages.


¿Qué es un poison message?
Un poison message (traducido literalmente “mensaje envenenado”) es un mensaje en una cola que ha excedido el número máximo de intentos de ser recibido por una aplicación. Esta situación puede producirse, por ejemplo, cuando una aplicación lee un mensaje de una cola como parte de una transacción, pero debido a errores (error de conversión, duplicidad de clave,...) no puede procesarlo. Si la aplicación aborta la transacción en la que se recibió el mensaje, éste es automáticamente devuelto a la cola. Si el problema con el mensaje no es solucionado puede provocarse un bucle infinito, provocando un poison message.


Service Broker provee detección automática de poison messages que deshabilita la cola (y te aseguro que no hace ninguna gracia que te pase esto en producción) si una transacción que recibe mensajes de una cola hace rollback 5 veces. Si se llega a dar este caso, el mensaje debe ser eliminado por una aplicación diferente o por un administrador de forma manual.


La detección automática también genera un evento de tipo Broker: Queue Disabled, que puede ser interceptado por un procedimiento almacenado que reactive la cola (vuelva a establecer su STATUS a ON). Además se pueden usar las alertas de SQL Agent para notificar al administrador que la cola se ha desactivado para que actúe en consecuencia.


¿Cómo tratar los poison message?
Debido a que la detección automática de poison messages provoca la detención de la cola, y por tanto, de todos los procesos asociados a ella, las aplicaciones deberían, como parte de su lógica, detectar y sacar de las colas de forma segura los mensajes que sean susceptibles de convertirse en poison messages.


- Usar bloques TRY ... CATCH para capturar todos los errores. Comprobar si la transacción (en caso de que exista) se pueda confirmar con la función XACT_STATE().

- Cuando se detecte un error que potencialmente pueda provocar un poison message, asegurarnos de que el mensaje ha sido sacado de la cola y finalizar la conversación con END CONVERSATION ... WITH ERROR.

- También es muy recomendable guardar el mensaje que provocó el poison message para su posterior tratamiento (podemos corregirlo y volver a introducirlo en el ciclo normal) y guardarnos un log.


Un control de poison messages podría ser algo así:


BEGIN TRY
BEGIN TRANSACTION
RECEIVE
.
.
.
COMMIT TRANSACTION
END TRY

BEGIN CATCH
DECLARE @state INT;
SET @state = XACT_STATE();
SELECT @error = ERROR_NUMBER(), @error_message = ERROR_MESSAGE();

IF @state = 1 -- Hay transacción de usuario activa y se puede confirmar
BEGIN
-- Se finaliza la conversación con error
END CONVERSATION @conversation_handle
WITH ERROR = @error description = @error_message;
COMMIT;
END
ELSE IF @state = -1
-- Hay transacción de usuario activa y no se puede confirmar, sólo revertir
BEGIN
ROLLBACK TRANSACTION; -- Se revierte la transacción

-- Se saca de la cola el mensaje y se finaliza la conversación con error
BEGIN TRANSACTION;
RECEIVE TOP(1) @msgBody = message_body
FROM [queue]
WHERE conversation_handle = @conversation_handle;
END CONVERSATION @conversation_handle
WITH ERROR = @error description = @error_message;
COMMIT;
END
END CATCH




He de reconocer que no es una solución perfecta pero por ahora es lo mejor que he encontrado y funciona bastante bien. En todo caso os recomiendo el blog de Remus Rusanu para profundizar en el uso de Service Broker.



Bola extra

Por si a alguien le interesa, buscando información sobre los poison messages en Service Broker me encontré que MSMQ realiza un tratamiento parecido, tenéis más información en esta página.


Edito: (08/09/08) hasta hoy no me di cuenta que lo de Bola Extra es una copia descarada, digo ... homenaje a los camaradas de Halon disparado, si es que de lo que se come se cría :p