miércoles, 29 de mayo de 2013

¿Qué significa T4?

Pregunta:

¿De dónde viene el nombre de las plantillas T4?

Respuesta:

Text Template Transformation Toolkit

sábado, 25 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (y 5)

ENUNCIADO


En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
En la entrada (4) calculamos los intervalo numéricos de la regla.


En está última entrada de la serie veremos como obtener una escala de colores con degradados centrados en el rango que contiene el valor cero.

En nuestro caso para simplificar y porque los colores son los mas intuitivos situaremos el centro en el color amarillo y degradaremos hacia el azulo por los valores negativos y hacia el rojo por los valores positivos.

En mi algoritmo el color amarillo se situará a la derecha o a la izquierda del cero según el número de rangos que haya en cada lado. El objetivo es aminorar la perdida de definición en los colores y visualizar siempre los tres colores, lo que podría no ocurrir si el cero estuviera en primera o última posición.

SOLUCIÓN

El algoritmo rellena la lista en tres pasos:
Primero por la parte derecha donde:
  • El azul está en máximos (#FF o 255) y decrece hasta casi #00.
  • El rojo y el verde crecen desde #00 hasta  "casi"  #FF
En este punto entra el amarillo (#FFFF00)
Luego la parte derecha:
  • El azul se mantiene en #00
  • El rojo que empieza en casi #FF deberá terminar en #FF (aunque puede sufrir una perdida de intensidad intermedia).
  • El verde empieza el casi #FF pero termina en #00
Obviamente el "truco" del algoritmo está en calcular:

  • El punto medio donde situamos el amarillo
  • Los pasos para cada color en los degradados derecho e izquierdo.
  • 'Operar' con colores para generar el string hexadecimal.


    ''' 
    ''' Calcula un rango de colores que empieza en azul, pasa por el verde en el valor cero y termina en el rojo
    ''' 
    ''' colección de extremos de rango que contiene el valor cero
    ''' devuelve la lista de colores
    ''' 
    Public Function coloresCero(rango As List(Of Double)) As List(Of String)
        Dim colores As New List(Of String)
        Dim posicionMedio = rango.IndexOf(0) + 1
        If posicionMedio > rango.Count / 2 Then posicionMedio -= 1
        Dim cuentaIniciales = posicionMedio - 1
        Dim cuentaFinales = rango.Count - posicionMedio

        Dim pasoInicial = 255 / cuentaIniciales
        Dim pasoMedio = 255 / (cuentaIniciales + 1)

        For i = 0 To cuentaIniciales - 1
            colores.Add(HexString(pasoMedio * i, pasoMedio * i, 255 - pasoInicial * i))
        Next
        colores.Add(HexString(255, 255, 0))
        Dim pasoFinal = 255 / cuentaFinales
        pasoMedio = 255 / cuentaFinales
        For i = 1 To cuentaFinales
            colores.Add(HexString((pasoFinal * i) + (pasoMedio * (cuentaFinales - i)),
                                  pasoMedio * (cuentaFinales - i), 0))
        Next
        Return colores
    End Function

    Function HexString(r As Integer, g As Integer, b As Integer) As String
        ' cálcula el string hexadecimal de color RGB
      Return "#" + hex(r) + hex(g) + hex(b)
    End Function

    Function hex(v As Integer) As String
        ' Obtiene el hexadecimal de un entero con cero por delante si hace falta
        Dim r = String.Format("{0:x}", v)
        If r.Length > 1 Then
            Return r
        Else
            Return "0" + r
        End If

    End Function

Reconozco que hay un poco de juego fácil en la elección de los colores, pero eran los que me pedían. ¡Más fácil habría sido elegir como central el verde¡.

Una regla de color uniforme, redondeada, con cero y colores desplazados (4)

ENUNCIADO

En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
Durante la modificación de las reglas de color del informe utilizamos una función rangosMapa que desde una sentencia MDX devuelve la lista de valores doubles para construir los rangos de la regla.

Dicho de otra forma, los números que vemos aquí:

Recordemos que estos números deben satisfacer las condiciones:

  1. Cada rango de color debe contener aproximadamente al misma cantidad de países.
  2. Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.

SOLUCIÓN

En nuestro ejemplo los datos provederán de un servidor SQL Server Analisys Services por lo que necesitaremos la librería Microsoft.AnalysisServices.adomdClient 
Imports Microsoft.AnalysisServices.AdomdClient

Dividimos nuestro trabajo en dos pasos: evaluar la sentencia MDX y calcular los rangos:

Public Function RangosMapa (s As String) As List(Of Double, numRangos as Integer)
    Dim lista As List(Of Double)= EvaluaMDX(s)
    Dim Rangos as List (Of Double)=RangosRedondos(lista, NumRangos, 0, True)
    Return (Rangos)
End Function

Evaluar la expresión MDX y almacenamos los resultados en una lista de doubles no tiene gran secreto:

 Public Function EvaluaMDX(s As String) As List(Of Double)
        Dim lista As New List(Of Double)
        Dim cn As New AdomdConnection(My.Settings.SSASConexion)
        cn.Open()
        Dim cmd As AdomdCommand = cn.CreateCommand()
        cmd.CommandText = evaluaSentencia(s)
        Dim lector = cmd.ExecuteReader()
        Do While lector.Read
            lista.Add(lector.GetDecimal(4)) 'el valor está en la posición 4
        Loop
        lector.Close()
        cn.Close()
        cn.Dispose()
        Return (lista)
End Function

El procedimiento siguiente será crear la lista de rangos.

El truco está en ordenar la lista y luego la recorrerla en saltos con un paso calculado desde el total de valores y numero de intervalos que queramos. En cada salto extremos el valor y lo añadimos a la lista de rangos Obtendríamos así una la lista que satisfacerla  la condición 1 más arriba..

Para el caso de que la lista contenga el cero (condición 2), el procedimiento es ligeramente distinto. Trabajamos con dos listas (positivos y negativos) y aplicamos el algoritmo anterior a cada uno de ellos con un numero de rangos en cada lista proporcional a la posición del cero.

Esta lista tendrá valores extraídos de la lista por ejemplo 9457,89. La siguiente tarea será redondear a un múltiplo de 10 sin alterar los valores incluidos en cada rango, por ejemplo 9000,00. pero deberemos hacer evitando afectar el número de valores entre los nuevos extremos.
Veamos el código que quizás sea lo mas simple:
 
   ''' 
    ''' Desde una lista de valores decimales obtiene otra lista de valores decimales que reparte de forma uniforme los valores.
    ''' 
    ''' Lista original
    ''' Número de rangos en los que se repartirá
    ''' número decimales
    ''' Lista de rangos
    ''' 
    Public Function RangosRedondos(lista As List(Of Double), numRangos As Integer, decimales As Integer, redondear As Boolean) As List(Of Double)
        Dim rangos As List(Of Double)
        lista.Sort() 'ordenamos
        'obtención del rango variable 
        If lista(0) <= 0 And lista(lista.Count - 1) >= 0 Then
            'cuando hay un cero deberá aparecer en un extremo de los rangos
            rangos = repartoConCero(lista, numRangos)
        Else
            rangos = reparto(lista, numRangos)
        End If
        'ahora vamos redondeando sin alterar el reparto
        If redondear Then rangos = RedondeoLista(rangos, lista, decimales)
        'y para terminar nos aseguramos de que los valores extremos esten incluidos
        rangos(0) -= 1
        rangos(rangos.Count - 1) += 1
        Return rangos
    End Function

    ''' 
    ''' Crea una lista de rangos que contienen aproximandamente la misma cantidad de elementos
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function reparto(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim paso As Decimal = lista.Count / numRangos
        Dim rangos As New List(Of Double)
        rangos.Add(lista(0))
        For i = 1 To numRangos - 1
            rangos.Add(lista(Math.Round(i * paso, System.MidpointRounding.AwayFromZero)))
        Next
        rangos.Add(lista(lista.Count - 1))
        Return rangos
    End Function

    ''' 
    ''' Reparte los valores de una lista decimal que cruza el valor cero entre una serie de rangos.
    ''' Dos rangos compartiran el extremo con valor cero
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function repartoConCero(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim Negativos = (From v In lista Where v < 0).ToList
        Dim Positivos = (From v In lista Where v >= 0).ToList
        Dim rangoNegativos = reparto(Negativos, numRangos * Negativos.Count / lista.Count)
        Dim rangoPositivos = reparto(Positivos, numRangos * Positivos.Count / lista.Count)
        If (-rangoNegativos(rangoNegativos.Count - 1)) < rangoPositivos(0) Then
            'el mayor número negativo está mas próximo a cero que el menor positivo
            rangoNegativos(rangoNegativos.Count - 1) = 0
            rangoPositivos.RemoveAt(0)
        Else
            rangoPositivos(0) = 0
            rangoNegativos.RemoveAt(rangoNegativos.Count - 1)
        End If
        Dim resultado As New List(Of Double)
        resultado.AddRange(rangoNegativos)
        resultado.AddRange(rangoPositivos)
        Return resultado
    End Function

    ''' 
    ''' Redondea todo lo posible los valores del rango.
    ''' 
    ''' rango a redondear
    ''' lista que controla el rango
    ''' número de decimales a conservar
    ''' 
    ''' 
    Private Function RedondeoLista(rangos As List(Of Double), lista As List(Of Double), decimales As Integer) As List(Of Double)
        For i = 1 To rangos.Count - 2
            Dim limiteRango = rangos(i)
            'calculamos la diferencia entre el último valor en el rango anterior y el primero del siguiente
            Dim difSupInf = (From v In lista Where v >= limiteRango).Min - (From v In lista Where v < limiteRango).Max
            'calculamos el primer digito no significativo mediante la parte entera del logaritmo en base 10
            Dim factorDigitos = 10 ^ Math.Floor(Math.Log10(difSupInf) + decimales - 1) 'le sumamos los decimales (ver linea siguiente)
            Dim factorDecimales = 10 ^ decimales
            limiteRango *= factorDecimales 'estamos trasladando los decimales a la parte entera.
            limiteRango = Math.Floor(limiteRango / factorDigitos) * factorDigitos
            rangos(i) = limiteRango / factorDecimales
        Next
        Return rangos
    End Function

Antes de terminar llamar la atención sobre dos parámetros que no he comentado:

  • decimales: que permite establecer el número de decimales con los que trabajamos en el redondeo.
  • redondeo: por si en un momento preferimos obtener la lista sin redondeo.
La próxima y última entrada de esta serie definirá una lista de colores para el rango calculado si este contuviera el "cero".

Una regla de color uniforme, redondeada, con cero y colores desplazados (3)


ENUNCIADO:

En la primera entrada de esta serie presentamos nuestro objetivo: Personalizar la regla de colores en un mapa incluido en un informe SSRS de forma que los valores y colores cumplieran ciertos requisitos.
En la segunda entrada vimos cómo podíamos cargar en memoria del cliente la definición XML de un informe SSRS para poder modificarla antes de entregarsela al control ReportViewer.
En esta entrada veremos como modificamos la entrada XML.

SOLUCIÓN:

El código esta comentado:
 
 Public Function ParametrizaMapa(informe As XDocument, 
                  s as string,
                  numRangos as integer) As XDocument
' s contiene una sentencia MDX 
' numRangos numero de rangos que deseamos

        'Calculamos los rangos de la regla de colores
        dim rangosMapa As List(Of Double)=rangosMapa(s,numRangos)
        
        ' Para escribir menos.
        Dim df = informe.Root.Name.Namespace
        Dim capas = informe.Root.Element(
            df + "ReportSections").Element(
            df + "ReportSection").Element(
            df + "Body").Element(
            df + "ReportItems").Element(
            df + "Map").Element(
            df + "MapLayers").Element(
            df + "MapPolygonLayer")

        ' comienzan las reglas de color
        Dim reglasColor As XElement

        ' borramos cualquier regla existente en el informe original
        If capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule") IsNot Nothing Then 
              capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule").Remove()
        end if
        If capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule") IsNot Nothing Then
              capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule").Remove()
        end if
        'Vamos a establecer los colores y sus degradados
        If rangosMapa.Contains(0) Then
            'mapa coloreado alrededor del cero 
            'hay que generar la personalización "MapColorRangeRule" mediante la función coloresCero
            capa.Element(df + "MapPolygonRules").Add(New XElement(df + "MapCustomColorRule"))
            reglasColor = capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule") 'Personalizado
            reglasColor.Add(New XElement(df + "MapCustomColors"))
            Dim colorines = reglasColor.Element(df + "MapCustomColors")
            dim colores as list (of String)= coloresCero(rangosMapa)  'obtenemos una lista de colores
            For Each color In colores
                colorines.Add(New XElement(df + "MapCustomColor", color))
            Next
        Else
            'mapa estándar podemos dejar utilizar la generación automática "MapColorRangeRule"
            capa.Element(df + "MapPolygonRules").Add(New XElement(df + "MapColorRangeRule")) 'Generado por SSRS
            reglasColor = capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule")
            reglasColor.Add(New XElement(df + "StartColor", My.Settings.ReglaColorInicio))
            reglasColor.Add(New XElement(df + "MiddleColor", My.Settings.ReglaColorIntermedio))
            reglasColor.Add(New XElement(df + "EndColor", My.Settings.reglaColorFin))

        End If
        'algunas cosas que siempre son necesarias
        reglasColor.Add(New XElement(df + "ShowInColorScale", "true"))
        reglasColor.Add(New XElement(df + "DataValue", "=Sum(Fields!Dato.Value)"))
        reglasColor.Add(New XElement(df + "BucketCount", rangosMapa.count.toString))
        reglasColor.Add(New XElement(df + "LegendText", "#FROMVALUE{N0} - #TOVALUE{N0}"))

        'Vamos a rellenar los rangos de valores
        Dim culturaUSA = New System.Globalization.CultureInfo("en-US")
        If rangosMapa.Count = 2 Then 'solo max y min
            reglasColor.Add(New XElement(df + "DistributionType", "EqualInterval"))
            reglasColor.Add(New XElement(df + "StartValue"), rangosMapa(0).ToString(culturaUSA.NumberFormat))
            reglasColor.Add(New XElement(df + "EndValue"), rangosMapa(1).ToString(culturaUSA.NumberFormat))
        Else
            'aquí es donde realmente estamos personalizando con nuestro rango personalizado
            reglasColor.Add(New XElement(df + "DistributionType", "Custom"))
            reglasColor.Add(New XElement(df + "MapBuckets"))
            Dim rangos = reglasColor.Element(df + "MapBuckets")
            For i = 0 To rangosMapa.Count - 2
                rangos.Add(New XElement(df + "MapBucket"))
                Dim rango = rangos.Elements(df + "MapBucket")(i)
                rango.Add(New XElement(df + "StartValue", rangosMapa(i).ToString(culturaUSA.NumberFormat)))
                rango.Add(New XElement(df + "EndValue", (rangosMapa(i + 1)).ToString(culturaUSA.NumberFormat)))
            Next
        End If

        'Establecemos la sentencia MDX que suministra los datos al informe
        Dim xdatos = (From f In doc.Root.Element(df + "DataSets").Elements(df + "DataSet")).First(
            Function(x As XElement) x.Attribute("Name") = "Datos").Element(df + "Query").Element(df + "CommandText")
            xdatos.SetValue(s)
        Return informe
End Function
          

Con esto hemos terminado la personalización del informe, nos quedan por definir las  dos funciones rangosMapa y coloresCero que presentaremos en la siguientes entradas



Una regla de color uniforme, redondeada, con cero y colores desplazados (2)

ENUNCIADO:

En la primera entrada de esta serie presentamos nuestro objetivo: Personalizar la regla de colores en un mapa incluido en un informe SSRS de forma que los valores y colores cumplieran ciertos requisitos.

El diseñador de informes de MS (BIDS) no nos permite definir reglas de color automáticas para nuestros requisitos, deberemos utilizar intervalos y colores personalizados.

Dado que esta personalización depende de los valores de la consulta no puede estar establecida en el informe, y deberemos modificar este cada vez que vayamos a visualizarlo.

En esta entrada vamos a ver como cargar un informe desde el servidor SSRS en nuestro programa cliente, modificar el XML e inyectar el resultado en un control ReportViewer que finalmente solicitará al servidor el procesado.

SOLUCIÓN:

Partimos de que hemos creado una página web que contiene un control ReportViewer llamado Visor y que está configurado para remoteProcessing.
Usaremos la libreria ReportingServices2010 nos permite recuperar la definición de un informe  mediante los servicios Web de reporting services:

Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

El esquema de nuestro proceso será:
 
Sub Main()
 Dim informe as byte()=cargaInforme(nombre as string) 'cargamos el informe
 Dim informeXML as system.XML.linq.xDocument= bytesToXML(informe)  'lo pasamos a XML
 informeXML=parametrizaMapa(informeXML, sentenciaMDX,10)  'Personalizamos e informe
 informe = XMLtoBytes(InformeXML)  'Volvemos a array de bytes
 Visor.ServerReport.LoadReportDefinition(informe) 'lo cargamos en el visor de informes.
 Visor.ServerReport.Refresh()
End Sub 

Deberemos importar la librería de acceso proxy al SSRS
Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

Para poder cargar el informe:

    Private Function CargaInforme(nombre As String) As byte()
        Dim SSRS As New ReportingService2010
        SSRS.Credentials = (New MyReportServerCredentials).NetworkCredentials
        Dim informe = SSRS.GetItemDefinition(My.Settings.pathMapas + nombre)
        SSRS = Nothing
        Return doc
    End Function

Para conectarnos necesitamos suministrar una credenciales utilizando la clase myReportServerCredentials descrita en Autenticación Reporting Services.

La función getItemDefinition y  nuetra función CargaInforme nos devuelven una matriz de bytes que deberemos convertir en un documento system.XML.link.xDocument para poder editarla:
    Private Function bytesToXML(informe As byte()) As xDocument
        Dim stream = New System.IO.MemoryStream(informe)
        Dim lector = System.Xml.XmlReader.Create(stream)
        Dim doc As XDocument = XDocument.Load(lector, LoadOptions.None)
        Return doc
    End Function

Cuando hayamos terminado de personalizar este documento deberemos volverlo a convertir en matriz de bytes.

   Private Function XMLBytes(doc As XDocument) As System.IO.Stream
        Dim stream As New System.IO.MemoryStream
        Dim escritor = System.Xml.XmlWriter.Create(stream)
        doc.Save(escritor)
        escritor.Close()
        stream.Position = 0
        Return stream
    End Function

En la siguiente entrada editaremos el XML del informe usando linq-to-xml.

viernes, 24 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (1)

ENUNCIADO

Desde una consulta que puede devolver valores negativos y positivos para todos los países del mundo quieres generar un mapa geográfico cuyos colores reflejen estos valores con una regla de color basada en tres colores: color inferior, color medio y color superior con degradados desde el color medio al inferior y desde el color medio al superior.
Hasta aquí todo se puede resolver con los mapas del servidor de informes del Microsoft (SSRS). Pero además deben satisfacerse los siguientes requisitos:
  1. Uniforme: Cada rango de color debe contener aproximadamente la misma cantidad de países.
  2. Redondeada: Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Con cero: Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.
  4. Colores desplazados: En caso de visualizarse el cero el color medio se situara en un rango que contenga este valor entre sus extremos.
Veamos con un ejemplo que ilustra una caída de ventas mundial en los inicios de la crisis del 2008:




Fíjate en los números redondeados y en el cero que desplaza el color amarillo a la derecha de la barra.

UN COMENTARIO PREVIO

Este entrada reemplaza a algunas que escribí anteriormente, pero en esta nueva versión he mejorado los algoritmos y voy a redactarlo mas claramente. De hecho dedicaré cuatro entradas a presentar la solución.
Comencemos.

SOLUCIÓN

El mapa lo visualizaremos mediante un control reportViewer de  WindowsForms o de WebForms configurado para informes de servidor SSRS (remote processing).

Aunque partiremos de un informe diseñado BIDS y plublicado en el SSRS, el visor no utilizará este informe directamente. En su lugar nuestra aplicación cargará la definición XML, la modificará en memoria y la cargará en el visor de informes que solicitará el procesado al SSRS.

Las modificaciones consistirán en personalizar las reglas de color de color de la regla tanto en lo que atañe a los rangos de valor (extremo superior e inferior para cada uno) como en los colores aplicados en cada rango.

Para personalizar los rangos numéricos utilizaremos una función que nos devolverá un lista de "Double" que contendrá los extremos de estos rangos según los requisitos 1, 2, 3 dichos arriba. A esta función dedicaremos la tercera entrada. Otra función nos devolverá una lisa de colores centrada en el rango de cambio de signo.

Dedicaremos cuatro entradas a estos trabajos

Comentario:

Antes de seguir quería decir que desarrollar una aplicación para visualizar magnitudes sobre mapas me ha recordado una vez más que cuando ves resultados estadísticos tienes que estar muy atento a lo que te muestran y como te lo muestran.

Por ejemplo el mapa mas arriba visualizaba las  variaciones de ventas interanuales  entre octubre 2009 y octubre 2008.
Si en lugar del acumulado mostráramos el acumulado interanual de ventas en octubre de 2009, podriamos decir ¿ Crisis? ¿Qué crisis?:







martes, 21 de mayo de 2013

Añadir etiquetas con jerarquía en el mapa del documento de un informe Microsoft

ENUNCIADO

Tienes un informe con grupos y detalles, ¿Donde añadir las etiquetas para que el mapa del documento esté estructurado jerarquicamente. ¿En los cuadros de texto? ¿En la celda del tablix?

SOLUCIÓN

Olvídate del área de diseño.
En los grupos y detalles del el panel inferior, les das propiedades y usas la ficha Avanzado del formulario que se abre.

viernes, 17 de mayo de 2013

Repetir las filas de encabezados en informes Microsoft

Enunciado

¿Cómo hacer que se repitan las filas de encabezado en tablix de report server?

Respuesta

En el panel de grupos (abajo) activa la vista avanzada
(La vista avanzada se consigue desplegando una flecha mínima a la derecha del panel que incluye los titulos grupo de filas y grupos de columnas ¡!)
Entonces veras unos grupos de filas (static) que corresponden a tus encabezados (si hay varios).
Selecciona uno.
Cambia en el panel de propiedades a la derecha:
KeepWithGroup: After
RepeatOnNewPage: True

martes, 14 de mayo de 2013

T-SQL para cambiar la intercalación

Enunciado

Cambiar la intercalación de todas las columnas de todas las tablas de una base de datos.

Solución

Ejecutas el siguiente T-sql en una consulta SSMS.
DECLARE @collation NVARCHAR(64)
SET @collation = 'Modern_Spanish_CI_AI'

SELECT 
    'ALTER TABLE [' + INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA  + '].[' + INFORMATION_SCHEMA.COLUMNS.TABLE_NAME + '] '
  + 'ALTER COLUMN [' + COLUMN_NAME + '] '
  + DATA_TYPE + '(' + CASE CHARACTER_MAXIMUM_LENGTH 
        WHEN -1 THEN 'MAX' 
        ELSE CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR) END + ') '
  + 'COLLATE ' + @collation + ' '
  + CASE WHEN IS_NULLABLE = 'NO' THEN 'NOT NULL' ELSE 'NULL' END
FROM INFORMATION_SCHEMA.columns
INNER JOIN INFORMATION_SCHEMA.TABLES
ON INFORMATION_SCHEMA.TABLES.TABLE_NAME=INFORMATION_SCHEMA.COLUMNS.TABLE_NAME
WHERE  COLLATION_NAME IS NOT NULL
AND COLLATION_NAME <> @collation
AND INFORMATION_SCHEMA.TABLES.TABLE_TYPE ='BASE TABLE'

El resultado lo copias y lo pegas en otra ventana de consulta.
Revisa ese código generado y si está correcto ejecutas.

viernes, 10 de mayo de 2013

Generar miniaturas de archivos TIF, JPG, PDF,...

Enunciado

Desarrollas una aplicación donde los usuarios deben subir archivos varios gráficos en diversos formatos gráficos (JPG, TIF,...etc), así como archivos PDF.
Quieres devolverles un informe en PDF con los archivos y las informaciones asociadas.
Sin embargo no los informes de Microsoft no permiten el uso de muchos de los tipos de archivos o estos tienen excesivo peso lo que repercute en la generación y peso mismo del informe.

Solución

Genera las miniaturas en el momento en que suban el archivo, para ello utiliza http://imageresizing.net que puedes instalar mediante NuGet.
Para usarla lee esta página: http://imageresizing.net/docs/howto/upload-and-resize
Para que pueda leer y procesar PDFs añádele el plugin PdfRenderer.
Para instalar este último plugin usa NuGet pero además debes editar el archivo web.config.

  
    
..... sigue el web config

El código será el mismo que para generar las miniaturas desde archivos gráficos. El plugin le añade la capacidad de leer PDFs.

Comentarios

Es una librería perfecta que tiene muchas mas aplicaciones. Es tan simple de usar que al principio no te lo crees.