NetTwit: Cliente para Twitter usando WCF

twitter

La comunidad Netaweb 2.0, como parte de su restructuración tiene como planes realizar una serie de proyectos “Open-Source” alojados en CodePlex para el uso y aprendizaje de las nuevas tecnologías que nos ofrece Microsoft.

En esta ocacion le toca el turno a NetTwit, siendo este no más un cliente rico, amigable y flexible para Twitter, usando las API que ofrece el servicio e integrando otros servicios que se integran con Twitter.

NetTwit estaba basado en Windows Communication Foundation (WCF) (tomando como base el desarrollo que hicieron los chicos de Vertigo Software y haciendole a la vez algunas mejoras) y Windows Presentation Foundation (WPF), siendo WCF la plataforma base para la implementación de las API de Twitter y WPF como la capa de presentación del cliente, el proyecto tiene como propósito aprender y entender la arquitectura de Windows Communication Foundation (WCF), no solamente a los miembros de la Comunidad NetaWeb 2.0 sino a cualquier entusiasta interesado en colaborar y aprender.

¿Qué es NetaWeb 2.0?

NetaWeb 2.0 es la más grande comunidad de desarrolladores profesionales de República Dominicana y el Caribe, teniendo en su cartera más de 1800 miembros registrados, la comunidad está enfocada en todo lo concerniente al desarrollo de software usando la tecnología .NET.

¿Qué es Twitter?

Tal como dice Wikipedia, Twitter es un servicio gratuito de microblogging, que hace las veces de red social y que permite a sus usuarios enviar micro-entradas (también denominadas "tweets") basadas en texto, con una longitud máxima de 140 caracteres, donde se responde a la pregunta ¿Qué estás haciendo?. El envío de estos mensajes se puede realizar tanto por la web de Twitter, como vía SMS (Short Message Service) desde un teléfono móvil, desde programas de mensajería instantánea, o incluso desde cualquier aplicación de terceros.

Pueden ver más información del proyecto en los links:

http://www.codeplex.com/NetTwit
http://www.twitter.com/estebanx

Haz tus queries a HTML con LINQ To HTML

Problema

Desde hace varios meses cada domingo envió más de 250 SMS vía Orange a distintos celulares de diferentes compañías telefónicas,  Orange tienen una aplicación web que me permite agregar contactos y a la vez con estos agregarlo a listas de contactos para poder enviarle lo mensajes, sucede que los números que no pertenecen a Orange no se pueden enviar y a la vez se quedan en los estados  “Numero Invalido”, “Pendiente”, “Expirado” y “Fallido”, hasta aquí vamos bien, la aplicación web me provee una opción para ver el histórico de mensajes enviado, el problema consiste en que el histórico no me provee una opción para filtrar los números que están en los estados “Numero Invalido”, “Pendiente”, “Expirado” y “Fallido”.

Solución   

Dada esta problemática decidí por mi cuenta poder hacer consultas al html que genera la aplicación

Aplicacion

Tome solamente del código html el table donde están los campos que necesito para poder hacerle más adelante consulta con Linq y lo guarde en el disco C: con el nombre geeksPhones.html

Entendiendo el esquema del archivo guardado

El archivo guardado tiene el siguiente esquema para la primera fila:

 esquemafirstrow

Esta primera fila realmente no nos interesa la que si nos va a interesar es a partir de la segunda fila en adelante que tendrá el mismo código repetitivo de los 1546 mensajes enviados:

secondrow

Diseño de nuestra aplicacion

Como todos sabemos HTML se deriva del GML (Standard Generalized Markup Language – Lenguaje de Marcación Estándar Generalizada), que tambien xml se deriva del GML, entonces  decidi convertir el HTML en XML y esto se hizo posible usando el componente htmlagilitypack y la vez parsiando ese html con mi Extension Methods ToXMLDocument();

  public static XDocument ToXMLDocument(this HtmlDocument doc)
        {
            using (StringWriter strWriter = new StringWriter())
            {
                doc.OptionOutputAsXml = true;
                doc.Save(strWriter);
                return XDocument.Parse(strWriter.GetStringBuilder().ToString());
            }
        }

Tenemos tambien la clase Phone:

public class Phones
    {
        public string SendDate { get; set; }
        public string Phone { get; set; }
        public string EndDate { get; set; }
        public Status Status { get; set; }
    }

Y finalmente el Enum Status:

public enum Status
    {
        None = 0, //Siempre uso este como valor Default
        Recibido = 1,
        Pendiente = 2,
        NumeroInvalido = 3,
        Fallido = 4,
        Expirado = 5
    }

 

Definido ya estos elementos entonces use el siguiente Linq Query para poder leer el html y convertirlo en una Lista genérica de tipo Phone:

HtmlAgilityPack.HtmlDocument doc = new HtmlAgilityPack.HtmlDocument();
            
            doc.Load(@"C:geeksphones.html");
            XDocument xmlDoc = doc.ToXMLDocument();
            List<Phones> query = (from d in xmlDoc.Descendants("table")
                         let tbody = d.Element("tbody")
                         let tr = tbody.Element("tr")
                         select new
                         {

                             SendDate = (from s in tbody.Descendants("td")
                                         select s
                                             into tempsenddate
                                             where tempsenddate.Attribute("width").Value == "5%" && 
tempsenddate.HasElements ==
false
&& tempsenddate.Value.Length > 18
select tempsenddate.Value ).ToArray(), Status = (from status in tbody.Descendants("td") select status into tempstatus where tempstatus.Attribute("width").Value == "3%" && tempstatus.HasElements == false
&& tempstatus.Value.Length > 3 && tempstatus.Value.Length <= 15 select tempstatus.Value).ToArray(), PhoneNumber = (from p in tbody.Descendants("td") select p into tempPhoneNumber where tempPhoneNumber.Attribute("width").Value == "5%" &&
tempPhoneNumber.HasElements ==
false && tempPhoneNumber.Value.Length == 10 select tempPhoneNumber.Value).ToArray(), ReceiveDate = (from r in tbody.Descendants("td") select r into tempReceive where tempReceive.Attribute("width").Value == "3%" &&
tempReceive.HasElements ==
false && tempReceive.Value.Length == 19
|| tempReceive.Value.Length == 3
select tempReceive.Value).ToArray() } into tempPhone from i in Enumerable.Range(0, tempPhone.PhoneNumber.Count()) select new Phones() { Phone = tempPhone.PhoneNumber[i], EndDate = tempPhone.ReceiveDate[i], SendDate = tempPhone.SendDate[i], Status = (Status)Enum.Parse(typeof(Status), tempPhone.Status[i].Replace(" ", "")) } ).ToList();

Explicación del Query

Básicamente lo que queremos de nuestro HTML es saber la fecha de envió, el número de teléfono, el estatus del mensaje y la fecha de recepción, entonces para obtenerlo correctamente tenemos que:

SendDate: solamente se llenara cuando el elemento (td) tenga el atributo width en 5%, no tenga más elementos hijos y la longitud del valor del elemento sea igual a 19 caracteres.

Status: para que pueda tener los datos correctos se debe cumplir la condición de que el elemento (td) tenga el atributo width = 3%, el elemento no tenga más elementos hijos y la longitud del valor del elemento este en un rango de 3 hasta 15 caracteres.

PhoneNumber: para obtener los números correctamente se debe cumplir las condiciones de que el elemento (td) tenga el atributo width = 5%, el elemento no tenga más elementos hijos y la longitud del valor del elemento sea igual a 10 caracteres debido a que los números en mi país tiene un total de 10 caracteres.

ReceiveDate: para obtener la fecha de recibido correctamente se debe cumplir las condiciones de que el elemento (td) tenga el atributo width=3%, el elemento no tenga más elemento hijos y la longitud del elemento sea igual a 18 caracteres o a 3 caracteres ya que cuando el mensaje se queda en el estatus expirado la fecha sale con los caracteres “—“.

 linqToHtml

Descargas

Codigo Linq To HTML aqui.
Archivo geekPhones.html aqui. (este archivo es necesario ponerlo en el disco C:)

Plus

Dentro de la aplicación también encontraran un Tab llamado leer imágenes el cual tiene la funcionalidad de leer las imágenes de una dirección web dada y mostrar el largo y el ancho de la imagen.

LinqToHTMLImg

y el Codigo para esto es:

 HtmlAgilityPack.HtmlWeb hweb = new HtmlAgilityPack.HtmlWeb();
            string strUrl = @textBox1.Text;
            Uri url = new Uri(strUrl);
            HtmlAgilityPack.HtmlDocument doc = hweb.Load(strUrl);
            


            var xdoc = doc.ToXMLDocument();
            var imgs = from x in xdoc.Descendants()
                       let width = int.Parse((x.Attribute("width") ?? new XAttribute(XName.Get("width"), "0")).Value.TrimEnd('%'))
                       let height = int.Parse((x.Attribute("height") ?? new XAttribute(XName.Get("height"), "0")).Value.TrimEnd('%'))
                       let metrica = Math.Sqrt(width * height)
                       where x.Name.LocalName == "img"
                       orderby metrica descending
                       select new
                       {
                           Src = x.Attribute(XName.Get("src")).Value,
                           Width = width,
                           Height = height
                       };

 

Espero que le haya sido de utilidad.

LINQ TO SQL Performance

linq

 

 

LINQ o Language integrated query es un lenguaje de consultas integrado al lenguaje que fue introducido en las nuevas versiones de Visual Studio 2008, LINQ nos permite una sintaxis estándar para consultar diferentes fuentes de datos, sea SQL, XML u objetos en memoria.

Antes del framework 3.5, los desarrolladores típicamente usábamos ADO para consultar alguna que otra base de datos relacional, es decir si queríamos conectarnos y consultar alguna tabla usábamos las librerías de ADO específicamente System.Data.SqlClient. Si queríamos traer datos de la manera más rápida posible, entonces usábamos DataReaders, pero la responsabilidad del mantenimiento de la Data estaba a cargo del Desarrollador, y si el performance no era una de nuestras prioridades entonces optábamos por usar Datasets.

Luego de una conversación con mi amigo Dany Paredes, discutíamos acerca del Performance de LINQ TO SQL, para mi entender desarrollar con LINQ TO SQL es bastante rápido el desarrollo y todo eso, pero que ¿tal el performance?, decidí hacer las pruebas por mí mismo.

Escribí una Aplicación de Consola, para probar que tal el performance de LINQ TO SQL, ADO Datasets y ADO Datareader. La aplicación contiene 3 métodos los cuales cada uno de ellos consultan la tabla Customer de la base de datos northwind que contiene 91 registros 10,000 veces. En el caso para popular el Datareader cree una clase llamada clCustomer.

 

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Text;
using System.Configuration;
using System.Linq;



namespace LinqToSqlTest
{
    public class Program
    {
        public static string myconnectionstring = @"Data Source=.;Initial Catalog=northwind;User Id=sa;Password=sa;";

        static void Main(string[] args)
        {
            DateTime start;
            DateTime endTime;

            start = DateTime.Now;
            GetDataFromDatasets();
            endTime = DateTime.Now;
            Tiempo(start, endTime, "Usando Datasets");

            start = DateTime.Now;
            GetDataFromLinqToSQL();
            endTime = DateTime.Now;
            Tiempo(start, endTime, "Usando LINQ TO SQL");


            start = DateTime.Now;
            GetDataFromDataReaders();
            endTime = DateTime.Now;
            Tiempo(start, endTime, "Usando DataReaders");

            System.Console.ReadLine();
        }

        /// <summary>
        /// Traer los datos desde LINQ TO SQL
        /// </summary>
        private static void GetDataFromLinqToSQL()
        {
            LinqToSqlTestDataContext con = new LinqToSqlTestDataContext();
            var query = con.Customers;

            for (int i = 0; i < 10000; i++)
            {
                query.ToList();
            }
        }

        /// <summary>
        /// Metodo para Traer los datos en un reader
        /// </summary>
        private static void GetDataFromDataReaders()
        {
            SqlConnection con = new SqlConnection(myconnectionstring);
            string query = "select [CustomerID], [CompanyName], [ContactName], [ContactTitle], " +
            "[Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]  from [dbo].[Customers]";
            SqlCommand command = new SqlCommand(query, con);
            con.Open();
            clCustomer sp = new clCustomer();
            for (int i = 0; i < 10000; i++)
            {
                SqlDataReader rdr = command.ExecuteReader();
                while (rdr.Read())
                {
                    sp.CustomerID = rdr["CustomerID"].ToString();
                    sp.CompanyName = rdr["CompanyName"].ToString();
                    sp.ContactName = rdr["ContactName"].ToString();
                    sp.ContactTitle = rdr["ContactTitle"].ToString();
                    sp.Address = rdr["Address"].ToString();
                    sp.City = rdr["City"].ToString();
                    sp.Region = rdr["Region"].ToString();
                    sp.PostalCode = rdr["PostalCode"].ToString();
                    sp.Country = rdr["Country"].ToString();
                    sp.Phone = rdr["Phone"].ToString();
                    sp.Fax = rdr["Fax"].ToString();

                }
                rdr.Close();
            }
        }

        /// <summary>
        /// Metodo para traer los resultados en un Dataset
        /// </summary>
        private static void GetDataFromDatasets()
        {
            SqlConnection con = new SqlConnection(myconnectionstring);
            string query = "select [CustomerID], [CompanyName], [ContactName], [ContactTitle], " +
            "[Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax]  from [dbo].[Customers]";
            SqlCommand command = new SqlCommand(query, con);
            SqlDataAdapter da = new SqlDataAdapter(command);
            DataSet ds = new DataSet();
            for (int i = 0; i < 10000; i++)
            {
                da.Fill(ds);
            }
        }

        private static void Tiempo(DateTime dt1, DateTime dt2, string message)
        {
            string time = ((dt2.Ticks - dt1.Ticks) / 10000000.0).ToString();
            System.Console.WriteLine(message + ": " + time + " Segundos");
        }
    }

    public class clCustomer
    {
        public string CustomerID { get; set; }
        public string CompanyName { get; set; }
        public string ContactName { get; set; }
        public string ContactTitle { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Region { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
        public string Phone { get; set; }
        public string Fax { get; set; }

    }
}

¿Cuáles fueron los resultados?

linqtosqlScreen

  • Datasets (29.313 segundos)
  • LINQ TO SQL (18.629 segundos)
  • DataReaders (18.037 segundos)

Así que tenemos que LINQ TO SQL es mas rápido que un Dataset y ligeramente más lento que un DataReader.

Si quieres puedes probarlo tu mismo copiando el código y creando un Linq To Sql Class llamado northwind.dbml. Solamente es necesario la tabla Customers.

Espero que les sea de utilidad.

{Evento: Desarollo de Aplicaciones Web con Prototype + Ajax + WCF}

communitylaunch


Hola amigos, este mes vamos a estar dando el evento “Desarrollo de Aplicaciones Web con Prototype + Ajax + WCF” todo esto tiene que ver con los “Heroes Community launch” que estamos celebrando en nuestra País República Dominicana.


Primera tanda:
27 de Mayo, 6:00 p.m., salón 204-205

Tema:
Desarrollo de aplicaciones Web con Prototype, Ajax y Windows communication Foundation
Lugar: Universidad Iberoamericana (UNIBE)
Speaker: Esteban Zavala & Mayreni Vargas

Segunda tanda:
Tema: Visual Studio 2008 & SQL 2008: Coming together
Lugar: Universidad Iberoamericana (UNIBE)
Speaker: Ismael Mendez & Hector Minaya


Para los primero 50 en llegar:
1 kit de developers para training
1 Windows Vista Ultimate (de evaluación, por 1 año)

Rifaremos:
20 Libros de Linq
5 Vouchers para exámenes de certificación con un 40% de descuento en los mismos y 5 Vouchers mas si dices que viste esto en geeks.ms 😛
5 kits de lanzamiento, incluyen Windows server 2008 trial de 1 año, SQL Server 2008 Developer, Visual Studio 2008 Standard Full.

Search Commands For Office 2007






searchcommands
Microsoft Office 2007 ha sustituido los menús convencionales y ha integrado una nueva tecnología llamada Ribbon, esta tecnología tiene la particularidad que combina los menús y las herramientas en un solo lugar para así tener un fácil acceso a las opciones que nos brinda office.

Sin embargo, hemos visto como en nuestras lugares de trabajo, familiares, amigos, se han quejado, reprochándonos (tal como si fuera un empleado de Microsoft) que no encuentran las opciones que estaban acostumbrados a usar.

Debido a esto Microsoft Office Labs ha lanzando un nueva herramienta que se integra con Office 2007 English version, específicamente con Word, Excel y Power Point llamada Search Commands, esta nos ayuda a encontrar comandos, opciones, asistentes y galerías, simplemente escribiendo la opción y esta traerá una lista de las posibles opciones.

Pueden ver más información enlace:
http://www.officelabs.com/projects/searchcommands/Pages/default.aspx

Por fin me llego mi {Heroes Community Launch Kit}

silhouette_1_thumb

 

Luego de varias semanas de espera, por fin he recibido el {Heroes Community Launch Kit} y asi poder ser un {Heroe} mas de las comunidades Microsoft.

Dicho Kit contiene:

1 {Exclusive} Windows Server 2008 Deep technical “event-in-a-box”

– 5 Windows Server 2008 Enterprise Edition DVDs

– 5 Visual Studio Standard DVDs

– 5 SQL Server 2008 CTP5 DVDs and a NFR voucher for SQL Server 2008 Enterprise redeemable when SQL Server 2008 is Generally Available (GA)

– 2 Full bit DVDs of Vista Ultimate SP1 NFR 

– 3 Vouchers for a Free 1 year subscription to TechNet Plus

– 1 WS08 Application Readiness Resource kit enhanced with Solutions Accelerator modules

– Microsoft Learning Solutions 40% off Exam Vouchers

– 1 SQL Server 2008 Technical Readiness kit 

– 1 .NET 3.5 Dev Resource Kit

– 1 AMD “Virtualization for Dummies” Handbook

– 40% off TechNet Plus Subscriptions with Full Bit/No Technical Time Bomb Product Download/Eval for All of your attendees

– Heroes {Community} Launch Poster

– CPLS  Certification Poster

 

Luego publicaremos los lugares en la Republica Dominicana donde se daran los eventos para poder repartir el material, dichos eventos seran impartidos por la nueva comunidad de desarrollo que vamos a lanzar que se llamara DotNetLab.

Saludos

  photo-01 photo-02 photo-03

¿Estas contento con Windows Vista SP1? Pues elimina los archivos basura

windows-vista-logo-1

Cuando uno instala el Service Pack 1 en nuestras computadoras,  este no elimina algunos archivos de la antigua version de Windows Vista de nuestro disco duro. Esto es a causa de que estos archivos podrian ser requerido en caso de que decidamos desinstalar el Service pack 1.

Logicamente estos archivos ocupan un importante espacio en nuestro disco duro, en mi caso personal 1.2 GB, entonces si estas sastifecho (como yo) con los resultados obtenidos con el Service Pack 1 y no tienes interes alguno en un futuro de desinstalarlo, entonces porque no reclamas el espacio que estos archivos ocupan en tu disco duro.

Para reclamar este espacio basta solamente seguir los siguientes pasos:

  1. Inicio -> Abrir el command prompt escribiendo cmd en el start search
  2. En el command prompt escribir vsp1cln.exe (Vista SP1 Cleaner).

Estos nos tomara algunos minutos (dependiendo de la maquina) para remover los archivos de la antigua version

  vsp1cln
Esta accion hace que el Windows Vista Service pack 1 se quede de forma permanente en tu computadora. Y el espacio extra nos sirve para ocuparlo con archivos importantes.

Para ejecutar el Vista Sp1 Cleaner no necesitas estar conectado a internet y no podras en un futuro eliminar el Service pack 1, asi que si lo ejecutas es bajo riezgo propio.

Espero que le sea de utilidad.

Agregar un simple Trigger para auditar tu base de datos SQL Server

Despues de pasar muchas horas pensando, debuggiando y buscandole la vuelta a una solucion que consistia en hacerle una auditoria a una base de datos SQL 2005, pudimos desarrollar una herramienta llamada SQLTableHistory esta herramienta nos da la facilidad de que podemos seleccionar las tablas que queremos auditar.

Pero navegando me encontre con una excelente solucion hecha por Jon Galloway, en la cual consiste en crear un solo script y que este se encarge de crear los triggers necesarios para cada tabla de la base de datos seleccionada, la diferencia con SQLTableHistory es que con nuestra herramienta podemos seleccionar las tablas que queremos que se le haga su respectiva auditoria y con la solucion presentada en este blog.

Aqui un ejemplo de como se verian los datos con la solucion de Jon Galloway

 

Con esta informacion podemos hacer reportes de una manera sencilla tales como:

  • Cuales tablas han sufridos cambios recientemente.
  • Cuales tablas no sufrieron cambios el pasado año.
  • Cuales tablas jamaz han sufrido cambios.
  • Mostrar todos los cambios a las tablas por un usuario y periodo especifico.
  • Mostrar las tablas mas activas en un determinado periodo.

    Con estas herramientas es posible volver al estado anterior de la tabla y volver en un punto especifico, claro esta ya depende de las nuevas extensiones que ustedes le hagan.

    Aqui os dejo el script completo que crea la tabla de auditoria y corre el script para crear los triggers a todas las tablas de la base de datos.

    USE MYDATABASE–Ponerle el nombre de la base de datos que va hacer auditada

    GO

     

    IF NOT EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= ‘Audit’)

    CREATE TABLE Audit

    (

    AuditID [int]IDENTITY(1,1) NOT NULL,

    Type char(1),

    TableName varchar(128),

    PrimaryKeyField varchar(1000),

    PrimaryKeyValue varchar(1000),

    FieldName varchar(128),

    OldValue varchar(1000),

    NewValue varchar(1000),

    UpdateDate datetime DEFAULT (GetDate()),

    UserNamevarchar(128)

    )

    GO

     

    DECLARE @sql varchar(8000), @TABLE_NAMEsysname

    SET NOCOUNT ON

     

    SELECT @TABLE_NAME= MIN(TABLE_NAME)

    FROM INFORMATION_SCHEMA.Tables

    WHERE

    TABLE_TYPE= ‘BASE TABLE’

    AND TABLE_NAME!= ‘sysdiagrams’

    AND TABLE_NAME!= ‘Audit’

     

    WHILE @TABLE_NAMEIS NOT NULL

     BEGIN

    EXEC(‘IF OBJECT_ID (»’ + @TABLE_NAME+ ‘_ChangeTracking», »TR») IS NOT NULL DROP TRIGGER ‘ + @TABLE_NAME+ ‘_ChangeTracking’)

    SELECT @sql =

    create trigger ‘ + @TABLE_NAME+ ‘_ChangeTracking on ‘ + @TABLE_NAME+ ‘ for insert, update, delete

    as

     

    declare @bit int ,

    @field int ,

    @maxfield int ,

    @char int ,

    @fieldname varchar(128) ,

    @TableName varchar(128) ,

    @PKCols varchar(1000) ,

    @sql varchar(2000),

    @UpdateDate varchar(21) ,

    @UserName varchar(128) ,

    @Type char(1) ,

    @PKFieldSelect varchar(1000),

    @PKValueSelect varchar(1000)

     

    select @TableName = »’ + @TABLE_NAME+ »’

     

    — date and user

    select @UserName = system_user ,

    @UpdateDate = convert(varchar(8), getdate(), 112) + » » + convert(varchar(12), getdate(), 114)

     

    — Action

    if exists (select * from inserted)

    if exists (select * from deleted)

    select @Type = »U»

    else

    select @Type = »I»

    else

    select @Type = »D»

     

    — get list of columns

    select * into #ins from inserted

    select * into #del from deleted

     

    — Get primary key columns for full outer join

    select@PKCols = coalesce(@PKCols + » and», » on») + » i.» + c.COLUMN_NAME + » = d.» + c.COLUMN_NAME

    fromINFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,

    INFORMATION_SCHEMA.KEY_COLUMN_USAGE c

    where pk.TABLE_NAME = @TableName

    andCONSTRAINT_TYPE = »PRIMARY KEY»

    andc.TABLE_NAME = pk.TABLE_NAME

    andc.CONSTRAINT_NAME = pk.CONSTRAINT_NAME

     

    — Get primary key fields select for insert

    select @PKFieldSelect = coalesce(@PKFieldSelect+»+»,»») + »»»» + COLUMN_NAME + »»»»

    fromINFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,

    INFORMATION_SCHEMA.KEY_COLUMN_USAGE c

    where pk.TABLE_NAME = @TableName

    andCONSTRAINT_TYPE = »PRIMARY KEY»

    andc.TABLE_NAME = pk.TABLE_NAME

    andc.CONSTRAINT_NAME = pk.CONSTRAINT_NAME

     

    select @PKValueSelect = coalesce(@PKValueSelect+»+»,»») + »convert(varchar(100), coalesce(i.» + COLUMN_NAME + »,d.» + COLUMN_NAME + »))»

    from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,   

    INFORMATION_SCHEMA.KEY_COLUMN_USAGE c  

    where  pk.TABLE_NAME = @TableName  

    and CONSTRAINT_TYPE = »PRIMARY KEY»  

    and c.TABLE_NAME = pk.TABLE_NAME  

    and c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME

     

    if @PKCols is null

    begin

    raiserror(»no PK on table %s», 16, -1, @TableName)

    return

    end

     

    select @field = 0, @maxfield = max(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName

    while @field < @maxfield

    begin

    select @field = min(ORDINAL_POSITION) from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName and ORDINAL_POSITION > @field

    select @bit = (@field – 1 )% 8 + 1

    select @bit = power(2,@bit – 1)

    select @char = ((@field – 1) / 8) + 1

    if substring(COLUMNS_UPDATED(),@char, 1) & @bit > 0 or @Type in (»I»,»D»)

    begin

    select @fieldname = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = @TableName and ORDINAL_POSITION = @field

    select @sql = »insert Audit (Type, TableName, PrimaryKeyField, PrimaryKeyValue, FieldName, OldValue, NewValue, UpdateDate, UserName)»

    select @sql = @sql + » select »»» + @Type + »»»»

    select @sql = @sql + »,»»» + @TableName + »»»»

    select @sql = @sql + »,» + @PKFieldSelect

    select @sql = @sql + »,» + @PKValueSelect

    select @sql = @sql + »,»»» + @fieldname + »»»»

    select @sql = @sql + »,convert(varchar(1000),d.» + @fieldname + »)»

    select @sql = @sql + »,convert(varchar(1000),i.» + @fieldname + »)»

    select @sql = @sql + »,»»» + @UpdateDate + »»»»

    select @sql = @sql + »,»»» + @UserName + »»»»

    select @sql = @sql + » from #ins i full outer join #del d»

    select @sql = @sql + @PKCols

    select @sql = @sql + » where i.» + @fieldname + » <> d.» + @fieldname

    select @sql = @sql + » or (i.» + @fieldname + » is null and  d.» + @fieldname + » is not null)»

    select @sql = @sql + » or (i.» + @fieldname + » is not null and  d.» + @fieldname + » is null)»

    exec (@sql)

    end

    end

    SELECT @sql

    EXEC(@sql)

    SELECT @TABLE_NAME= MIN(TABLE_NAME) FROM INFORMATION_SCHEMA.Tables

    WHERE TABLE_NAME> @TABLE_NAME

    AND TABLE_TYPE= ‘BASE TABLE’

    AND TABLE_NAME!= ‘sysdiagrams’

    AND TABLE_NAME!= ‘Audit’

    END

     

    Para mas informacion:

  • Jon Galloway
    SQL Server 2005’s DDL triggers
    Nigel Rivett’s SQL Server Auditing triggers

    Espero que les sea de utilidad!!!

    What’s new in Internet Explorer 8 Beta 1

    Ayer Microsoft anuncio al mundo en la conferencia MIX08 en las vegas la publicacion del primer Beta de Internet explorer 8 para desarrolladores y diseñadores Web. Este primer Beta no trae muchos cambios en la apariencia pero si introduce un nuevo motor de renderizado basado en normas y estandares de la industria entre otras caracteristicas. Entre la que se destacan nuevas herramientas para los desarrolladores , activities y webslides.

    Como pueden ver la UI del explorer no han habido cambios significativos, la primera vez que corremos el explorer nos saldra la ventana de Setup para configurar nuestro explorer.

    firsttime

    Activities

    Activities son nuevas vias que nos brinda internet explorer 8 a los desarrolladores para usar servicios de otros portales web eliminando asi la necesidad de copiar y pegar informacion hacia otro website, me explico anteriormente uno necesitaba copiar una direccion y encontrar la localizacion de dicha direccion en live maps, ahora con activities en IE8 simplemente seleccionamos el texto que contiene la direccion, entonces nos saldra un menu donde estaran las opciones posibles que podriamos hacer con el texto seleccionado, en este caso seleccionamos "Map with live maps" y esta nos brinda un preview del resultado de la opcion seleccionada. Microsoft nos brinda tambien la posibilidad de agregar nuevos activity providers para mas info http://ie.microsoft.com/activities/en-en/Default.aspx

    activities

    Video

    Webslides

    Los Webslides es otra de las nuevas caracteristicas de IE8 permitiendo esta suscribirse a las actualizaciones de un website. Una de los casos donde es bastante util es en ebay. Con los webslides podemos ver de manera facil y si estar actualizando la ventana de ebay los cambios que esta teniendo el producto al cual nos hemos suscribido. La misma caracteristica tambien se puede usar en facebook y otros portales.

    Video

    Este primer beta del internet explorer esta orientado mas como dije al principio a diseñadores y desarrolladores web, muchos portales no se podran ver de manera correcta debido a los cambios en el motor de renderizacion que nos trae IE8. Gracias a Dios y a Microsoft se creo la opcion para emular el IE7 y usar el motor anterior al IE8. Asi que hay que esperar los nuevos cambios y retos que nos traera microsoft con el internet explorer y no desesperarze y juzgar antes de tiempo como han hechos muchos que ya han catalogado al internet explorer 8 como una decepcion total, al parecer se les ha olvidado el significado de Beta y se dejan llevar por el odio despiadado hacia microsoft dejando asi de ser objectivos :P, espero que tambien analizen otras compañias donde a mi entender tienen una pecera de Pez Beta estando casi todos sus productos por mas de 2 y 3 años en fase beta.

    Saludos y bendiciones!!! 😉

    SQLTableHistory utilidad para hacerle auditoria a tus tablas

    SQLTableHistory es una utilidad que hicimos junto a un compañero que nos permite llevar un rastro de auditoria de tablas especificas que podemos seleccionar desde la aplicacion, mantiendo asi un registro de cada accion(insertar, actualizar, eliminar) que involucre las tablas que se le quiera hacer la auditoria.

    Requerimientos

    • .Net Framework 3.5
    • Sql Server 2000-2005

    Arquitectura

    La aplicacion consta de de una sola tabla llamada AUDIT donde guarda cada uno de los registros de las tablas, guardando asi el nombre de la tabla, nombre de la columna que fue afectada el valor anterior y el valor nuevo de la columna, el usuario de la transaccion, fecha y  asi tambien el tipo de transaccion que fue realizada(insertar, actualizar, eliminar).

    Para ser posible esto, la utilidad nos crea tres triggers por cada una de las tablas, uno para insertar, actualizar e eliminar.

    tableaudit

    Script:

    Tabla Audit:

    /****** Object:  Default [DF_Audit_Date]    Script Date: 02/22/2008 19:27:40 ******/
    IF  EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[DF_Audit_Date]’) AND type = ‘D’)
    BEGIN
    ALTER TABLE [dbo].[Audit] DROP CONSTRAINT [DF_Audit_Date]

    END
    GO
    /****** Object:  Table [dbo].[Audit]    Script Date: 02/22/2008 19:27:40 ******/
    IF  EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[Audit]’) AND OBJECTPROPERTY(id, N’IsUserTable’) = 1)
    DROP TABLE [dbo].[Audit]
    GO
    /****** Object:  Table [dbo].[Audit]    Script Date: 02/22/2008 19:27:40 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[Audit]’) AND OBJECTPROPERTY(id, N’IsUserTable’) = 1)
    BEGIN
    CREATE TABLE [dbo].[Audit](
        [AuditID] [int] IDENTITY(1,1) NOT NULL,
        [Table] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
        [Column] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
        [OldValue] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
        [NewValue] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
        [UserID] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
        [Date] [datetime] NOT NULL,
        [Type] [int] NOT NULL,
    CONSTRAINT [PK_Audit] PRIMARY KEY CLUSTERED
    (
        [AuditID] ASC
    )
    )
    END
    GO
    /****** Object:  Default [DF_Audit_Date]    Script Date: 02/22/2008 19:27:40 ******/
    IF Not EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[DF_Audit_Date]’) AND type = ‘D’)
    BEGIN
    ALTER TABLE [dbo].[Audit] ADD  CONSTRAINT [DF_Audit_Date]  DEFAULT (getdate()) FOR [Date]

    END
    GO

     

    Tabla AuditType:

    /****** Object:  Table [dbo].[AuditType]    Script Date: 02/22/2008 19:35:13 ******/
    IF  EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[AuditType]’) AND OBJECTPROPERTY(id, N’IsUserTable’) = 1)
    DROP TABLE [dbo].[AuditType]
    GO
    /****** Object:  Table [dbo].[AuditType]    Script Date: 02/22/2008 19:35:13 ******/
    SET ANSI_NULLS ON
    GO
    SET QUOTED_IDENTIFIER ON
    GO
    IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[dbo].[AuditType]’) AND OBJECTPROPERTY(id, N’IsUserTable’) = 1)
    BEGIN
    CREATE TABLE [dbo].[AuditType](
        [AuditTypeID] [int] NOT NULL,
        [Name] [varchar](20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    CONSTRAINT [PK_AuditType] PRIMARY KEY CLUSTERED
    (
        [AuditTypeID] ASC
    )
    )
    END
    GO

    Y debemos tener estos tres valores por default en nuestra base de datos:
    INSERT [dbo].[AuditType] ([AuditTypeID], [Name]) VALUES (1, convert(text, N’Agregar’ collate SQL_Latin1_General_CP1_CI_AS))
    INSERT [dbo].[AuditType] ([AuditTypeID], [Name]) VALUES (2, convert(text, N’Remover’ collate SQL_Latin1_General_CP1_CI_AS))
    INSERT [dbo].[AuditType] ([AuditTypeID], [Name]) VALUES (3, convert(text, N’Modificar’ collate SQL_Latin1_General_CP1_CI_AS))

    Clases

    Usando Linq to SQL y a la vez el Designer, tenemos las siguientes clases:

    clasese

    Donde:

    SysTable: es la clase que contiene todas las tablas de nuestra base de datos seleccionada, se conecta a la tabla sys.tables

    Table: clase que contiene el schema de las tablas, se conecta a la tabla INFORMATION_SCHEMA.TABLES

    Trigger: es la clase que contiene los triggers de la base de datos, se usa la tabla del sistema sys.triggers

    Column: clase que contiene el schema de las columnas de cada tabla de nuestra base de datos, para obtener esto se usa la tabla del sistema INFORMATION_SCHEMA.COLUMNS

    Si se fijan para todo esto usamos tablas del sistema, ya que estas nos pueden proveer el schema de las tablas y poder asi crear los triggers de una manera dinamica.

    Deben configurar el archivo app.config en la cadena de conexion y espeficiar el connectionstring correspondiente a su base de datos… en un futuro lo pondre para que se pueda hacer de manera mas dinamica.

    Si les interesa pueden descargar el codigo fuente y asi poder echarle una miradita al codigo.

    Preview de la utilidad:

    app

    Espero que les sea de utilidad.