SQL Server

Entity Framework (III): Select, Insert, Update, Delete


Pese a que LINQ to Entities es bastante parecido a LINQ to SQL, a la hora de trabajar con ambas tecnologías es necesario conocer las diferencias más importantes a nivel práctico (dejaremos los fundamentos teóricos a un lado). Ambas tecnologías tienden a la convergencia, puesto que Microsoft está intentando coger lo mejor de cada una de ellas y adaptarlo a ambos mundos. Sin embargo, siguen existiendo diferencias importantes, especialmente en las primeras versiones de LINQ to Entities.

Select. Carga diferida explícita.

Las primeras versiones de Entity Framework contenían ciertas carencias que, con posteriores actualizaciones, han sido solventadas y corregidas. Una de las carencias más importantes se correspondería con la imposibilidad de las versiones anteriores a 4.1 de realizar una carga automática de los elementos referenciados por un objeto. Es decir, si tenemos el siguiente código:


            // Instanciamos el contexto
            var contexto = new testdbEntities();

            // Lanzamos una consulta
            var clientes = from cliente in contexto.Clientes
                           select cliente;

            // Recorremos los clientes
            foreach (Cliente cliente in clientes)
            {
                Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
                    cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));

                // Recorremos los pedidos
                foreach (Pedido pedido in cliente.Pedidos)
                {
                    Console.WriteLine(string.Format("\tPEDIDO: {0}\tFECHA: {1}",
                        pedido.IdPedido, pedido.FechaPedido));

                    // Recorremos las líneas de pedido
                    foreach (LineaPedido linea in pedido.LineasPedido)
                    {
                        Console.WriteLine(string.Format("\t\tPRODUCTO: {0}\tCANTIDAD: {1}\tTOTAL: {2}",
                            linea.Producto.Descripcion, linea.Cantidad, (linea.Producto.Precio*linea.Cantidad)));
                    }
                }
                Console.WriteLine(" -----------------------------------------\n");
            }

 

(más…)

Entity Framework (II): ObjectContext y Entity SQL


Una de las mayores ventajas de Entity Framework es que es una tecnología agnóstica respecto a la base de datos que tiene por debajo. Con ADO.NET era necesario utilizar clases específicas para cada base de datos (SqlCommand para SQLServer, OracleCommand para Oracle, etc.). Sin embargo, Entity Framework no se casa con nadie: hace uso de elementos genéricos (EntityConnection, EntityCommand, etc.) que genera instrucciones en un lenguaje intermedio denominado Entity SQL, que es muy similar al lenguaje SQL estándar.

En última instancia, el ADO.NET de toda la vida estará trabajando por debajo, pero Entity Framework establece una capa de abstracción superior que evita la necesidad de atarnos a una fuente de datos concreta. No obstante, es posible descender un poco en el nivel de abstracción y, en lugar de hacer uso de LINQ to Entities tal y como hicimos en el artículo anterior, lanzar directamente consultas sobre el ObjectContext usando para ello Entity SQL.

Dependiendo de la versión de Entity Framework que se esté usando, esto se realizará de un modo u otro, ya que la versión 4.1 introdujo como novedad la utilización del DbContext por defecto, envolviendo el ObjectContext dentro de él.

Por lo tanto, si queremos acceder al ObjectContext del DbContext que el diseñador habrá generado por nosotros, será necesario realizar un casting explícito a IObjectContextAdapter y acceder a su propiedad ObjectContext, tal y como mostramos en el siguiente ejemplo, en el que se ejecuta una sentencia Entity SQL en la que se recuperan todos los clientes de la tabla Clientes.


            // Instanciamos el DbContext
            var dbContext = new testdbEntities();

            // Extraemos el ObjectContext del DbContext (a partir de Entity Framework 4.1)
            var objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;

            // Ejecutamos una sentencia de Entity SQL que recupere todos los clientes
            string sqlQuery = "SELECT VALUE c FROM Clientes AS c";
            var clientes = new ObjectQuery<Cliente>(sqlQuery, objectContext);

            foreach (Cliente cliente in clientes)
            {
                Console.WriteLine(string.Format("ID: {0}\tNOMBRE: {1}\tAÑO NAC: {2}",
                    cliente.IdCliente, cliente.Nombre, cliente.FechaNacimiento.Year));
            }

El nombre de la tabla Clientes está en plural porque es el nombre que recibe la propiedad en la clase de contexto generada por el editor, no porque sea el nombre de la tabla de la base de datos. Recordemos que, Entity SQL realiza consultas sobre entidades, es decir, objetos, no sobre la base de datos.

Nuevamente, comprobamos que la consulta recupera los datos correctamente.

LINQ to SQL (IV): Modificación de datos


Antes de la aparición de LINQ, la filosofía de ADO.NET estaba orientada a datos. De este modo, cuando era necesario modificar o eliminar un registro, nuestro afán era detectar la clave primaria de ese elemento y generar, bien de forma estática, bien de forma dinámica, una sentencia UPDATE o DELETE para que la fuente de datos realizase la operación pertinente sobre ella.

La aparición de los mappers objeto-relacionales como LINQ to SQL intentan hacer algo difícil: desterrar ese concepto de nuestra cabeza y obligarnos a pensar exclusivamente en objetos.

Esta diferencia, al implicar un cambio de paradigma, puede resultar duro. Para ilustrar la diferencia entre ADO.NET básico y LINQ to SQL, veamos un ejemplo.

Anteriormente podíamos usar un DataAdapter para mapear una tabla de base de datos dentro de un DataTable. Y era posible, a partir de ese DataAdapter, obtener dos copias de un mismo conjunto de datos, es decir:


            //Obtenemos la cadena de conexión de App.config o Web.config
            string cadenaConexion = ConfigurationManager.ConnectionStrings["TestDb"].ConnectionString;

            // Creamos una conexión a partir de la cadena de conexión
            using (SqlConnection conexion = new SqlConnection(cadenaConexion))
            {
                // Declaramos una consulta
                string sqlSelect = "select * from Cliente";

                // Abrimos la conexión y generamos un SqlCommand con la conexión y la consulta
                // Instanciamos un SqlDataAdapter a partir del SqlCommand
                conexion.Open();
                SqlCommand commandSelect = new SqlCommand(sqlSelect, conexion);
                SqlDataAdapter dataAdapter = new SqlDataAdapter(commandSelect);

                //Declaramos dos DataTables y los rellenamos con el DataAdapter
                DataTable dt1 = new DataTable();
                DataTable dt2 = new DataTable();

                dataAdapter.Fill(dt1);
                dataAdapter.Fill(dt2);

                conexion.Close();
            }

(más…)

Permitir conexiones locales a IIS y SQL Server en Windows 7


A la hora de desarrollar una aplicación .NET / ASP.NET con persistencia de datos, será necesario establecer una conexión con un servidor de base de datos. Generalmente, .NET suele hacer uso del binomio ASP.NET/SQL Server, para lo cual suelen darse, dejando a un lado los entornos de pruebas, integración y producción, dos escenarios típicos:

  • Utilizar una base de datos local en la que “cacharrear” y otra base de datos de desarrollo común a todos los desarrolladores de la aplicación.
  • Utilizar directamente una base de datos de desarrollo sin hacer uso de una base de datos local.

El segundo escenario típicamente suele consistir en un servidor SQL Server en red local, que bien puede ser dedicado (preferiblemente) o directamente (aunque sea mala práctica) el equipo de un compañero concreto.

Sin embargo, el acceso al equipo de un equipo en red local, políticas de red aparte, no es automático, sino que requiere permitir que tanto el servidor web (IIS 7 en nuestro caso) como el servidor de base de datos (ejemplificaremos con SQL Server 2012) permita el acceso a conexiones remotas y no sólo locales. Veremos a continuación cómo lograr este objetivo. (más…)

Crear un campo autoincrementable en ORACLE


Para los que nos iniciamos en los Gestores de Bases de Datos con SQL Server, la transición a ORACLE resulta siempre más dura que cuando se realiza el camino inverso. Realizando una analogía simple, en mi caso podría decir que T-SQL es a PL/SQL lo que C# es a C++. Más complicación, más potencia… y un sistema más “tiquismiquis” que los desarrollados por Microsoft.
SQL Server proporciona una forma muy sencilla de crear campos autonuméricos, generalmente utilizados en los IDs de las claves primarias de las tablas. Para crear un campo autoincrementable, lo único que teníamos que hacer era editar las propiedades de la columna que queríamos convertir en autoincrementable, acceder a sus propiedades y seleccionar la opción [Especificación de Identidad] marcando la casilla (Identidad) e informando del número de inicio del índice (Inicialización de identidad) y el número de unidades que se incrementará cada vez que se intente insertar un registro (Incremento de identidad).

Oracle nos pone las cosas un poco más difíciles, obligándonos a realizar dos pasos para realizar el mismo procedimiento: crear una secuencia y crear un trigger.

(más…)

Utilizar la Base de Datos como repositorio de imágenes (II)


En un artículo anterior aprendimos a almacenar una imagen en nuestra base de datos SQL Server. Ahora vamos a obtener la imagen que almacenamos previamente utilizando como dirección de la imagen un formulario aspx al que le pasaremos el identificador de la imagen a través de la QueryString.

Comenzaremos creando un nuevo formulario, al que llamaremos showImage.aspx. En el formulario, crearemos un nuevo método al que llamaremos ObtenerImagen, que recibirá como parámetro un entero (el ID de la imagen) y devolverá una secuencia de bytes (un byte[]).

byte[] ObtenerImagen(int idImagen)
{
}

Lo primero que hará este método será declarar una cadena de conexión y una sentencia SELECT que devuelva la imagen cuyo ID le pasamos. Para ello, declararemos ambas cadenas de texto.


string selectCommandText = "SELECT Fichero FROM Imagen WHERE IdImagen = " + Convert.ToString(idImagen);
string ConnectionString = @"Data Source=DANIGARCIASQLSERVER2005;Initial Catalog=TestDB;Persist Security Info=True;User ID=dani;Password=c0ntr4s3n14";

A continuación podemos hacer dos cosas: crear un SqlCommand y ejecutar la sentencia SELECT, o bien declarar un DataSet y un SqlDataAdapter y dejar que sea éste último objeto el que nos proporcione los datos. Optaremos por la segunda opción, que pese a ser menos óptima, resulta mucho más cómoda 🙂


// Declaramos un DataSet y un SqlDataAdapter
DataSet ds = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter(selectCommandText, selectConnectionString);

// Rellenamos el DataSet con la consulta y la cadena de conexión anteriores
adapter.Fill(ds);

Llegados a este punto, ya deberíamos tener almacenada en el DataSet la imagen que estábamos buscando. Por lo tanto, lo único que deberemos hacer será serializarla y devolverla, realizando las comprobaciones pertinentes:


if ((ds != null) && (ds.Tables.Count > 0) && (ds.Tables[0].Rows.Count > 0))
{
if (!ds.Tables[0].Rows[0].IsNull("Fichero"))
{
return ((byte[])(ds.Tables[0].Rows[0]["Fichero"]));
}
}

return null;

Llegados a este punto, codificaremos el contenido del Page_Load de nuestro aspx, comprobando la QueryString y pasándole el parámetro a nuestra función ObtenerImagen, declarando, en primer lugar, un array de Bytes para almacenar el resultado:


protected void Page_Load(object sender, EventArgs e)
{
byte[] bImagen = null;

if (!string.IsNullOrEmpty(Convert.ToString(Request.QueryString["id"])))
{
bImagen = ObtenerImagen(Convert.ToInt32((Convert.ToString(Request.QueryString["id"]))));
}

Si el resultado obtenido no es nulo, modificamos la respuesta para que sea de tipo imagen, haciendo lo siguiente:


if(bImagen != null)
{
Response.Clear();
Response.Buffer = true;
Response.ContentType = "image/jpeg";
Response.BinaryWrite(bImagen);
Response.End();
}
}

Con lo cual, si invocamos la ruta a través del ID, obtendremos directamente una imagen:


Esto es una prueba <br />
<img src="../aspx/showImage.aspx?id=1" />

Utilizar la Base de Datos como repositorio de imágenes (I)


En cierto proyecto necesitaba mostrar una serie de imágenes que el usuario debía insertar desde su equipo local. En un principio pensé enviar al servidor las imágenes, guardar la ruta relativa en Base de Datos y, cuando fuese necesario acceder a las imágenes, utilizar dicha ruta para acceder a la imagen. Pero no era tan sencillo. Por desgracia, no teníamos permiso de escritura en el disco duro, por lo que tenía que juguetear con las imágenes sin que éstas existieran físicamente. ¿Cómo? Almacenando y recuperando las imágenes de base de datos.

Guardando una imagen en SQL Server

Para empezar, veremos cómo almacenar en base de datos una imagen. Crearemos, desde el SQL Server Management Studio, una nueva tabla que tendrá tres campos:

  • Un Id único entero y autoincrementable (IdImagen)
  • Un nombre para la imagen nvarchar(50) (NombreImagen)
  • Una secuencia de bytes, variable de tipo image (Fichero).

09111201 Creada la tabla, crearemos una página que, a partir de la ruta de la imagen, la inserte en base de datos. Podemos utilizar un Input File o un control similar para indicarle la ruta, pero aquí indicaremos únicamente el código necesario para subir la imagen dada su ruta física. Lo que haremos a continuación será lo siguiente:

  • Crear una conexión a Base de Datos a partir de una cadena de conexión
string ConnectionString = @"Data Source=DANIGARCIASQLSERVER2005;Initial Catalog=TestDB;Persist Security Info=True;User ID=dani;Password=c0ntr4s3n14";
  • Crear una cadena de texto con la sentencia INSERT.
string CommandString = "INSERT INTO Imagen(NombreImagen, Fichero) VALUES (@NombreImagen, @Fichero)";
  • A continuación necesitaremos convertir nuestra imagen en un objeto que podamos manejar, por ejemplo un array de bytes. Para ello, a partir de la ruta física de la imagen crearemos un objeto de tipo System.Drawing.Bitmap que guardaremos en un MemoryStream que, a su vez, convertiremos en un array de bytes (byte[]).
  byte[] bImagen = null; System.IO.MemoryStream ms = new System.IO.MemoryStream();  System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(@"C:TEMPinicio.jpg"); if (bmp != null) { bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); bImagen = ms.ToArray(); ms.Close(); }  
  • Instanciar un objeto de tipo SqlCommand a partir de la  conexión y de la sentencia INSERT.
  SqlConnection conexion = new SqlConnection(ConnectionString);  SqlCommand command = new SqlCommand(CommandString, conexion);  
  • Añadiremos ahora los SqlParameters necesarios al objeto SqlCommand con los datos a insertar en la base de datos. Estos serán, en nuestro caso, el nombre de la imagen y el array de bytes con la imagen.
  SqlParameter nombre = new SqlParameter("@NombreImagen", "inicio.jpg"); SqlParameter imagen = new SqlParameter("@Fichero", bImagen);  
  • Por último, abrimos la conexión, ejecutamos la sentencia mediante ExecuteNonQuery() y cerramos la conexión. Hecho esto, tendremos nuestra imagen almacenada en base de datos.
  conexion.Open();  command.Parameters.Add(nombre); command.Parameters.Add(imagen); command.ExecuteNonQuery();  conexion.Close();  

Hecho esto insertaremos una imagen en una tabla de la base de datos. 09111202 Más adelante aprenderemos cómo recuperarla y referenciarla directamente a través de una URL con extensión aspx, es decir, invocar una ruta de tipo getImage.aspx?Id=xxx.

Obtener el identificador de un registro recien insertado en SQL Server


Cuando insertamos un registro en una tabla en la que su Identificador es único y autoincrementable, podemos solicitar a la base de datos que nos devuelva ese ID en concreto para realizar o encadenar nuevas operaciones. Ese ID se obtiene llamando a la función SCOPE_IDENTITY() de T-SQL.

Así, si almacenamos la siguiente sentencia en una cadena de texto:

string sentenciaSQL = &quot;INSERT INTO USUARIOS(Nombre, Apellidos, NIF, Login, Password) VALUES('Alberto' 'Lopez Villarino', '12345678Z', 'alopezv', '7EVASGEAGE9='); SELECT SCOPE_IDENTITY();&quot;;

Al invocar al método ExecuteScalar() de un objeto de la clase SqlCommand utilizando la sentencia anterior, el valor devuelto será, precisamente, el ID del registro insertado.

Transacciones en ADO.NET (II): Activando las Transacciones Distribuidas mediante MSDTC


Anteriormente hemos aprendido a utilizar entornos transaccionales o Transaction Scopes. El ejemplo anterior mostraba lo que se conoce como una ‘transacción ligera’ que afecta a varias tablas de una misma fuente de datos. Sin embargo, en numerosas ocasiones se nos presentará la dificultad añadida de trabajar con múltiples fuentes de datos localizadas incluso en puntos geográficos diversos.

Para solventar esta dificultad, el propio Windows nos ofrece la posibilidad de utilizar las llamadas Transacciones distribuidas. Por defecto aparecen deshabilitadas, por lo que a continuación, mostraremos cómo activarlas. Una vez activadas, utilizaremos la clase TransactionScope como si se tratara de una transacción ligera, dejando en manos del sistema operativoel control de la propia transacción.

(más…)

Abstracción de Datos (V): Aplicando el patrón Abstract Factory con C# y ADO.NET


Por norma general, un proyecto estará “atado” a una fuente de datos en particular (SQL Server, MySQL, Oracle…). Sin embargo, hay veces que esto no será así: la fuente de datos podría cambiar en cualquier momento, por lo que deberíamos ser capaces de, en la medida de lo posible, abstraer nuestra aplicación de nuestra base de datos.

Previamente comentábamos la flexibilidad otorgada por el patrón de diseño Abstract Factory. Hoy veremos una aplicación práctica, orientada precisamente a obtener práctica independencia de la fuente de datos mediante la utilización de clases comunes a todas las fuentes de datos.

Por norma general utilizamos clases específicas para lograr interactuar con una fuente de datos en concreto (por ejemplo, usaremos un objeto de la clase SqlConnection cuando trabajemos con SQL Server). Éstas clases específicas están optimizadas para trabajar con unos esquemas determinados, aportando un importante incremento del rendimiento de la aplicación. Pero como todo en esta vida, no es gratis. ¿Cuál es el precio? La portabilidad.

(más…)

Comprobar la existencia de una tabla en SQL Server


Cuando tratamos con un procedimiento almacenado en el que utilizamos tablas temporales, es posible que un fallo en la consulta arruine el flujo normal del programa, haciendo que la eliminación de la propia tabla temporal no se realice.

Este caso en concreto puede provocar un error en la próxima ejecución del procedimiento, ya que la creación de la tabla temporal provocará un fallo si esta ya existe.
Para intentar subsanar esta posibilidad, podemos comprobar, antes de crear la tabla temporal, si esta ya existe. Para ello haremos lo siguiente:

IF OBJECT_ID('tempdb..#UsuarioTemp') IS NOT NULL
BEGIN
DROP TABLE #UsuarioTemp;
END

Esto comprobará la existencia de la tabla temporal, y si existe, la eliminará. Posteriormente podremos incluir el código de creación de la tabla.

El el caso de que queramos comprobar la existencia de una tabla real (no temporal) de la base de datos, utilizaremos el siguiente código T-SQL:

IF OBJECT_ID('USUARIO') IS NOT NULL
BEGIN
SELECT 'SELECT * FROM USUARIO';
END
ELSE
BEGIN
CREATE TABLE USUARIO(
[IdUsuario] [int],
[Nombre] [varchar](100),
[Apellido1][varchar](100),
[Apellido2][varchar](100),
[Login][varchar](100),
[Password][varchar](100),
)
END

Con lo cual se realizará una consulta sobre la tabla si ésta existe, o la creará en caso contrario. Queda a voluntad del programador las posibilidades de la existencia o no de las tablas.

Tratamiento de cadenas en SQL Server


En cualquier lenguaje de programación es tarea obligada poseer una manipulación de cadenas fluida.

Transact SQL proporciona una variedad bastante amplia de funciones para llevar a cabo tal tarea. Algunas de las operaciones que podemos realizar son concatenación, obtención de subcadenas, pasar a mayúsculas o minúsculas, eliminar espacios en blanco… incluso detectar la similitud entre expresiones.

A continuación veremos algunas de ellos y aplicaremos algunos ejemplos:

(más…)

Eliminar la hora a una fecha en SQL Server 2005


Hay veces que pese a recibir un parámetro en formato fecha que almacene dia y hora, nos interese únicamente almacenar (o recuperar) la parte correspondiente a la fecha.
Aquellos que hayan realizado alguna vez comparaciones de fechas situadas en un intervalo probablemente sepan de qué hablo: ¿Comparar entre un día a las 00:00:00 y el siguiente a las 00:00:00? ¿Entre el mismo día entre las 00:00:00 y las 23:59:59?

Una forma simple de ahorrarnos dolores de cabeza sería almacenar la fecha sin hora. Para ello haremos uso de los distintos formatos de fecha que nos permite promocionar SQL Server y escribiríamos algo así:

 UPDATE NombreTabla SET CampoFecha = convert(datetime, convert(varchar(10), CampoFecha, 103))  

Con lo cual, almacenaremos nuestra fecha en formato DD:MM:AAAA 00:00:00.

Realizando una transacción en SQL Server


Por norma general, me gusta codificar la lógica de acceso a datos en programa, en lugar de en procedimientos almacenados en base de datos. Esta aproximación no es muy óptima (como se suele decir, al César lo que es del César y a Dios lo que es de Dios), ya que el entorno transaccional estará mejor gestionado por el SGBD que por el código .NET.
Si queremos trasladar la responsabilidad de gestionar la transacción al SGBD, deberemos saber en primer lugar que se declara del siguiente modo:

BEGIN TRAN
 -- Ejecución de sentencias
COMMIT TRAN

Como imaginaremos, la primera sentencia “arranca” la transacción, mientras que la última la compromete. Pero ¿y si ocurre un error en alguna de las sentencias? ¿Cómo lo detectamos?
En el siguiente ejemplo, utilizaremos variables enteras para trazar los fallos y realizar un ROLLBACK en el caso de que la transacción falle.

Se declarará una variable de tipo INT por cada sentencia a ejecutar, y se inician a 0. Se comprueba, después de cada sentencia ejecutada, la variable @@Error, y se almacena en las variables anteriormente creadas. Si es distinta de 0, hay un error, por lo que se añade información al mensaje de error.
Al final de la transacción, si todas las variables son 0, se hace el COMMIT. Si una de ellas es distinta de cero, significará que existe un error y se hace el Rollback, mostrando el mensaje de error.


DECLARE @CadenaError varchar(1000);
 DECLARE @CodigosError varchar(1000);
 DECLARE @ErrorSentencia1 int;
 DECLARE @ErrorSentencia2 int;
 DECLARE @ErrorSentencia3 int;

 SET @ErrorSentencia1 = 0;
 SET @ErrorSentencia2 = 0;
 SET @ErrorSentencia3 = 0;

 SET @CadenaError = 'Error al realizar actualización: ';
 SET @CodigosError = 'Códigos de error: ';

 -- Inicio de la transacción
 BEGIN TRAN

 -- Primera actualización
 UPDATE Usuario SET Nombre = 'Elena' WHERE Nombre = "Helena";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia1 = @@Error;
 IF (@ErrorSentencia1  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 1; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia1 AS VARCHAR(10)) + '; ';
 END

 -- Segunda actualización
 UPDATE Perfil SET Nombre = 'Administrador' WHERE Nombre = "Admin";

 -- Almacenamos el posible código de error
 SET @ErrorSentencia2 = @@Error;
 IF (@ErrorSentencia2  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 2; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia2 AS VARCHAR(10)) + '; ';
 END

 -- Tercera actualización
 UPDATE UsuarioPerfil SET CodigoUsuario = 1 WHERE CodigoPerfil < 3;

 -- Almacenamos el posible código de error
 SET @ErrorSentencia3 = @@Error;
 IF (@ErrorSentencia3  0)
 BEGIN
     SET @CadenaError = @CadenaError + 'Actualización 3; '
     SET @CodigosError = @CodigosError + CAST(@ErrorSentencia3 AS VARCHAR(10)) + '; ';
 END

 -- En ausencia de errores, comprometemos la transacción
 IF (@ErrorSentencia1 = 0) AND (@ErrorSentencia2 = 0) AND (@ErrorSentencia3 = 0)

BEGIN
     COMMIT TRAN;
     PRINT('Transacción realizada con éxito');
 END
 ELSE
 BEGIN
     ROLLBACK TRAN;
     PRINT('Transacción abortada');
     PRINT(@CadenaError);
     PRINT(@CodigosError);
 END

Con todo esto, obtendremos suficiente información (esperemos) como para detectar donde está el fallo.

Concatenar campos nulos en una consulta SQL


Hay ocasiones en las que puede que se nos pidan consultas del siguiente tipo:

SELECT Nombre + ' ' + Apellido1 + ' ' + Apellido2 AS NombreCompleto FROM Usuario

Es decir: concatenar varios campos y devolverlo como uno solo. Ante esto existe un problema bastante común: ¿Qué ocurre cuando uno de estos campos es NULL?
Si uno de estos campos es NULL, por defecto el registro devolverá NULL. Esto es, si tenemos un usuario llamado Santiago, cuyo primer apellido es López y su segundo apellido no consta en la base de datos, el resultado de concatenar Santiago + López + NULL será NULL.

Una posible solución a este problema es utilizar condicionales dentro de la propia sentencia T-SQL, haciendo algo así:

SELECT U.IdUsuario, U.Nombre + ' ' + CASE 
     WHEN U.Apellido1 is NULL THEN '' 
     ELSE U.Apellido1 END + ' ' + CASE 
     WHEN U.Apellido2 is NULL THEN ' ' 
     ELSE U.Apellido2 END AS NombreCompleto
FROM Usuario U

Esto nos devolverá una columna “NombreCompleto” con los campos concatenados correctamente, sin temor a que éstos sean NULL.

Obteniendo el esquema de una Tabla de SQL Server


Hay lugares en los que la generación automática de código no está del todo bien vista. Sin embargo, utilizando las herramientas apropiadas, un generador de código simple puede ahorrar muchas horas de trabajo.
Esta introducción parece no tener demasiado que ver con el título del artículo, pero en realidad está bastante relacionada.

La razón es la siguiente: hemos visto previamente, cuando hablábamos del patrón DAO, que la necesidad de crear entidades DTO para cada tabla de la base de datos era una realidad.

Bien, hasta aquí todo claro. Pero… ¿y si fuese posible crear estos ficheros de forma automática? ¿Cómo? Preguntándole a la base de datos qué tablas y campos posee, y generando a partir de ellos el código necesario para implementar un DTO. Y todo ello sin despeinarnos.
Este artículo trata de hacer un breve inciso en el principio en el que se basa dicha generación de código. Únicamente veremos el mecanismo por el cual preguntar a la base de datos por estos parámetros. Hecho esto, podremos crear un fichero fuente con facilidad.

Para comenzar, declararemos un método que, a partir de una conexión de tipo Sql Server, devuelva un DataSet con una colección de tablas, cuyo nombre sea el propio nombre de la tabla y sus campos sean los datos de dichas tablas que queramos obtener.


      public DataSet ObtenerEsquemaBD(SqlConnection Conexion)
        {

Lo primero que haremos será abrir la conexión, con su correspondiente comprobación de nulos (es decir, si la conexión existe o no).

            try
            {
                if (!(Conexion == null))
                {
                    // Abrimos la conexión
                    if (!(Conexion.State == ConnectionState.Open))
                    {
                        Conexion.Open();
                    }

Seguidamente, crearemos un DataTable para almacenar los nombres de las tablas de la base de datos, así como un DataSet y un DataAdapter para almacenar los datos de las consultas. Utilizaremos una cadena de texto para almacenar las sentencias de las consultas.

                    // Creamos un DataTable para almacenar los nombres de las tablas de la base de datos
                    DataTable dtTablas = new DataTable();

                    // Creamos un DataSet y un DataAdapter para obtener los datos
                    DataSet ds = new DataSet();
                    SqlDataAdapter da = new SqlDataAdapter();

                    // Declaramos una cadena para la consulta
                    string sSQL;

Indicaremos que queremos obtener las tablas de la Base de Datos, y lo almacenamos en el DataTable destinado a ello mediante el DataAdapter

                    sSQL = "SELECT TABLE_NAME as Tabla FROM Information_Schema.Tables";
                    da.SelectCommand = new SqlCommand(sSQL, Conexion);
                    da.Fill(dtTablas);

Ahora recorreremos la tabla que contiene los nombres de cada tabla, realizando una consulta por cada una de ellas. En esta consulta obtendremos datos como el nombre de columna, si admite nulos o no, el tipo de dato…

                    foreach (DataRow dr in dtTablas.Rows)
                    {
                        if(!dr.IsNull("Tabla"))
                        {
                            DataTable dt = new DataTable();
                            sSQL = ("SELECT TABLE_CATALOG as Db, TABLE_NAME as Tabla, ORDINAL_POSITION as Posicion, COLUMN_NAME as Columna" +
                            ", IS_NULLABLE as AdmiteNulos, DATA_TYPE as TipoDatos, CHARACTER_MAXIMUM_LENGTH as MaxChar FROM Infor" +
                            "mation_Schema.Columns WHERE TABLE_NAME = \'"
                                        + (dr["Tabla"].ToString() + "\'"));
                            da.SelectCommand = new SqlCommand(sSQL, Conexion);
                            da.Fill(dt);

                            // Hecha la consulta, añadimos la tabla obtenida por este procedimiento a nuestro DataSet
                            dt.TableName = dr["Tabla"].ToString();
                            ds.Tables.Add(dt);
                        }
                    }

Finalmente, cerramos la conexión y devolvemos el DataSet

                    Conexion.Close();
                    return ds;
                }
                else
                {
                    Conexion.Close();
                    return null;
                }
            }
            catch (Exception ex)
            {
                throw(ex);
            }
        }

El resultado aparecerá en el DataSet, almacenando, por lo tanto, un esquema por cada tabla.

090501Tablas

Rellenando un DataSet con una invocación a procedimiento almacenado


Cuando queremos ejecutar un procedimiento almacenado, por norma general necesitaremos dos parámetros: una cadena con el nombre del procedimiento, y una lista de parámetros que contendrá los filtros por los cuales el procedimiento almacenado limitará su búsqueda. Tomando como ejemplo una base de datos Sql Server, pensemos en el siguiente procedimiento almacenado:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	Daniel García
-- Create date: 11/05/2009
-- Description:	Autentica un usuario
-- =============================================
CREATE PROCEDURE SP_AutenticarUsuario

@NombreUsuario	VARCHAR(20)	= NULL,
@Password		INT	= NULL

AS
BEGIN
SET NOCOUNT ON;

SELECT IdUsuario, Nombre, Apellido1, Apellido2 FROM USUARIO WHERE NombreUsuario = @NombreUsuario AND Password = @Password
END
GO

El código necesario para ejecutar un procedimiento almacenado sería el siguiente:


' Declaramos una conexión, pasándole como parámetro la cadena de conexión a la Base de Datos
Dim conexion As New SqlClient.SqlConnection("Data Source=SRVSQL2005;Initial Catalog=DatosCliente;User Id=DBUser;Password=DBPassword;")

' Declaramos un SqlCommand a partir del nombre del procedimiento y la conexión que hemos creado.
Dim command As New SqlClient.SqlCommand("SP_AutenticarUsuario", conexion)

' Indicamos que vamos a ejecutar un procedimiento almacenado
command.CommandType = CommandType.StoredProcedure

' Rellenamos los parámetros de entrada del procedimiento.
' NOTA: Se trata de un EJEMPLO. No hagáis esto en casa, niños, seguridad ante todo... =)
Dim nombreUsuario As String = "admin"
Dim password As String = "123456"

' Añadimos los parámetros al SqlCommand
command.Parameters.Add(New SqlClient.SqlParameter("@NombreUsuario", nombreUsuario))
command.Parameters.Add(New SqlClient.SqlParameter("@Password", password))

' Declaramos un DataAdapter a partir del SqlCommand y un DataSet para almacenar los datos.
Dim da As New SqlClient.SqlDataAdapter(command)
Dim ds As New DataSet

' Por último, rellenamos el DataSet
da.Fill(ds)

Con esto rellenaríamos el DataSet con una tabla que contendría los campos IdUsuario, Nombre, Apellido1 y Apellido2, tal y como mostraba el procedimiento.
Este proceso es sencillo, pero tiene un problema: aumenta de forma dramática el acoplamiento, al codificar la lógica de acceso a datos de forma monolítica. Si posteriormente necesitáramos añadir o eliminar algún campo de la consulta, deberíamos acceder a este código y modificarlo. Más adelante veremos cómo abstraer un poco la entidad a la cual se referencia y el acceso a datos, utilizando el llamado patrón DAO.