martes, 15 de marzo de 2011

Quitar la hora de un campo datetime

Un pequeño post para empezar el martes con alegría.

Cada vez que tengo que realizar esta operación tengo que rebuscar en código anterior para ver como hacerlo, la verdad es que es muy sencillo pero mis neuronas no dan para grabar a fuego esta operación.

La situación inicial es que tenemos un campo datetime grabado con fecha y hora y por algún motivo necesitamos el campo sólo con la fecha, así que la instrucción Sql para conseguirlo es la siguiente:

CAST(CONVERT(NVARCHAR, MiCampoFecha, 112) AS DATETIME)

Lo que hacemos aquí es:

  • Convertir el campo fecha a su representación en texto con el modificador 112 que nos da una salida de aaaammdd.http://msdn.microsoft.com/es-es/library/ms187928.aspx
  • Convertir el texto obtenido de nuevo a datetime para tener un tipo adecuado (efectivamente no me gusta guardar fechas en texto… y tampoco números en texto...)

Bueno, pues sin más, me despido hasta la siguiente.

Un saludo!.

martes, 8 de marzo de 2011

TransactionScope, ¡ha venido para quedarse!

Todas las aplicaciones que desarrollo en mi empresa están orientadas a datos. De este modo, una de mis grandes preocupaciones es como gestionar de forma eficiente las transacciones en el acceso a datos. Hasta ahora, siempre había utilizado el objeto SqlTransaction para llevar a cabo esta tarea, pero lo cierto es que no estoy del todo satisfecho con su uso. Para intentar argumentar porque no quiero SqlTransaction, primero veamos un ejemplo de cómo utilizar este objeto.

    Sub Main()

        Using cnn As New SqlConnection("Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;Integrated Security=True")

            cnn.Open()

            Dim tran As SqlTransaction = Nothing

            Try

                tran = cnn.BeginTransaction

                InsertarPrimerCliente(cnn, tran)

                InsertarSegundoCliente(cnn, tran)

                tran.Commit()

            Catch ex As Exception

                If Not tran Is Nothing Then

                    tran.Rollback()

                End If

                Console.WriteLine(ex.Message)

            Finally

                Console.ReadLine()

            End Try

        End Using

    End Sub

 

    Sub InsertarPrimerCliente(ByVal cnn As SqlConnection, ByVal tran As SqlTransaction)

        Dim cmdText As String = "INSERT INTO Clientes VALUES (1, 'Cliente 1')"

        Using cmd As New SqlCommand(cmdText, cnn, tran)

            cmd.ExecuteNonQuery()

        End Using

    End Sub

 

    Sub InsertarSegundoCliente(ByVal cnn As SqlConnection, ByVal tran As SqlTransaction)

        Dim cmdText As String = "INSERT INTO Clientes VALUES (2, 'Cliente 2')"

        Using cmd As New SqlCommand(cmdText, cnn, tran)

            cmd.ExecuteNonQuery()

        End Using

    End Sub

 

Realmente este código no me gusta porque los métodos “InsertarPrimerCliente” e “InsertarSegundoCliente” tienen que recibir como parámetros tanto el objeto SqlConnection como el objeto SqlTransaction. Esto es así porque si quiero que los comandos ejecutados desde estos métodos y contra la base de datos participen todos en la misma transacción, tiene que compartir tanto la misma conexión como la misma transacción.

Creo que es fácil entender que la dependencia de este modelo transaccional en ADO.NET implica mantener siempre viva la referencia tanto al objeto SqlConnection como al objeto SqlTransaction. Ahora imagina, no 2 métodos, sino X métodos en distintas clases, distintos ensamblados, etc. No sé, en mi opinión, en el momento que tu código transaccional ya no es sólo una sencilla llamada a un único método con un principio y fin concreto, este modelo en vez de ayudarme, me la hace la vida más difícil.

De hecho, si por ejemplo sólo pasamos a los métodos el objeto SqlConnection, pero no el objeto SqlTransaction, obtendremos este bonito error que para mí ya es todo un clásico:
ExecuteNonQuery requiere que el comando tenga una transacción cuando la conexión asignada al mismo está en una transacción local pendiente. No se ha inicializado la propiedad Transaction del comando..
Por otro lado, si directamente no pasamos ni la conexión ni la transacción, nuestros comandos en métodos como los expuestos serán simple y llanamente una nueva conexión sin intervenir en el contexto de ninguna transacción.

Para solucionar esto, hemos ideado en nuestro empresa un contexto ficticio de conexión y transacción que mejor o peor funciona y evita tener que pasar constantemente estos parámetros (SqlConnection y SqlTransaction), pero aún así es una carga extra de diseño en cualquier programa que hay que soportar y por supuesto no está exenta de errores ni de posibles refactorizaciones.

En este punto creí que todo estaba perdido hasta que descubrí el objeto TransactionScope.

Ahora, el anterior código pasaría a ser este otro:

Sub Main()

        Using tran As New Transactions.TransactionScope

                Try

                    InsertarPrimerCliente()

                    InsertarSegundoCliente()

                    tran.Complete()

                Catch ex As Exception

                    Console.WriteLine(ex.Message)

                End Try

        End Using

        Console.ReadLine()

    End Sub

 

    Sub InsertarPrimerCliente()

        Using cnn As New SqlConnection("Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;Integrated Security=True")

            cnn.Open()

            Dim cmdText As String = "INSERT INTO Clientes VALUES (1, 'Cliente 1')"

            Using cmd As New SqlCommand(cmdText, cnn)

                cmd.ExecuteNonQuery()

            End Using

        End Using

    End Sub

 

    Sub InsertarSegundoCliente()

        Using cnn As New SqlConnection("Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;Integrated Security=True")

            cnn.Open()

            Dim cmdText As String = "INSERT INTO Clientes VALUES (2, 'Cliente 2')"

            Using cmd As New SqlCommand(cmdText, cnn)

                cmd.ExecuteNonQuery()

            End Using

        End Using

    End Sub

 

Si nos fijamos, ahora simplemente “iniciamos un contexto transaccional” y a partir de aquí cualquier comando ejecutado contra esa misma conexión o contra cualquier otra nueva conexión será ejecutado de forma automática en el contexto de la misma “transacción de ambiente” (más adelante veremos que se puede configurar esto, pero en principio este es el comportamiento predeterminado). Por otro lado, sólo hay que controlar el éxito de la operación para llamar al método Complete, mientras que para anular, revertir, deshacer o rechazar la transacción (hoy estoy que lo tiro con los sinónimos), simplemente se hará automáticamente si al salir de la instrucción Using donde se declaró el objeto TransactionScope no se llamó de forma explícita al método Complete.

Para la utilización de TransactionScope es necesario agregar una referencia a System.Transactions.
Esta referencia sólo es necesario en los proyectos que hacen referencia explícita al objeto TransactionScope. Si por ejemplo la función “InsertarPrimerCliente” estuviera en una biblioteca de clases, participaría de la transacción pero no sería necesario agregar la referencia a System.Transactions porque no la utiliza de forma explícita.

Realmente, TransactionScope utiliza MSDTC (Microsoft Data Transaction Coordinator, coordinador de transaciones distribuidas) para llevar a cabo su tarea. De hecho y donde radica principalmente la potencia de TransactionScope (más allá de su facilidad de uso frente a SqlTransaction) es que es capaz de incluir en misma transacción, operaciones contra distintos orígenes de base de datos o recursos transaccionales (por ejemplo las colas también son transaccionables). Eso significa que en nuestro ejemplo anterior, “InsertarPrimerCliente” podría haber insertado en una base de datos A, mientras que “InsertarSegundoCliente” podría haberlo hecho en una base de datos B, todo ello en una misma transacción que se completaría o rechazaría como una operación ACID.

Algo importante es que si estamos utilizando una sola base de datos en nuestra transacción, sólo tenemos una conexión abierta a la vez y además no tenemos transacciones anidadas (todos estos requisitos se cumplen en el ejemplo anterior), TransactionScope utilizará una “transacción ligera” gestionada por LTM (Lightweight Transaction Coordinator), pero en el momento en que utilicemos 2 o más base de datos, las cadenas de conexión sean distintas (aunque finalmente apunten a la misma base de datos y servidor) o anidemos transacciones, la transacción ligera será promocionada a MSDTC.  Además, todo lo anterior se cumple en SQL Server 2008, pero en versiones anteriores (2005) incluso abrir 2 conexiones simultaneas a la misma base de datos y con la misma cadena de conexión, también promocionará la transacción, ver más en Avoid unwanted Escalation to Distributed Transactions

Cuando decía que hay que tener cuidado con las cadenas de conexión me refería a que si accedemos a nuestra única base de datos (en la que suponemos será gestionada por LTM) pero con distintas cadenas de conexión, entonces TransactionScope pensará que son distintas base de datos y pasará a ser una transacción gestionada por MSDTC.

Para ver esto lo mejor será un ejemplo. Simplemente cambiaremos la cadena de conexión de “InsertarPrimerCliente” a otra cadena de conexión válida para la misma base de datos y observaremos como ahora pasamos de LTM a MSDTC.

InsertarPrimerCliente

Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;User Id=sa;Password=XX

InsertarSegundoCliente

Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;Integrated Security=True

 

clip_image002[4]

Sobra decir que está claro que la transacción ha sido promovida a MSDTC, ¡si es un perro nos muerde!, pero además vemos como hemos recibido un error que nos informa de que MSDTC no está disponible en nuestro equipo. Aunque podría ser más complicado, inicialmente para configurar MSDTC tienes que abrir “Servicios de componentes” y en las propiedades de DTC local, activar el checkbox “Permitir clientes remotos”.

Una vez ya tenemos configurado MSDTC, las transacciones promovidas podemos visualizarlas también en “Servicios de componentes”. Vamos a poner un Console.ReadLine() antes de llamar a Complete() para poder ver esto.

clip_image004[4]

clip_image006[4]

Lo que hemos hecho hasta ahora ha sido romper el hielo con el tema de TransactionScope, pero ahora debemos ver unas cuantas opciones más en lo relativo a su uso que cerrarán el círculo y nos permitirán tener mayor control sobre cómo y cuándo suceden las cosas.

Todos los ejemplos vistos han utilizando la instrucción Using, diciendo que si al salir de ella aún no se ha llamado al método Complete() los cambios serán rechazados. Esto es totalmente cierto, pero también es totalmente cierto que no es necesario utilizar Using (aunque si recomendable) puesto que Using lo único que asegura es que se llama siempre al método Dispose(). Pues bien, es en ese método donde se haya toda la lógica de rechazo de la transacción, así que si tienes la necesidad de usar TransactionScope sin Using, simplemente llama a Dispose() y obtendrás la misma funcionalidad que con Using.

La clase TransactionOptions permite especificar el comportamiento de la transacción. Por ejemplo, la propiedad Timeout especifica el tiempo de espera máximo de la transacción antes de que sea abortada automáticamente, mientras que la propiedad IsolationLevel especifica el nivel aislamiento de la transacción.

Y ahora viene en mi opinión la parte más importante y que es precio entender para manejar correctamente TransactionScope y es la relación entre la transacción de ambiente con siguientes nuevas transacciones declaradas en el código. Veamos esto con un ejemplo:

        Using cnn As New SqlConnection("Data Source=SERGIO-VAIO;Initial Catalog=Pedidos;Integrated Security=True")

            cnn.Open()

            Using tran1 As New TransactionScope

                InsertarPrimerCliente()

                Using tran2 As New TransactionScope

                    InsertarSegundoCliente()

                    tran2.Complete()

                End Using

                tran1.Complete()

            End Using

        End Using

 

En este ejemplo podemos ver como hay 2 TransactionScope, una de ellas anidada dentro de otra (haz esto extensible a llamadas entre métodos, etc.). ¿Cómo se comporta entonces la segunda transacción?

Primero decir que la siguientes instrucciones son equivalentes:

Using tran As New TransactionScope()

 

Using tran As New TransactionScope(TransactionScopeOption.Required)

 

Cuando declaramos un nuevo objeto TransactionScope podemos definir su comportamiento respecto a una posible transacción de ambiente existente. El valor predeterminado es Required, pero veamos que valores puede tomar esta opción:

Required

El ámbito requiere una transacción.

Utiliza una transacción de ambiente si ya existe una.

De lo contrario, crea una nueva transacción antes de introducir el ámbito. Éste es el valor predeterminado.

RequiresNew

Siempre se crea una nueva transacción para el ámbito.

Supress

Se suprime el contexto de transacción de ambiente al crear el ámbito.

Todas las operaciones dentro del ámbito se realizan sin un contexto de transacción de ambiente.

 

Esto traducido al español es:

Required

Si hay una transacción de ambiente la utilizaré sino crearé una nueva.

RequiresNew

Me da igual si hay o no hay una transacción de ambiente, yo a mi bola en una nueva transacción.

Supress

Paso de transacciones, soy un machote y no quiero ejecutarme en el contexto de ninguna transacción.

 

Cabe mencionar que todos estos ejemplos lo estoy realizando con SQL Server 2008 y al menos esta versión soporta perfectamente transacciones anidadas, por lo que por ejemplo si la transacción externa es Required y la interna RequiresNew se crearán 2 transacciones (esto es 2 BEGIN TRANSACTION en SQL Server).

Por último, un par de apuntes sobre el método Complete y sobre como averiguar si nuestro código está en el contexto de una transacción y en qué transacción.

  • Si se llama a Complete para la misma instancia de TransactionScope 2 o más veces, excepto la primera, las siguientes fallarán con System.InvalidOperationException.
  • Si se llama a Complete desde una transacción anidada especificada como Required (ámbito predeterminado), realmente no se está confirmando la transacción y no tiene por ahora ningún efecto en la misma, sólo la llamada a Complete de la transacción de ambiente (la más externa) será la que confirma o rechace los cambios.
  • La propiedad Transaction.Current nos devuelve (también sirve para establecer) la transacción de ambiente. Además también nos devuelve información sobre la transacción ¿Recuerdas ese maravillo GUID que salía en “Servicios de componentes” como identificador de la transacción activa?.

Bueno, la verdad es que este post podría continuar y continuar, pero por hoy ya es suficiente.

Lo dicho, TransactionScope ha venido para quedarse.

Un saludo!

lunes, 28 de febrero de 2011

A veces veo “Cookies”

Explicar ahora mismo como funcionan las cookies en ASP.NET me parecería volver a escribir sobre algo que ya está suficientemente explotado en Internet. Sin embargo, trabajando hoy en un proyecto que hace un uso intensivo de las mismas, me he encontrado con un par de situaciones, cuando menos curiosas, que creo si podrían aportar algo nuevo al debate y explicación de cookies en ASP.NET.

En el siguiente enlace se explica casi todos los temas relacionados con cookies en ASP.NET, http://msdn.microsoft.com/es-es/library/ms178194.aspx. Una de los puntos explicar cómo acceder a una cookie en el objeto Request, es decir, saber si hemos recibido del cliente una determinada cookie.

        If Not Request.Cookies.Item("panicoenlaxbox") Is Nothing Then

            ' La cookie existe

        End If


El anterior código no tiene ningún problema y funciona satisfactoriamente, pero cuál es mi sorpresa cuando veo que para preguntar si hemos establecido el valor de una cookie durante el ciclo de vida de nuestra página, el código de comprobación cambia radicalmente. Es decir, un código como el siguiente no funciona como yo pienso cabría esperar:

        If Not Response.Cookies.Item("panicoenlaxbox") Is Nothing Then

            ' Si utilizamos Response, nunca una cookie será Nothing...

            ' Ahora debemos comprobar la propiedad Value de la cookie

        End If

 
Lo que pasa es que si utilizamos Response, la propiedad Item nunca nos devolverá Nothing, luego siempre hay que comprobar, no ya la existencia de la cookie, sino el valor de la cookie.

        If Not String.IsNullOrEmpty(Response.Cookies.Item("panicoenlaxbox").Value) Then

            ' La cookie existe

        End If

 
Ahora que parece que ya hemos solucionado esta dicotomía en la forma de preguntar por la existencia de una cookie, nos encontramos con un comportamiento aún más extraño y que yo, y a menos que alguien me lo explique, no termino de entender. El comportamiento “extraño” consiste en que simplemente por el mero hecho de acceder a la propiedad Item de la colección Response.Cookies, la cookie por la preguntamos es creado automáticamente con el valor “” para su propiedad Value, tanto en la colección Response como en la colección Request. Veamos un ejemplo que no quiero quedar como un mentiroso:

Inicialmente tenemos esta situación en lo relativo a cookies.

clip_image002

Ahora simplemente escribimos el siguiente código:

        If Not String.IsNullOrEmpty(Response.Cookies.Item("panicoenlaxbox").Value) Then

            ' La cookie existe

        End If

 

Y vemos como todo cambia “mágicamente”.

clip_image004

De hecho, esto se traduce en la siguiente cabecera enviada al navegador cliente.

clip_image006

Otro punto interesante en lo relativo al uso de las cookies es que cuando agregamos una cookie, no se comprueba que el nombre sea único en la colección, y de este modo podemos tener 2 cookies con el mismo nombre. Por ejemplo:

        Dim cookie As New HttpCookie("panicoenlaxbox", "primer pánico")

        Response.Cookies.Add(cookie)

        cookie = New HttpCookie("panicoenlaxbox", "segundo pánico")

        Response.Cookies.Add(cookie)

 

clip_image008

Si ahora preguntamos por el valor de la cookie “panicoenlaxbox” el valor devuelto por la colección es siempre el primer elemento de la colección, luego será “primer pánico” en Response y “” en Request.

clip_image010

Y con sus correspondientes 2 cabeceras!!

clip_image012

La conclusión es que mientras que en el código de la página .aspx cuando preguntábamos por “panicoenlaxbox” nos devolvía “primer pánico” (la primera aparición en la colección), en el siguiente PostBack de la página nos devolverá “segundo pánico” puesto que de las 2 cabeceras Set-Cookie ha prevalecido la segunda.

clip_image014

La verdad es que después de todo esto, sólo me queda el consuelo que el método Remove(NombreCookie) elimina todas las cookies con el mismo nombre en la colección, así que si entras en barrena y tienes que borrar cookies al menos puede estar seguro de haber dejado limpia la colección ;-)

Un saludo!