Sunteți pe pagina 1din 109
Tutorial de SQL Server 2005 Express En este tutorial, aprenderá los fundamentos para desarrollar aplicaciones

Tutorial de SQL Server 2005 Express

En este tutorial, aprenderá los fundamentos para desarrollar aplicaciones con la versión Express de SQL Server 2005. El objetivo del tutorial no es cubrir en gran detalle todos los aspectos de SQL Server 2005 Express, pero si aportar una idea general del producto y su integración con el resto de herramientas Express (Visual Basic Express, Visual C# Express, y Visual J# Express

El tutorial cubrirá los siguientes puntos:

Módulo 1: Introducción a SQL Server Express

Requisitos del sistema, instalación del producto, y descripción de sus componentes.

Integración del producto con otras versiones Express; durante el tutorial se trabajará con la versión Express de Visual Basic; sin embargo todos los ejemplos y demostraciones usados son aplicables a Visual C#, y J#.

Proceso de despliegue de aplicaciones desarrolladas con Visual Basic Express. Conceptos XCOPY, instancias de usuario no-administrador, y duplicación de datos.

Proveedores de acceso a datos disponibles. A la hora de conectar a un servidor de base de datos como SQL Server 2005 Express, el nexo de comunicación entre el servidor y la aplicación cliente, es el proveedor de acceso a datos; se hablará e introducirá brevemente los proveedores de acceso a datos disponibles.

Módulo 2: Creación de bases de datos con Visual Studio

Objetos básicos del sistema. Conocerá aspectos de la base de datos como su creación tipos de datos disponibles en SQL Server 2005, creará tablas, restricciones (constraints), relaciones, y teoría relativa a la creación de índices, y su idoneidad.

Conocerá como realizar consultas a las tablas de la base de datos, cláusulas de las sentencias, agrupaciones, joins, etc. También conocerá una nueva característica del producto que son las expresiones de tablas comunes (CTE). Además aprenderá a hacer sentencias de inserción, actualización, y borrado.

Módulo 3: Programación de base de datos

Procedimientos almacenados. Conocerás los fundamentos para crear procedimientos almacenados en la base de datos.

Triggers. Introducción al uso de triggers en SQL Server 2005 Express; veremos los triggers "tradicionales" (llamados DML, los que se activan por modificaciones de datos), y también veremos los nuevos triggers que se activan por cambio en el esquema de base de datos (DDL).

Funciones definidas de usuario. Veremos como crear UDFs, y los tipos de funciones definidas de usuario que existen.

Módulo 4: Conceptos avanzados

Conceptos de seguridad de objetos, esquemas, y credenciales de inicio de sesión. Se introducirá el nuevo paradigma de seguridad basado en esquemas.

Trasaccionalidad y niveles de aislamiento. Se cubrirán los niveles de aislamiento y cómo SQL Server 2005 garantiza la atomicidad, consistencia, integridad, y durabilidad de las transacciones.

Soporte Nativo XML. Conocerá en qué consiste el soporte nativo de XML, uso de las columnas tipo XML, indexación, y consultas XQuery.

Integración del CLR. Conocerá la nueva posibilidad de crear objetos en la base de datos usando cualquier lenguaje .NET como VB.NET, o C#.

Aunque el tutorial no está escrito para un tipo de audiencia determinada, es recomendable que el alumno tenga conceptos de desarrollo de aplicaciones cliente-servidor, y fundamentos de bases de datos que aunque durante el curso se irán cubriendo en mayor o menor profundidad, ayudarán al alumno a una rápida comprensión de las lecciones.

En la introducción del tutorial, se verá cómo instalar el producto y los diferentes proveedores de acceso a datos que se pueden utilizar. En el segundo módulo, se verá cómo aprovechar la integración entre las herramientas de desarrollo de Visual Basic Express con el motor relacional de SQL Server 2005 Express para crear bases de datos, tablas, vistas, y otros objetos. En el tercer módulo, aprenderá conceptos básicos sobre procedimientos almacenados, triggers, y UDFs, y para finalizar el tutorial, aprenderá otros conceptos relacionados con seguridad, transacciones, y niveles de aislamiento. A su vez, conocerá nuevas funcionalidades del producto como soporte nativo XML, y la integración del CLR en el motor relacional de SQL Server 2005.

Recuerde que si quiere poner en práctica este curso tiene disponibles una versión sin limitaciones de Visual Basic 2005 Express, que incluye la base de datos SQL Server 2005 Express.

¡Que disfrutes del curso!

Solid Quality Learning University http://www.sqlu.com

Solid Quality Learning Iberoamericana http://www.SolidQualityLearning.com

Diseñado, y editado por los siguientes mentores de Solid Quality Learning:

http://www.SolidQualityLearning.com/aboutUs.aspx:

Miguel Egea (megea@sqlu.com) MVP SQL Server Director de Servicios Corporativos

Eladio Rincón (erincon@sqlu.com) MVP SQL Server Director de Tecnologías de Bases de Datos

Eugenio Serrano (eserrano@sqlu.com) MVP ASP/ASP.NET

Antonio Soto (asoto@sqlu.com) MCT, Director de Formación

Agradecimientos

Queremos agradecer a Alfonso Rodríguez, David Carmona, y Luís Mazario de Microsoft Ibérica su soporte y colaboración durante las fases de diseño y desarrollo de este curso.

Muchas gracias!!!

1.

Introducción a SQL Server 2005 Express

Durante el módulo, verá cuales los requisitos hardware/software necesarios para instalar SQL Server 2005 Express, y cómo realizar la instalación del producto. Se introducirá al alumno brevemente los componentes del producto, enfocado en las necesidades del desarrollador de software. A su vez se verá cómo se integra SQL Server 2005 Express con las herramientas de desarrollo de las ediciones Express. Para finalizar se presentarán los drivers de acceso a datos que se usarán para conectar a SQL Server 2005 Express.

1.1. Instalación de SQL Server 2005 Express y sus componentes

En esta lección, conocerá los requisitos para instalar SQL Server 2005 Express; además, verá las diferentes opciones disponibles durante la instalación asistida. A continuación se le introducirá los componentes instalados, explicándose su funcionalidad. Para finalizar la lección se hablará de otros componentes disponibles con el producto como código y bases de datos de ejemplo, y documentación del producto (Libros en pantalla).

1.1.1. Requisitos del sistema

La instalación del SQL Server 2005 Express tiene los siguientes requerimientos:

- Requisitos

 

previos de

software

- Microsoft .NET Framework

- SP1 de Microsoft Internet Explorer 6.0 o posterior

 

- Mínimo: 192 MB

- RAM

- Recomendado: 512 MB o más

- Espacio en el disco duro

- 600 MB de espacio libre

 

- Compatible con Pentium III o superior

- Procesador

- Mínimo: 500 MHz

- Recomendado: 1 GHz o más

 

- Windows Server 2003 Standard Edition, Enterprise Edition,

Datacenter Edition

- Sistema

operativo

- Windows XP Professional, Home Edition (SP2 o posterior)

- Windows 2000 Professional, Server, Advanced Server, Datacenter Server (SP4 o posterior)

1.1.2. Proceso de instalación

La instalación de SQL Server 2005 Express se puede realizar de dos formas:

1.1.2.1. Como parte de la instalación de algún producto Express.

Si se instala como parte de la instalación de algún producto Express (por ejemplo Visual Basic Express), el proceso de instalación le pedirá si desea incluir SQL Server 2005 Express en la instalación: en la imagen puede ver la ventana en la que se le solicita la opción (la instalación por defecto no incluye instalar SQL Server 2005 Express).

4

1.1.2.2.

Como instalación independiente.

Si se instala de forma independiente, el proceso de instalación requiere los siguientes pasos:

El primer paso de la instalación consiste en instalar Microsoft .NET Framework 2.0 que es uno de los requisitos de la instalación; si ya está instalado previo al proceso de instalación, este paso será omitido. La actualización desde versiones anteriores de .NET Framework está soportada hasta la versión 1.1, en caso de haber instalado una versión posterior, deberá desinstalarla antes de instalar SQL Server 2005 Express; en otras palabras, la actualización se puede realizar sólo desde versiones soportadas de .NET Framework.

2005 Express; en otras palabras, la actualización se puede realizar sólo desde versiones soportadas de .NET

Ventana de condiciones de EULA; aceptar los términos de la licencia y condiciones y pulsar Siguiente.

Ventana de condiciones de EULA; aceptar los términos de la licencia y condiciones y pulsar Siguiente.

Ventana de bienvenida de instalación de los prerrequisitos necesarios para SQL Server 2005 Express; pulsar Instalar.

Ventana de bienvenida de instalación de los prerrequisitos necesarios para SQL Server 2005 Express; pulsar Instalar.

Ventana de finalización de instalación de los prerrequisitos; pulsar Siguiente.

Ventana de finalización de instalación de los prerrequisitos; pulsar Siguiente. 8

Ventana de bienvenida al asistente de instalación de SQL Server 2005 Express; pulsar Siguiente.

Ventana de bienvenida al asistente de instalación de SQL Server 2005 Express; pulsar Siguiente. 9

Ventana de comprobación de requerimientos del sistema; si la comprobación ha sido satisfactoria, todas las opciones aparecerán con la opción de verificación en color verde, en caso de haber algún requerimiento no severo, aparecerá en color amarillo; si hay algún requerimiento crítico que no se cumple, aparecerá en color rojo y no podrá seguirse con el proceso de instalación. Pulsando en el botón Report, podrá ver cuales son los requisitos no cumplidos para poder preparar al sistema para cumplirlos; por ejemplo, si no se cumplen los requerimientos de ASP.NET, podrá salir de la instalación, actualizar ASP.NET, y volver a realizar la instalación. Si la comprobación ha sido satisfactoria, pulse Siguiente.

actualizar ASP.NET, y volver a realizar la instalación. Si la comprobación ha sido satisfactoria, pulse Siguiente

Introduzca la información de registro, desmarque la opción Ocultar opciones avanzadas de configuración, y pulse Siguiente.

la información de registro, desmarque la opción Ocultar opciones avanzadas de configuración, y pulse Siguiente .

Seleccione los componentes que desea instalar; en éste caso, seleccione instalar todos los componentes y pulse Siguiente.

los componentes que desea instalar; en éste caso, seleccione instalar todos los componentes y pulse Siguiente

Seleccione el nombre de la instancia de SQL Server 2005 Express (por defecto SQLExpress), y pulse Siguiente.

Seleccione el nombre de la instancia de SQL Server 2005 Express (por defecto SQLExpress), y pulse

Seleccione el nombre de la cuenta que arrancará el servidor de SQL Server 2005 Express (Network Service por defecto), y habilite que el servicio de SQL Server se arranque al finalizar la instalación; a continuación pulse Siguiente.

y habilite que el servicio de SQL Server se arranque al finalizar la instalación; a continuación

Seleccione el modo de autenticación (por defecto autenticación integrada de Windows), y pulse Siguiente.

Seleccione el modo de autenticación (por defecto autenticación integrada de Windows), y pulse Siguiente . 15

Seleccione el collation de la instancia de SQL Server. Como es una instalación nueva, y no se va a trabajar con bases de datos de versiones anteriores de SQL Server, se seleccionará Latin1_General, en caso de migraciones o posibilidad de trabajar con bases de datos importadas de SQL Server 2000 (o MSDE 2000), considere la opción por defecto (SQL Collations, Dictionary-order, case insensitive, for use with 1252 Character Set); a continuación pulse Siguiente.

Collations, Dictionary-order, case insensitive, for use with 1252 Character Set ); a continuación pulse Siguiente .

Seleccionar la opción de configuración de la instancia de SQL Server sobre la posibilidad de permitir a usuarios no-administradores de crear instancias. Por defecto habilitado; durante las siguientes lecciones se hablará de ello, habilítelo y pulse Siguiente.

Por defecto habilitado; durante las siguientes lecciones se hablará de ello, habilítelo y pulse Siguiente .

Opciones para informar a Microsoft sobre los errores no esperados sucedidos en la aplicación, y para enviar automáticamente información sobre las características usadas del producto; por defecto están deshabilitadas, pero se recomienda habilitarla para mejorar el producto enviando automáticamente información del uso que se hace del producto; a continuación se pulsa en Siguiente.

enviando automáticamente información del uso que se hace del producto; a continuación se pulsa en Siguiente

Comienza el proceso de instalación informando de las características que se van instalando.

Comienza el proceso de instalación informando de las características que se van instalando. 19

Finalización del proceso de instalación con estado de casa funcionalidad instalada; en caso de haber algún error durante la instalación, aparecerán botones en color Rojo indicando el error; pulsando en la casilla correspondiente de la columna Status, podrá ver información del error producido; pulse Siguiente.

en la casilla correspondiente de la columna Status , podrá ver información del error producido; pulse

ver todas las

características instaladas, y un fichero de resumen con cada paso realizado durante la instalación. Pulse Finalizar, y ya está preparado para poder utilizar SQL Server 2005 Express.

Informe final

de la instalación

de SQL

Server

en

el

que

se podrá

para poder utilizar SQL Server 2005 Express. Informe final de la instalación de SQL Server en

1.1.3.

Componentes instalados

Para acceder a los componentes instalados en SQL Server 2005 Express, deberá hacer click en Inicio, Todos los Programas, Microsoft SQL Server 2005, Herramientas de configuración como ve en la siguiente imagen:

, Todos los Programas , Microsoft SQL Server 2005 , Herramientas de configuración como ve en

Las opciones disponibles son las siguientes:

1.1.3.1. Administrador de configuración

Componente basado en Microsoft Management Console (MMC), con el que se puede administrar la configuración de los servicios SQL Server, protocolos de red utilizados, y configurar el cliente nativo de acceso a SQL Server. Es una herramienta que realiza todas las modificaciones haciendo uso de las nuevas APIs de administración SMO (sustituto de SQL-DMO). El hecho de usar tales APIs nos da la posibilidad de poder crear aplicaciones personalizadas para configurar el servidor; por ejemplo, se podría crear una aplicación basada en SMO que implemente o extienda las funcionalidades expuestas a través de la aplicación MMC. La aplicación tiene la siguiente apariencia:

o extienda las funcionalidades expuestas a través de la aplicación MMC. La aplicación tiene la siguiente

Al igual que desde el administrador de servicios del sistema operativo, se pueden cambiar las propiedades del servicio; la gran diferencia, es que mientras las llamadas de la aplicación MMC de los servicios realiza llamadas a las APIs del Kernel de Windows, el Administrador de Configuración, realiza las llamadas a través de las APIs de SMO.

APIs del Kernel de Windows, el Administrador de Configuración , realiza las llamadas a través de

Se pueden configurar, habilitar o deshabilitar protocolos; configurar, o modificar puertos TCP/IP como se ve en la imagen:

pueden configurar, habilitar o deshabilitar protocolos; configurar, o modificar puertos TCP/IP como se ve en la

Así como establecer el orden de los protocolos de Red; en el siguiente ejemplo, están habilitados los protocolos Shared Memory, TCP/IP, y Named Pipes, mientras que el protocolo VIA está deshabilitado:

Pipes , mientras que el protocolo VIA está deshabilitado: Nota : Recuerde que la mayoría de

Nota: Recuerde que la mayoría de estos cambios no tendrán efecto hasta que el servicio de SQL Server haya sido reiniciado.

1.1.3.2.

Informes de uso y errores de SQL Server

La utilidad permite modificar las opciones de configuración relativas al feedback que se envía a Microsoft en cuanto al uso de las características del producto, e informe de errores no esperados. El nivel de detalle llega hasta el nivel de instancia, es decir, podemos decidir qué instancias envían información sobre los errores no esperados, o sobre el uso de las funcionalidades del producto:

instancias envían información sobre los errores no esperados, o sobre el uso de las funcionalidades del

1.1.3.3.

Configuración de superficie de SQL Server

El aplicativo configuración de superficie de SQL Server es un asistente que ayuda a configurar cuales son las partes de SQL Server que se encuentran expuestas a interactuación desde el exterior. La filosofía del aplicativo es ayudar a configurar de manera sencilla y rápida los puntos de acceso al servidor. El aplicativo expone al usuario funcionalidades tales como configuración de servicios, y configuración de características de SQL Server; por ejemplo, CLR habilitado o no, soporte HTTP habilitado o no, endpoints configurados en el servidor, etc.

SQL Server; por ejemplo, CLR habilitado o no, soporte HTTP habilitado o no, endpoints configurados en

El aplicativo permite modificar la configuración del servicio de SQL Server.

El aplicativo permite modificar la configuración del servicio de SQL Server. 29

También permite configurar el tipo de conexiones permitida sobre el servidor. Por ejemplo, SQL Server 2005 Express permite por defecto sólo conexiones locales; esto quiere decir que no se pueden realizar conexiones desde equipos externos a menos que se configure de servidor para permitirlo. Desde la aplicación, se podrá habilitar la posibilidad de conexiones de equipos remotos, y los protocolos de conexión permitidos.

equipos remotos, y los protocolos de conexión permitidos. En cuanto a las opciones de configuración de

En cuanto a las opciones de configuración de funcionalidades del motor de base de datos, se incluyen:

Posibilidad de habilitar o deshabilitar la ejecución de consultas con OPENROWSET y OPENDATASOURCE: lo que estas funciones permiten es realizar consultas a servidores remotos (servidores expuestos a través de un origen de datos del que se provea drivers de acceso como ODBC, OLEDB, etc.) sin la necesidad de tener que crear un servidor vinculado.

Habilitar el soporte del CLR: creación de objetos de base de datos con cualquier lenguaje .NET Framework.

Habilitar el soporte nativo de Servicios Web: opción sólo disponible en la versión Enterprise de SQL Server 2005 que permite exponer SQL Server 2005 sin necesidad de implementar Servicios Web expuestos por IIS.

Habilitar el uso de Automatización OLE con sentencias T-SQL: posibilidad de realizar llamadas a objetos COM desde Transact-SQL con los procedimientos almacenados de

sistema sp_OACreate, sp_OAGetProperty, sp_OASetProperty, sp_OAMethod, sp_OAStop, y sp_OADestroy. Automatización OLE entrará en desuso debido a la integración del CLR y su consiguiente facilidad para implementarlo mediante .NET Framework en lugar de objetos COM.

Habilitar el uso del procedimiento almacenado de sistema xp_cmdshell que permite ejecutar comandos de sistema en el servidor (tales como DIR, DELETE, COPY, etc.)

Habilitar el uso de endpoints de Service Broker.

etc.) ∑ Habilitar el uso de endpoints de Service Broker. 1.1.3.4. SQLCMD SQL Server 2005 Express

1.1.3.4. SQLCMD

SQL Server 2005 Express no incluye la herramienta SQL Server Management Studio como

el resto de versiones de SQL Server 2005. Para poder conectarse a SQL Server, el producto

incluye una utilidad de línea de comando que permite conectarse a una instancia de SQL Server y realizar operaciones de manera similar a las herramientas gráficas.

Evidentemente, la funcionalidad que nos proveen Management Studio no puede ser alcanzada con la utilidad de línea de comando, pero si provee lo necesario para conectarse

e interactuar con la instancia. Típicamente esta utilidad suele ser utilizada para realizar

tareas administrativas básicas. Por ejemplo, un ISV que desarrolla una aplicación y necesita ejecutar un script contra la instancia de SQL Server; imagínese que el ISV no ha desarrollado sus propias herramientas para ejecutar consultas ad-hoc contra el servidor; en este caso, puede utilizar SQLCMD para conectarse a la instancia de SQL Server y ejecutar el script deseado. Incluso, el ISV tiene la posibilidad de crear el script con SQL Server

Management Studio en sus equipos de desarrollo porque Management Studio incorpora la posibilidad de ejecutar scripts en modo SQLCMD.

La documentación del SQLCMD la puede encontrar en los Libros en Pantalla en "SQL Server 2005 Express Edition", "Working with SQL Server Express", "Using the sqlcmd Utility (SQL Server Express)".

La funcionalidad más novedosa implementada en SQLCMD es la posibilidad de utilizar variables a la hora de ejecutar scripts; por ejemplo:

Dado el siguiente script llamado copia.sql:

BACKUP DATABASE $(db) TO DISK = "$(path)\$(db).bak" WITH INIT

donde $(db) es una variable que referenciará a un nombre de base de datos, y $(path) la ruta donde se guardará la copia.

Si se ejecuta desde línea de comando

SQLCMD -ic:\copia.sql -vdb="AdventureWorks" path="c:\data"

lo que SQLCMD ejecutará será:

BACKUP DATABASE AdventureWorks TO DISK = "c:\data\AdventureWorks.bak" WITH INIT

lo cual quería decir que con un script de copia de seguridad, se podrían reutilizar con bastante facilidad distintas estrategias de copia de seguridad. Esto está muy bien, pero se limita la capacidad a la instancia en uso. Sin embargo, SQLCMD también permite cambiar de conexión tras haber conectado a un servidor con la palabra clave :connect; por ejemplo:

desde línea de comando, se conecta a una instancia de la siguiente manera:

SQLCMD -E -S(local)\SQLEXPRESS

A continuación aparece el prompt, y puede ejecutar lo siguiente:

:connect (local)\SQLEXPRESS_2

que conectaría a una segunda instancia de SQL Server 2005. Otras opciones disponibles son :r para ejecutar un script, y :setvar para definir variables, por lo que el ejemplo anterior del backup se podría ejecutar de la siguiente forma:

SQLCMD -E -S(local)\SQLEXPRESS :setvar db AdventureWorks :setvar path c:\data :r c:\copia.sql GO

1.1.4. Componentes opcionales

Las bases de datos de ejemplo y el código de ejemplo son un buen comienzo para conocer las nuevas funcionalidades del producto. La base de datos AdventureWorks se ha creado para sustituir a las anteriores Northwind, y Pubs que pecaban de ser bases de datos poco reales en cuanto a volumen de datos. Se ha tenido más cuidado en el diseño relacional de la base de datos AdventureWorks, y se ha aprovechado para incluir nuevas funcionalidades como soporte XML, novedades Transact-SQL, nuevos tipos de datos, etc.

La otra fuente de conocimiento a mencionar son los Libros en Pantalla (BOL): la versión incluida en SQL Server 2005 Express es una versión "reducida" de la versión completa, pero en todo momento, da acceso a la Web de Microsoft para acceder a documentación y ayuda incluida en la versión completa de los Libros en Pantalla. A su vez, los Libros en Pantalla, incluyen la posibilidad de acceder directamente a los grupos de noticias para obtener

respuesta a casos concretos, y también realizar búsquedas en sitios de la comunidad como CodeZone, ElGuille.info, GotDotNet.com, SSUG.com, SQLIS.com, etc.

1.2. Integración con versiones Express

En esta lección, conocerá cómo se integran la versión Express de SQL Server con Visual Studio, y cómo ayuda la versión Express de SQL Server en el proceso de despliegue de aplicaciones de bases de datos. Se le introducirá a nuevos conceptos como instancias de nombre para no- administradores, y se le enseñará a desplegar aplicaciones de bases de datos con la versión Express de SQL Server 2005.

Uno de los objetivos de la versión Express de SQL Server 2005, es simplificar el proceso de despliegue de aplicaciones de bases de datos; a diferencia del resto de versiones de SQL Server (Workgroup, Standard, Enterprise) en el que las bases de datos se cambian de lugar poco o casi nunca, la versión Express está pensada para aplicaciones de bases de datos que requieren en cierto modo que los ficheros de base de datos sean dinámicos. Esta movilidad de los ficheros se consigue durante la fase de despliegue.

¿En qué ayuda al despliegue la versión Express? Los Vendedores de Software Independientes (ISV’s) generalmente desarrollan aplicaciones comerciales que requieren de un instalable, y a su vez requieren instalar una base de datos; La versión Express viene a solucionar los problemas que surgían en el proceso de instalación en este tipo de aplicaciones.

1.2.1. Compatibilidad con versiones "mayores" de SQL Server

La versión Express de SQL Server se pone en la línea de las versiones MSDE de anteriores versiones de SQL Server; esto quiere decir que una base de datos de SQL Server 2005 para la versión Express, es totalmente compatible con el resto de versiones de SQL Server (Workgroup, Standard, Enterprise). El proceso de migración entre versiones es tan sencillo como hacer una restauración de la copia de seguridad de la base de datos (comando RESTORE, o usando las herramientas gráficas), o usando la opción de adjuntar bases de datos (comando sp_attach_db, o con las herramientas gráficas).

1.2.2. Instancias de nombre para usuarios no-administradores

Uno de los problemas que tienen los ISVs que distribuyen aplicaciones con MSDE es que el usuario que realiza la instalación de la aplicación debe pertenecer al rol de administradores de SQL Server. Este es un requisito que en la versión Express de SQL Server 2005 se elimina con la aparición de las instancias de usuario.

El objetivo de las instancias de usuario, es acercar a SQL Server al concepto de base de datos de escritorio; es una base de datos que sólo admite conexiones locales a través del protocolo de red "named pipes", no se pueden realizar conexiones a través de la red. El concepto de las instancias de usuario es similar al modelo de las bases de datos Access: una vez conectado al fichero de base de datos, el usuario tiene derechos administrativos sobre la base de datos, sin necesidad de la intervención de un usuario administrador.

1.2.2.1. ¿Cómo funciona?

En primer lugar, para habilitar el uso de instancias de usuario, en la cadena de conexión de la aplicación cliente deberás añadir la cadena "User Instance=true". De esta forma se indica a SQL Server Express, que la conexión se realizará sobre una instancia de usuario.

En caso de que no existiera la instancia de usuario, SQL Server automáticamente, creará la instancia de usuario; consiste en crear una nueva instancia de SQL Server para el usuario requerido, en el que:

Se copiarán las bases de datos de sistema de la instancia por defecto de SQL Server 2005 Express, y

Se iniciará otra "copia" del servicio de SQL Server Express; el nombre de la instancia es un valor aleatorio, por ejemplo (2E67C75A-1693-4D), y se debe considerar a la nueva instancia hija de la instancia principal de SQL Server Express.

Nota: Conectándose a la instancia \SQLEXPRESS, con la vista sys.dm_os_child_instances se pueden consultar cuales son las instancias de usuario activas.

La diferencia fundamental con las instancias tradicionales es que a la instancia de usuario solamente puede conectar el usuario para el que se ha creado la instancia. Además, por diseño, sólo se puede conectar mediante canalización por nombres, y no se puede conectar a la instancia de forma remota.

Después de crear o iniciar la instancia de SQL Server Express, automáticamente, el proceso de conexión se encargará de adjuntar la base de datos que se ha especificado en la cadena de conexión a la instancia recién creada.

A su vez, cuando la aplicación cliente cierra conexión con la base de datos, la base de datos

se "desadjunta" de la instancia de SQL Server. En realidad, SQL Server tiene configurado un tiempo de espera antes de desadjuntar la base de datos de la instancia porque en caso

contrario, el proceso de adjuntar/ desadjuntar podría causar problemas de rendimiento en

la aplicación.

Un ejemplo de cadena de conexión que deberá usarse para trabajar con instancias de usuario podría ser el siguiente:

conn string = "Data Source=.\\SQLExpress;" + "Integrated Security=true;" + "attachdbfilename=|DataDirectory|\\mi_bd.mdf;" + "User Instance=true;"

En .NET 2.0, aparece la palabra clave |DataDirectory|, que representa un path relativo a la instalación de la aplicación; por ejemplo, distribuimos una aplicación que admite personalizar la ruta de instalación, con |DataDirectory| podemos especificar rutas relativas.

A su vez, también se puede especificar el valor de DataDirectory de la siguiente manera:

AppDomain.CurrentDomain.setData(“DataDirectory”,”C:\ruta_por_defecto\”);

1.2.3.

Consideraciones

Como la instancia de usuario es hija de la instancia SQLEXPRESS, si la instancia SQLEXPRESS no está arrancada, la instancia de usuario no arrancará.

Un usuario sólo puede tener una instancia de usuario.

Las bases de datos de la instancia de usuario se crean en la ruta: \Documents and Settings\nombre_usuario\Local Settings\Application Data\Microsoft\Microsoft SQL Server Data\SQLEXPRESS

La réplica se deshabilita.

La instancia de usuario no admite Autenticación de SQL Server. Sólo se admite la Autenticación de Windows.

La compatibilidad de protocolo de red con las instancias de usuario sólo es posible mediante canalizaciones con nombre locales.

La instancia de usuario comparte las entradas de registro de la instancia primaria.

No se admiten instancias de usuario con código nativo. Esta característica sólo se admite con ADO .NET.

1.2.4.

Despliegue de la aplicación

El objetivo principal de las instancias de usuario, es disponer de un fichero de base de datos, que se utiliza en una instancia cuando el usuario necesita acceder a la base de datos. Cuando no se está usando, la base de datos se desadjunta. ¿Qué se consigue con esto? Acercar la base de datos al concepto de base de datos de escritorio (como si fuera Access). De esta forma, el despliegue de la aplicación relacionada con la base de datos consistirá en:

Copiar los binarios de la aplicación.

Copiar el fichero de base de datos.

Nota. Fíjate que sólo hay que copiar el fichero de base de datos; no se necesita adjuntar la base de datos a ninguna instancia de SQL Server, porque la propia aplicación se encarga de hacerlo por nosotros.

Por lo tanto, para desplegar la aplicación deberemos incluir en la distribución la copia de la base de datos de la aplicación. Para ello lo podremos hacer de dos formas:

Usar el concepto de XCOPY copiando el contenido del directorio \bin\release de la aplicación generada en el directorio destino.

Usar la nueva tecnología ClickOnce, cuyo objetivo es facilitar el despliegue de la aplicación, y a su vez, gestionar las actualizaciones; en el proceso de instalación, además de la realizar la propia instalación, se podrá configurar la aplicación para actualizar automáticamente la aplicación cuando existan nuevas versiones.

1.3. Acceso a datos

Sin lugar a dudas uno de los ámbitos más importantes de un lenguaje o entorno de programación es su capacidad de acceso a datos. Prácticamente todas las aplicaciones conllevan la realización de accesos a datos.

Le gustará saber que la plataforma .NET, y por lo tanto ASP.NET, ofrecen un potente modelo de acceso a fuentes de datos. Se le conoce con el nombre genérico de ADO.NET.

Nota: No se deje engañar por el nombre: ADO.NET no tiene casi nada que ver con el anterior ADO utilizado en los tiempos de ActiveX y COM. Sí, dispone de conexiones, comandos e incluso una clase que recuerda a los Recordset, pero créame cuando le digo que es mejor que se olvide para siempre de todos ellos. Tanto la filosofía de trabajo como la tecnología son diferentes por completo y es mejor que utilice una estrategia de "ojos limpios" para acercarse correctamente a la nueva tecnología.

Los conocimientos adquiridos en este módulo le servirán para cualquier tipo de desarrollo con .NET, no sólo para aplicaciones Web. Los conceptos explicados son válidos también para cualquier versión de .NET no sólo para la 2.0.

1.3.1. Introducción a ADO.NET

Como cualquier otro modelo de acceso a datos, ADO.NET es un conjunto de clases relacionadas entre sí que están especializadas en ofrecer toda la funcionalidad que un programador necesita para realizar acceso a datos y manejarlos una vez los ha obtenido.

Las clases genéricas expuestas por ADO.NET se encuentran bajo el espacio de nombres System.Data. Este espacio de nombres define clases genéricas de acceso a datos que posteriormente son extendidas para ofrecer características y funciones específicas de cada proveedor.

El objeto más importante a la hora de trabajar con el nuevo modelo de acceso a datos es el DataSet. Sin exagerar demasiado podríamos calificarlo casi como un motor de datos

relacionales en memoria. Aunque hay quien lo asimila a los clásicos Recordsets su funcionalidad va mucho más allá como se verá en breve.

1.3.1.1. Arquitectura de ADO.NET

El concepto más importante que hay que tener claro sobre ADO.NET es su modo de funcionar, que se revela claramente al analizar su arquitectura:

que se revela claramente al analizar su arquitectura: Figura 4.1.- Arquitectura de ADO.NET Existen dos capas

Figura 4.1.- Arquitectura de ADO.NET

Existen dos capas fundamentales dentro de su arquitectura: la capa conectada y la desconectada

1.3.1.2. La capa conectada

La capa conectada de ADO.NET contiene objetos especializados en la conexión con los orígenes de datos. Así, la clase genérica Connection se utiliza para establecer conexiones a los orígenes de datos. La clase Command se encarga de enviar comandos de toda índole al origen de datos. Por fin la clase DataReader está especializada en leer los resultados de los comandos.

La clase DataAdapter hace uso de las tres anteriores para actuar de puente entre la capa conectada y la desconectada como veremos después.

Estas clases son abstractas, es decir, no tienen una implementación real de la que se pueda hacer uso directamente. Es en este punto en donde entran en juego los proveedores de datos. Cada origen de datos tiene un modo especial de comunicarse con los programas que los utilizan, además de otras particularidades que se deben contemplar. Un proveedor de datos de ADO.NET es una implementación concreta de las clases conectadas abstractas que hemos visto, que hereda de éstas y que tiene en cuenta ya todas las particularidades del origen de datos en cuestión.

Así, por ejemplo, las clases específicas para acceder a SQL Server se llaman SqlConnection, SqlCommand, SqlDataReader y SqlDataAdapter y se encuentran bajo el espacio de nombres System.Data.SqlClient. Es decir, al contrario que en ADO clásico no hay una única clase Connection o Command que se use en cada caso, si no que existen

clases especializadas para conectarse y recuperar información de cada tipo de origen de datos.

Nota: El hecho de utilizar clases concretas para acceso a las fuentes de datos no significa que no sea posible escribir código independiente del origen de datos. Todo lo contrario. La plataforma .NET ofrece facilidades de escritura de código genérico basadas en el uso de herencia e implementación de interfaces. De hecho la versión 2.0 de .NET ofrece grandes novedades específicamente en este ámbito.

Existen proveedores nativos, que son los que se comunican directamente con el origen de datos (por ejemplo el de SQL Server o el de Oracle), y proveedores "puente", que se utilizan para acceder a través de ODBC u OLEDB cuando no existe un proveedor nativo para un determinado origen de datos.

Nota: Estos proveedores puente, si bien muy útiles en determinadas circunstancias, ofrecen un rendimiento menor debido a la capa intermedia que están utilizando (ODBC u OLEDB). Un programador novel puede sentir la tentación de utilizar siempre el proveedor puente para OLEDB y así escribir código compatible con diversos gestores de datos de forma muy sencilla. Se trata de un error y siempre que sea posible es mejor utilizar un proveedor nativo.

La plataforma .NET proporciona "de serie" los siguientes proveedores de acceso a datos.

Proveedor

Espacio de nombres

Descripción

ODBC .NET Data Provider

System.Data.Odbc

Permite conectar nuestras aplicaciones a fuentes de datos a través de ODBC.

OLE DB .NET Data Provider

System.Data.OleDb

Realiza la conexión utilizando un proveedor OLEDB, al igual que el ADO clásico.

Oracle Client .NET Data Provider

System.Data.OracleClient

Proveedor de datos para acceder a Oracle.

SQL Server .NET Data Provider

System.Data.SqlClient

Permite la conexión optimizada a SQL Server 7.0 o posterior, incluyenbdo la última versión SQL Server 2005.

Los proveedores de acceso a datos que distribuye Microsoft en ADO.NET y algunos desarrollados por otras empresas o terceros, contienen los mismos objetos, aunque los nombres de éstos, sus propiedades y métodos, pueden ser diferentes.

Existen, por supuesto, proveedores para tipos de orígenes de datos de cualquier gestor de datos existente en el mercado. Éstos los desarrolla normalmente la empresa responsable del producto. Si bien éstos optimizan el acceso a estos orígenes de datos nosotros siempre podremos realizarlo mediante ODBC u OLEDB si tenemos necesidad.

En resumen: con la capa conectada de ADO.NET realizamos la conexión y comunicación con los orígenes de datos. Cada proveedor de datos implementa su propia versión de las clases Connection, Command, DataReader y DataAdapter (entre otras).

Las clases derivadas de Connection se utilizan para realizar la conexión y enviar y recibir información.

Las clases derivadas de Command permiten ejecutar sentencias SQL y procedimientos almacenados en el gestor de datos.

Las clases derivadas de DataReader se emplean para obtener los posibles resultados de un comando utilizando para ello el conducto de comunicación establecido por Connection.

1.3.1.3. La capa desconectada

Una vez que ya se han recuperado los datos desde un origen de datos la conexión a éste ya no es necesaria. Sin embargo sigue siendo necesario trabajar con los datos obtenidos de una manera flexible. Es aquí cuando la capa de datos desconectada entra en juego. Además, en muchas ocasiones es necesario tratar con datos que no han sido obtenidos desde un origen de datos relacional con el que se requiera una conexión. A veces únicamente necesitamos un almacén de datos temporal pero que ofrezca características avanzadas de gestión y acceso a la información.

Por otra parte las conexiones con las bases de datos son uno de los recursos más escasos con los que contamos al desarrollar. Su mala utilización es la causa más frecuente de cuellos de botella en las aplicaciones y de que éstas no escalen como es debido. Esta afirmación es especialmente importante en las aplicaciones Web en las que se pueden recibir muchas solicitudes simultáneas de cualquier parte del mundo.

Finalmente otro motivo por el que es importante el uso de los datos desconectado de su origen es la transferencia de información entre capas de una aplicación. Éstas pueden encontrarse distribuidas por diferentes equipos, e incluso en diferentes lugares del mundo gracias a Internet. Por ello es necesario disponer de algún modo genérico y eficiente de poder transportar los datos entre diferentes lugares, utilizarlos en cualquiera de ellos y posteriormente tener la capacidad de conciliar los cambios realizados sobre ellos con el origen de datos del que proceden. Todo esto y mucho más es lo que nos otorga el uso de los objetos DataSet, núcleo central de la capa desconectada de ADO.NET.

Nota: Otra interesante característica de los DataSet es que permiten gestionar simultáneamente diversas tablas (relaciones) de datos, cada una de un origen diferente si es necesario, teniendo en cuenta las restricciones y las relaciones existentes entre ellas.

Los DataSet, como cualquier otra clase no sellada de .NET, se pueden extender mediante herencia. Ello facilita una técnica avanzada que consiste en crear tipos nuevos de DataSet especializados en la gestión de una información concreta (por ejemplo un conjunto de tablas relacionadas). Estas nuevas tipos clases se denominan genéricamente DataSet Tipados, y permiten el acceso mucho más cómodo a los datos que representan, verificando reglas de negocio, y validaciones de tipos de datos más estrictas.

Los objetos DataSet no dependen de proveedor de datos alguno y su funcionamiento es independiente de cómo hayan sido obtenidos los datos que contienen. Este es el concepto más importante que debemos recordar.

El proceso general de trabajo de ADO.NET para acceder a un gestor de datos (SQL Server, por ejemplo) es casi siempre el mismo: se abre una conexión (clase Connection), se lanza una consulta SQL o procedimiento almacenado mediante un objeto de la clase Command, y se almacenan en memoria los resultados dentro de un objeto DataSet, cerrando la conexión.

Nota: Aunque este es el comportamiento habitual de una aplicación de datos existen casos en los que no es recomendable almacenar en memoria (en un DataSet) todos los resultados de una consulta, bien por ser muchos registros o por contener datos muy grandes. En este tipo de casos se puede usar u objeto DataReader directamente para leer la información, tratarla y no almacenarla en lugar alguno. De todos modos lo más frecuente es realizar el proceso descrito.

1.3.1.3.1.

Unión entre capa conectada y desconectada

La clase DataAdapter se ha incluido anteriormente en la capa conectada por que está implementada por cada proveedor de un modo diferente. En realidad es una clase que pone sus pies en ambos mundos (conectado y sin conexión) y sirve de nexo entre ellos.

Un DataAdapter sabe como manejar correctamente los objetos proporcionados por su proveedor específico (fundamentalmente los vistos: Connection, Command y DataReader) y proporciona métodos para trasegar información desde el servidor a DataSets desconectados y viceversa haciendo uso de dichos objetos específicos del proveedor.

Así, por ejemplo, el método Fill de un DataSet se utiliza para introducir los resultados de una consulta dentro de un DataSet para luego trabajar con ellos sin preocuparnos de su origen. Del mismo modo su método Update se utiliza para conciliar automáticamente con el origen de datos los datos modificados en un DataSet mientras no había conexión.

1.3.1.3.2. Otras clases dependientes de DataSet

Como hemos dicho antes un DataSet podría asimilarse a un pequeño gestor de datos en memoria. Como tal un DataSet permite mantener diversas tablas así como las relaciones entre ellas, incluso forzando que se cumplan restricciones de creación y actualización, como en una base de datos "real". Para ello se apoya en el uso de otras clases especializadas que son las siguientes:

DataTable: representa una tabla o relación de datos. Se puede asimilar a una tabla de un gestor de datos. Los datos obtenidos de una consulta de tipo SELECT de SQL se almacenan en un objeto de esta clase.

DataColumn: ofrece información sobre cada uno de los campos de los registros almacenados en un DataTable, como su nombre o su tipo.

DataRow: representa un registro de la tabla virtual definida por el DataTable. Contiene tantos elementos como campos tiene la tabla, cada uno del tipo definido por el objeto DataColumn correspondiente.

Constraint: las clases Constraint se emplean para definir restricciones en los tipos de datos contenidos en un DataTable. Por ejemplo se puede usar un objeto de esta clase para indicar que un determinado campo debe ser único o que se trata de una clave externa que debe ser tenida en cuenta en actualizaciones o borrados en cascada.

DataRelations: define la relación existente entre dos objetos DataTable. Normalmente se suelen utilizar un identificador común a ambas tablas aunque pueden ser combinaciones de más de uno de ellos. De este modo se sabe cómo obtener información de una tabla a partir de información en otra. Por ejemplo el identificador de una factura (su número, por ejemplo) puede servir para relacionar su registro con los registros de detalle de factura que están contenidos en otra tabla.

DataView: representa una vista concreta de un DataTable. Normalmente se trata de ordenaciones o filtros sobre los datos originales. Todas las tablas disponen de una vista por defecto (propiedad DefaultView) que ofrece los datos tal y como se han introducido en ésta y es la vista que se usa habitualmente.

1.3.1.4. Vinculación de datos a controles Web

Otra característica fundamental de ASP.NET que lo convierte en una herramienta ventajosa para el desarrollo de aplicaciones Web es la capacidad de vincular datos a controles Web.

La mayor parte de los controles que podemos arrastrar sobre una página ASPX se pueden vincular a los objetos DataSet, DataTable y DataReader o a informaciones concretas contenidas en éstos.

Ello facilita mucho el trabajo con datos desde la interfaz de usuario ya que no hay que molestarse en generar tablas con ellos, escribir JavaScript o proporcionar complejos medios propios para permitir su edición o navegación si hacemos un uso adecuado de la vinculación y los controles disponibles.

Todo ello, gracias a ASP.NET y Visual Studio, equipara en muchos aspectos el desarrollo Web al clásico desarrollo de aplicaciones de escritorio donde este tipo de facilidades han estado disponibles desde hace años. Sin embargo en una aplicación Web donde no existe una conexión permanente disponible entre la visualización (navegador) y el lugar en el que se ejecuta el código no es algo fácil de conseguir. El uso de un modelo consistente como ADO.NET (idéntico en Web, escritorio y otros entornos) junto con las capacidades nativas

consiguen el

de ASP.NET para abstraernos de estas dificultades (ViewState, Postback "milagro".

En este módulo veremos también lo sencillo que resulta crear interfaces para explotar los datos desde una página Web.

)

1.3.2. Acceso a datos manual

Tras haber aprendido un poco de teoría sobre ADO.NET a continuación explicaremos cómo se utilizan las clases de acceso datos para escribir código de acceso a datos de manera manual.

Si bien es un tema algo árido y además en un gran porcentaje de los casos utilizaremos herramientas que nos faciliten la creación automática de código, es fundamental conocer la forma de trabajar sin ayuda para entender el funcionamiento real de los objetos de datos.

1.3.2.1. Escritura manual de código

En este apartado vamos a analizar cómo es el código necesario para recuperar y actualizar datos con ADO.NET. Posteriormente veremos como sacar partido a las facilidades del entorno de desarrollo Visual Studio 2005 para no tener que escribir el código a mano. Sin embargo es útil aprender a hacerlo de esta manera para entender bien su funcionamiento.

1.3.2.1.1. Comandos de selección simples

La mayor parte de las consultas que se lanzan contra una base de datos suelen utilizarse para obtener un conjunto de registros para tratar. Este tipo de consultas suelen ser expresiones SQL de tipo SELECT. El siguiente fragmento de código muestra los pasos necesarios para mostrar en una página los registros resultantes de una consulta:

No se trata de un código optimizado (es más bien burdo) pero nos ayudará a

No se trata de un código optimizado (es más bien burdo) pero nos ayudará a entender perfectamente el proceso. Los datos los obtendremos de la conocida base de datos de ejemplo Northwind (que viene con todas las versiones de SQL Server).

Nota: Para poder escribir código de acceso a datos en nuestro módulo debemos agregar referencias a los espacios de nombres que contienen las clases que vamos a utilizar. Para ello usamos las dos sentencias Imports siguientes:

Para ello usamos las dos sentencias Imports siguientes : La primera de ellas agrega las clases

La primera de ellas agrega las clases genéricas de acceso a datos (como DataSet) y la siguiente las específicas de SQL Server. Si no lo hacemos recibiremos un error.

Lo primero que se debe hacer es instanciar un objeto que represente la conexión a la base de datos. Dado que nos estamos conectando a SQL Server esta conexión será del tipo SqlConnection. Es lo que se hace en la primera línea del código anterior. La conexión debe realizarse con un servidor de datos y un esquema de datos concreto. Esto se indica mediante la cadena de conexión (al igual que se hacía en ADO tradicional). En este caso la cadena de conexión es la típica de SQL Server. Cada gestor de datos tiene la suya y hay que construirla de manera adecuada. El entorno de desarrollo Visual Studio 2005 nos ayuda a crearlas como veremos luego.

Una vez creado el objeto con el que nos conectaremos hay que definir el comando a lanzar a la base de datos. Para ello se utiliza un objeto SqlCommand. Las propiedades básicas que hay que establecer para éste son la consulta que se lanzará (propiedad CommandText) y la conexión que se empleará para lanzarla (propiedad Connection) que es lo que se refleja en las líneas 6 y 7.

Ahora que ya sabemos cómo nos conectaremos y qué queremos obtener debemos lanzar la consulta y recoger el resultado de alguna manera.

La clase Command dispone de diversos métodos para ejecutar consultas:

ExecuteReader: este método lanza la consulta a la base de datos y devuelve una referencia a una instancia de la clase DataReader (SqlDataReader en el caso de SQL Server). Podemos utilizar el DataReader para recorrer los datos y procesarlos.

ExecuteNonQuery: ejecuta la consulta sin devolver resultado alguno. Simplemente envía la consulta al servidor y devuelve el número de registros afectados por ésta. Útil para realizar consultas de inserción (INSERT), actualización (UPDATE) y borrado (DELETE).

ExecuteScalar: lanza la consulta y devuelve un objeto con el valor del primer

campo del primer registro que se obtenga de dicha consulta. Es útil para lanzar

) y

consultas de agregación como sumas (SUM), cuentas (SELECT COUNT * similares de las que sólo necesitamos un valor como resultado.

En el ejemplo hemos utilizado el primer método ya que requerimos que devuelva varios registros con diferentes campos. Entonces lo que hacemos es (líneas a partir de la 9):

1. Abrir la conexión.

2. Crear una variable para contener una referencia a un objeto de la clase DataReader que es el que nos permitirá acceder a los resultados. Fíjese en que no se puede instanciar un DataReader (la declaración no lleva la palabra clave New).

3. Se obtiene el resultado mediante el método ExecuteReader, recogiéndolo en la variable (dr) recién declarada.

4. Se procesan los resultados (líneas 14-18).

5. Se cierra la conexión.

El objeto DataReader es asimilable a un cursor de servidor de sólo lectura y hacia adelante (conocidos como de manguera de bombero o firehose). Es decir, los datos devueltos por el DataReader sólo se pueden leer y no actualizar. Además de esto sólo se pueden leer en secuencia hacia adelante (no hay forma de regresar sobre lo andado).

La propiedad HasRows nos indica si hay o no resultados devueltos. El método Read avanza una posición en los registros devolviendo True si quedan registros pendientes de leer. Con esta información es muy fácil entender las líneas 14 a 18.

1.3.2.1.2. La cláusula Using

¿Qué ocurre si se produce un error durante el procesamiento del bucle anterior en el que se trata el DataReader? La respuesta es que la conexión, que debemos tener abierta durante el procesamiento, no se cerrará pues el código no llega al final.

Esto es algo muy grave ya que las conexiones que no se cierran no se pueden reutilizar y por lo tanto puede llegar un momento en que no tengamos conexiones disponibles, lo que limita enormemente la escalabilidad del sistema.

Podemos evitar el problema escribiendo:

De este modo, con la cláusula Finally nos aseguramos que siempre se va a cerrar

De este modo, con la cláusula Finally nos aseguramos que siempre se va a cerrar la conexión.

De todos modos escribir este código es algo tedioso sobre todo si queremos que la excepción se replique y sólo metemos la cláusula Finally por el hecho de cerrar la conexión.

Para facilitar el trabajo VB.NET en .NET 2.0 incluye una cláusula especial denominada Using que habilita la destrucción automática de los objetos a los que se hace referencia. Así el código anterior quedaría simplemente:

referencia. Así el código anterior quedaría simplemente: Al terminar la cláusula Using (aunque haya un error

Al terminar la cláusula Using (aunque haya un error por medio) se llama de manera automática al método Dispose del objeto utilizado (en este caso una conexión). Entre otras cosas este método se encarga de cerrar el objeto si estaba abierto, por lo que no nos tendremos que preocupar de este aspecto.

1.3.2.1.3. Grupos de registros

Aunque los DataReader se asemejan al funcionamiento de un cursor firehose, en realidad difieren bastante de éstos. Imaginemos que conectamos con la base de datos en el ejemplo anterior y, mientras estamos procesando el bucle de los registros, se interrumpe la conexión a la base de datos por el motivo que sea.

En principio en el caso de un cursor firehose tradicional obtendríamos un error porque se ha roto la conexión con el servidor. En el caso de un DataReader es posible que sigamos ejecutando varias vueltas más del bucle sin problemas. Esto se debe a que, en

realidad, el DataReader obtiene los registros en grupos a través de la conexión y va solicitando nuevos grupos a medida que los necesita.

Es algo que hay que tener en cuenta a la hora de utilizarlos.

1.3.2.1.4. Ventajas e inconvenientes

El código anterior, aunque sencillo, es un poco lioso y el uso de los DataReader está algo limitado dada su idiosincrasia (de sólo lectura y hacia adelante). Este código es adecuado si no necesitamos almacenar los resultados de la consulta en memoria ni regresar sobre ellos una vez procesados una primera vez. También es muy útil para obtener resultados con miles o millones de registros que queremos tratar secuencialmente pero no almacenar en memoria.

Sin embargo para un uso cotidiano se trata de un código muy poco útil y complicado de utilizar salvo para cosas muy sencillas. Además sólo hemos utilizado clases de la capa conectada de ADO.NET. Todavía debemos aprender a obtener los resultados dentro de un DataSet para su explotación de manera cómoda. Hay que tender un puente entre ambos mundos (conectado y desconectado): el DataAdapter.

1.3.2.2. DataAdapter: puente entre mundos

Si lo que deseamos es poder almacena temporalmente en memoria los datos obtenidos de una consulta debemos recurrir al uso de objetos de la clase DataSet. Como sabemos se trata de un almacenamiento en memoria desvinculado por completo del origen de los datos.

Si el ejemplo anterior lo modificamos para convertirlo en una función que nos devuelva un DataSet con los datos obtenidos a partir de la consulta, el resultado sería similar a este:

a partir de la consulta, el resultado sería similar a este: La primera parte del código

La primera parte del código es como la anterior. Se crean una conexión y un comando. Lo que difiere bastante es la segunda parte. Hay varias diferencias importantes:

1. Ya no aparece en el código objeto DataReader de tipo alguno.

2. No se abre ni se cierra la conexión a la base de datos.

3. Se crea un nuevo DataSet y aparece un objeto de la clase DataAdapter.

Un DataAdapter es una clase que conoce las particularidades de la capa conectada de un determinado proveedor y es capaz de "comunicar" información entre ésta y la capa desconectada formada por los DataSet.

El método Fill de un DataAdapter se encarga e rellenar un DataSet con los datos obtenidos

a partir de una consulta (o procedimiento almacenado) definida a través de un comando.

Su propiedad SelectCommand se usa para indicar qué comando se utilizará para rellenar

el DataSet.

Internamente el método Fill emplea el objeto DataReader devuelto por ExecuteReader y rellena el DataSet con él por lo que sería factible crear un código equivalente. Sin embargo es muy cómodo y nos evita problemas y el tener que "reinventar la rueda" en cada proyecto.

1.3.2.2.1. El objeto DataSet

Los objetos DataSet contienen a su vez objetos DataTable que son los que contienen realmente los datos. Para acceder a las tablas de un DataSet se utiliza su colección Tables. Se puede acceder a las tablas por posición (números enteros) o por nombre si lo sabemos.

En un ejemplo sencillo como el anterior (y por otro lado uno de los más comunes) se crea una única tabla en el DataSet de nombre "Table1" y posición 0.

Las tablas contienen dos colecciones interesantes:

Columns: conjunto de objetos de tipo DataColumn que ofrecen información sobre los campos de la tabla (nombre, tipo, longitud

Rows: colección de objetos de la clase DataRow que contienen información concreta sobre cada campo de un registro de la base de datos.

Con esta información resulta muy sencillo tratar los datos de una consulta. Podemos acceder directamente a cualquier registro de la tabla usando su posición en la colección de filas. Por ejemplo para acceder al quinto registro de una tabla basta con escribir:

Dim dr As DataRow Dr = ds.Tables(0).Rows(4)

(nótese que las colecciones comienzan a numerarse en 0, de ahí que el quinto registro tenga índice 4).

Podemos acceder a cualquier campo del registro usando su posición o bien su nombre, así:

ds.Tables(0).Rows(4)("CompanyName")

que devolverá un objeto del tipo adecuado para el campo que representa (una cadena,

un objeto de fecha, un booleano, etc

Nota: Es muy sencillo definir objetos DataTable que dispongan de los campos que deseemos sin depender de origen alguno de datos. Emplee el método Add de la colección Columns para crear nuevos campos, algunos de los cuales pueden ser incluso derivados mediante una fórmula de los valores de otros. Esto permite definir estructuras de almacenamiento a medida en memoria sin preocuparnos de usar una base de datos para ello.

1.3.2.2.2. Ventajas del uso de objetos DataSet

Es posible cargar datos de varias tablas en un mismo DataSet, incluso aunque procedan de bases de datos diferentes, y relacionarlas en memoria. Es posible establecer relaciones entre ellas para mantener la consistencia, así como hacer un mantenimiento en memoria de los datos (altas, bajas y modificaciones). Posteriormente se puede

).

sincronizar el DataSet con el servidor usando un DataAdapter, realizando el proceso contrario al de obtención de datos. Luego lo veremos.

La principal ventaja de un DataSet es que podemos almacenarlo en donde queramos (en una variable global, en caché, incluso a disco a una base de datos) para trabajar con él sin estar conectado a una base de datos. De hecho se pueden transferir DataSet completos entre diferentes máquinas o por Internet, trabajar con ellos en remoto, almacenarlos, recuperarlos y finalmente transferirlos en cualquier momento de nuevo al origen (enteros o sólo los cambios) para sincronizarlos.

Es posible moverse con libertad entre los registros de una tabla y sus registros relacionados en otras tablas. Y sobre todo, se pueden vincular con elementos de la interfaz gráfica para mostrar los datos automáticamente.

1.3.2.3. Consultas parametrizadas

Las consultas simples como la que acabamos de utilizar en los ejemplos anteriores son muy raras. En la realidad las consultas son mucho más complejas, suelen intervenir varias tablas y dependen de diversos parámetros que le añaden condiciones.

Por ejemplo, si en la base de datos Northwind queremos obtener sólo aquellos clientes de un país determinado. Podríamos escribir de nuevo la función DameClientes para que se pareciese a la siguiente:

función DameClientes para que se pareciese a la siguiente: Esta función acepta un país como parámetro

Esta función acepta un país como parámetro y lo único que hace es concatenar a la consulta una nueva condición que introduce el país.

Esto, sin duda, funcionaría. Sin embargo presenta multitud de problemas seguramente no demasiado obvios para los programadores noveles. Los más importantes son los siguientes:

1. Este código es vulnerable a problemas de seguridad provocados por ataques de inyección de SQL. Esto puede poner en peligro fácilmente nuestra aplicación e incluso todo nuestro sistema en casos graves. El estudio de esta problemática se sale del ámbito de este curso, pero créame cuando le digo que se trata de un gravísimo problema que debemos evitar a toda costa.

2. Si se solicitan 100 consultas idénticas al servidor en las que sólo varía el nombre del país por el que se filtra, éste no tiene modo de saber que son las mismas y sacar factor común. Es decir, no se puede reutilizar el plan de consulta de la primera de ellas para las demás por lo que se debe calcular de nuevo cada vez, incidiendo en el rendimiento y la escalabilidad de la aplicación. Obviamente en consultas más complicadas es un problema más importante que en esta.

3. Este código es más difícil de transportar a otros sistemas de bases de datos ya que hay que incluir los delimitadores y notaciones específicos de cada gestor. Por ejemplo en SQL Server los delimitadores de fechas son comillas simples ('), mientras que en Access son almohadillas (#) y la sentencia usada no se puede reutilizar.

La forma correcta de realizar este tipo de consultas es utilizar parámetros en la consulta. Ello evita los problemas enumerados.

Los objetos de tipo Command disponen de una colección llamada Parameters que sirve para asignar dichos parámetros. Éstos previamente se definen en la consulta utilizando comodines que marquen su ubicación.

Nota: Cada proveedor de datos utiliza su propia convención para indicar la posición de los parámetros en una consulta. En el caso de SQL Server se indican con una arroba '@' seguida de un identificador. En otros proveedores no se puede definir nombre para los parámetros, sólo se marca su posición con un caracter especial.

La función anterior empleando parámetros sería la siguiente:

función anterior empleando parámetros sería la siguiente: Se ha resaltado los cambios. Como vemos en lugar

Se ha resaltado los cambios.

Como vemos en lugar de concatenar cadenas se marca la posición de las partes de la

consulta que varían con un parámetro que identificador.

Luego hay que declarar el parámetro añadiéndolo a la colección de parámetros. Para ello se indica su nombre y el tipo de datos que representa (en este caso un NVarChar de SQL Server, que es una cadena de longitud variable).

arroba seguida de un

consta

de

una

Por fin se asigna su valor concreto antes de lanzar la consulta.

El proveedor de datos crea la consulta de la manera correcta, usando los delimitadores

adecuados y tratando los datos del modo que sea preciso para asegurar que es correcta. Ello elimina los problemas de los que hablábamos anteriormente y permite optimizar el uso

de consultas.

1.3.2.4.

Altas bajas y modificaciones

Con lo que hemos visto hasta ahora ya estamos en condiciones de escribir código para realizar altas, bajas y modificaciones de registros. Al fin y al cabo éstas son simplemente consultas SQL del tipo adecuado (INSERT, DELETE o UPDATE) que se deben enviar al servidor.

Así pues, para crear un nuevo cliente podemos escribir una función como la siguiente:

cliente podemos escribir una función como la siguiente: Es un código muy similar al anterior que

Es un código muy similar al anterior que realizaba una selección de datos. En este caso se ha definido una consulta de inserción con tres parámetros. En lugar de usar ExecuteReader

o un DataAdapter en este caso se utiliza el método ExecuteNonQuery. Éste lanza la

consulta (es decir, se inserta el nuevo registro) y devuelve el número de registros afectados por la consulta (que en el caso de una inserción siempre es 1 si se inserta

correctamente o 0 si ha habido un error).

Las actualizaciones y eliminaciones de registros se podrían conseguir de modo similar.

1.3.2.4.1. Trabajando desconectados

El código anterior funciona perfectamente pero no es la forma óptima de trabajar cuando tenemos que tratar con datos desconectados contenidos en objetos DataSet.

Los DataSet normalmente circulan, independientes de cualquier origen de datos, entre

las diferentes capas de una aplicación e incluso se mueven entre contextos de ejecución

y máquinas (por ejemplo a través de un servicio Web como veremos). Hemos dicho que

son como pequeñas bases de datos, por lo que la forma habitual de trabajar con ellos es

añadir, eliminar y modificar registros directamente sobre sus tablas, sin pensar en la ubicación real de estos datos.

Así pues, para crear un nuevo registro en un DataSet debemos usar el método NewRow del DataTable en la que lo queremos insertar. El nuevo registro tiene la misma estructura (el mismo "esquema") que el resto de registros y sólo tendremos que rellenar sus datos. Una vez rellenados añadiremos el nuevo registro a la colección de filas de la tabla. Así por ejemplo si tenemos almacenado un DataSet con una tabla con los datos de los clientes obtenidos con la función de ejemplo DameClientes, podríamos agregar uno nuevo de la siguiente manera:

podríamos agregar uno nuevo de la siguiente manera: Es decir, se crea la nueva fila/registro, se

Es decir, se crea la nueva fila/registro, se rellenan sus campos y se añade a la colección de filas con el método Add de ésta.

La actualización de datos es más sencilla aún ya que basta con acceder directamente al registro que nos interesa modificar y cambiar los valores de sus campos. Por ejemplo, para modificar la dirección del cliente cuyos datos están en el quinto registro de nuestra tabla sólo hay que escribir:

el quinto registro de nuestra tabla sólo hay que escribir: Por fin, para eliminar un registro

Por fin, para eliminar un registro sólo hay que usar su método Delete, así:

un registro sólo hay que usar su método Delete , así: que borraría el quinto registro

que borraría el quinto registro de nuestra tabla en memoria.

1.3.2.4.2. Conciliando los cambios con el origen

Es muy fácil trabajar con los Dataset en memoria de este modo. Sólo hay un "pequeño" problema: los cambios se realizan en memoria pero, al no estar vinculado el DataSet con origen de datos alguno, no los veremos reflejados en la base de datos que es lo que buscamos.

Debemos realizar una sincronización entre la representación en memoria de los datos y su ubicación física real, para lo cual debemos hacer que los cambios trasciendan a la capa conectada. Al igual que cuando los recuperamos, el trasiego en el otro sentido se realiza con la ayuda del puente que representa la clase DataAdapter.

Al igual que utilizábamos la propiedad SelectCommand para indicar cómo se recuperaban los datos hacia un DataSet, ahora debemos utilizar las propiedades InsertCommand, UpdateCommand y DeleteCommand para indicar qué comandos

se deben usar para agregar, modificar y eliminar los registros modificados en memoria

a través del DataSet.

Una vez especificados estos comandos sólo resta llamar al método Update del DataAdapter para que se ocupe de sincronizar automáticamente todos los cambios que hayamos realizado en las tablas en memoria. Este método acepta como argumentos un

DataSet completo, una DataTable o incluso un DataRow si sólo queremos actualizar un único registro.

1.3.2.4.3. Definición de los comandos

Los comandos de inserción, modificación o borrado para el DataAdapter se definen del mismo modo que en el código de ejemplo al principio de este apartado, es decir, se define la consulta apropiada y sus parámetros. En esta ocasión como los parámetros serán rellenados automáticamente por el DataAdapter hay que utilizar una sobrecarga del método Add de la colección de parámetros que incluye el nombre del campo asociado con el parámetro, así:

el nombre del campo asociado con el parámetro, así: En este caso se define el parámetro

En este caso se define el parámetro "@Dirección", de tipo NVarChar y longitud 60 que se refiere siempre al valor del campo "Address" de la tabla.

Por ejemplo, para definir la consulta de eliminación de registros de la tabla de clientes usaríamos el siguiente código:

de la tabla de clientes usaríamos el siguiente código: siendo CustomerID la clave primaria de la

siendo CustomerID la clave primaria de la tabla.

Una vez definidos los tres parámetros de alta, baja y modificación sólo resta llamar a Update para que el DataAdapter se ocupe de toda la sincronización.

1.3.2.4.4. Ventajas

Este modelo está lleno de ventajas aunque a primera vista pueda parecer algo engorroso.

Nota: Luego veremos que podemos usar las herramientas que nos proporciona Visual Studio 2005 para definir de manera automática los comandos de manipulación de datos sin necesidad de pasar el trabajo de hacerlo manualmente.

Para empezar podemos trabajar con los datos en total libertad sin preocuparnos de si existe conexión o no con el origen y aunque el DataSet se manipule en una ubicación física a miles de kilómetros del origen y desconectado de éste o los cambios se almacenen a disco y se concilien días más tarde.

Se pueden realizar multitud de modificaciones en los datos y luego conciliarlas simultáneamente en lugar de hacerlo en tiempo real de una en una.

El paso de datos entre capas y funciones se simplifica. Lo habitual antes era definir funciones con tantos parámetros como datos se quisieran modificar en un registro. Por ejemplo, para insertar un registro en una tabla que tiene 20 campos se definía una función con 20 parámetros (muchos de ellos opcionales) o, en el mejor de los casos, se pasaba una clase creada a medida que representaba lo valores del registro. Ahora basta con pasar un DataSet, un DataTable o un dataRow que ya contiene toda la información que necesitamos saber sobre los registros y su tabla asociada.

Lo mejor es que es posible saber mediante ciertas propiedades qué registros han cambiado (nuevos, modificados, borrados) y mover entre capas exclusivamente estos. La propiedad HasChanges de los DataSet, DataTable y DataRow nos informa de si el objeto ha sufrido cambios de algún tipo.

El método GetChanges de los objetos DataSet y DataTable devuelve un subconjunto de los datos que contiene exclusivamente los cambios. Así, aunque en un DataSet

tengamos 1000 registros, si sólo hemos modificado 2 sólo será necesario trasegar la información de estos a la hora de enviarlos a otra capa o función para sincronizarlos con la base de datos.

El método GetChanges se puede invocar sin parámetros o indicando qué tipo de cambios queremos obtener, lo que se indica con un valor de la enumeración DataRowState:

Valor

Significado

Added

Registros que no estaban originalmente

Deleted

Registros que se han eliminado

Modified

Registros cuyos valores se han modificado

UnChanged

Registros que no se han modificado

Detached

Registros que se han desasignado de una tabla (pero no borrado con Delete)

Se puede dejar un DataSet en estado sin modificar llamando a su método AceptChanges. Esto es lo que hace un DataAdapter tras haber sincronizado los cambios con el origen de datos.

1.3.3. Acceso a datos con Visual Studio 2005

Ahora que ya hemos visto la forma de trabajo manual con ADO.NET y dominamos los fundamentos, vamos a sacar partido a todas las ventajas que nos proporciona un entorno como Visual Studio 2005 que, como veremos, nos permiten hacer casi cualquier tarea de datos sin necesidad de escribir código.

1.3.3.1. Controles de datos

Aparte de la escritura manual de código y el uso directo de objetos de ADO.NET, ASP.NET 2.0 proporciona un nuevo modelo de trabajo declarativo para acceso a datos que nos permite realizar tareas comunes de acceso a datos sin escribir código alguno.

ASP.NET 2.0 presenta dos nuevos tipos de controles Web que participan en este modelo declarativo de enlace a datos. Estos controles nos abstraen por completo de las complejidades de manejo de las clases de ADO.NET por un lado, y de las dificultades inherentes al modo de trabajo desconectado de las páginas Web. Equiparan en muchos aspectos el desarrollo Web al tradicional desarrollo de aplicaciones de escritorio.

Estos controles se encuentran agrupados en el cuadro de herramientas bajo el nombre de "Datos", tal y como se ve en la figura.

1.3.3.1.1. Orígenes de datos Estos controles de datos representan conexiones con diferentes tipos de orígenes

1.3.3.1.1. Orígenes de datos

Estos controles de datos representan conexiones con diferentes tipos de orígenes de información que van desde bases de datos a objetos de negocio. No disponen de apariencia visual pero se arrastran igualmente sobre los formularios Web para trabajar con ellos visualmente y poder usar sus paneles de tareas. Abstraen al programador de las complejidades relacionadas con el manejo de los datos, permitiendo de manera automática seleccionarlos, actualizarlos, ordenarlos, paginarlos, etc.

ASP.NET 2.0 ofrece los siguientes controles de origen de datos que se pueden ver en la figura anterior:

Control

Descripción

SqlDataSource

Enlaza con cualquier base de datos para que exista un proveedor de ADO.NET.

AccessdataSource

Esta especializado en trabajar con bases de datos Microsoft Access.

ObjectDataSource

Se enlaza con objetos de negocio y capas personalizadas de acceso a datos.

XmlDataSource

Trata datos contenidos en documentos XML.

SiteMapDataSource

Se enlaza con la jerarquía de clases expuesta por el modelo de navegación de sitios de ASP.NET 2.0.

¡ATENCIÓN!: El control SqlDataSource sirve para enlazar con cualquier gestor de datos relacional para la que haya proveedor ADO.NET disponible, no sólo para enlazar con SQL Server. No se deje despistar por su nombre.

1.3.3.1.2. Concurrencia optimista

Durante la configuración de un origen de datos SQL (luego lo verá en el primer vídeo de esta lección) una de las opciones avanzadas que se presenta habla de la Concurrencia optimista. La concurrencia optimista evita la actualización de registros en el caso de que haya variado cualquiera de sus campos desde que se obtuvieron de la fuente de datos. Este comportamiento puede ser bueno en ciertas ocasiones, cuando queremos preservar los cambios realizados por cualquier usuario. En otras sin embargo no resulta de utilidad y sí añade una gran sobrecarga al acceso a datos.

Se le denomina concurrencia optimista porque parte de la base de que la posibilidad de coincidencia de dos usuarios sobre el mismo registro es baja y es un caso que apenas se dará. Al caso contrario a la concurrencia optimista se le denomina concurrencia pesimista.

Nota: En mi opinión debería llamarse "concurrencia realista" ya que, lo cierto es que si se analiza con detenimiento la posibilidad de conflicto en un sistema grande tiende a ser realmente pequeña en la mayoría de los casos. Y de todos modos el sobreescribir cierta información no suele ser un problema grave salvo cuando hablamos de cuestiones de dinero, facturas y similares.

Cuando se establece la concurrencia optimista las consultas que genera el asistente de Visual Studio incluyen todos los campos del registro como condición de búsqueda del mismo, por ejemplo:

DELETE FROM [Customers] WHERE [CustomerID] = @original_CustomerID AND [CompanyName] = @original_CompanyName AND [ContactName] = @original_ContactName AND [ContactTitle] = @original_ContactTitle AND [Address] = @original_Address AND [City] = @original_City AND [Region] = @original_Region AND [PostalCode] = @original_PostalCode AND [Country] = @original_Country AND [Phone] = @original_Phone AND [Fax] = @original_Fax

mientras que en un caso de concurrencia pesimista se emplea simplemente la clave primaria del registro para localizarlo:

DELETE FROM [Customers] WHERE [CustomerID] = @original_CustomerID

Es decisión suya qué tipo de actualización utilizar pero en la mayor parte de los casos usará seguramente la pesimista que, de hecho, es la que usted utiliza normalmente si escribe las funciones a mano de manera independiente.

1.3.3.1.3. Controles enlazados a datos

Se trata de controles de interfaz de usuario que, haciendo uso de los anteriores, muestran la información a través de la página. Pueden sacar partido a todas las propiedades de los orígenes de datos y por lo tanto habilitan de manera sencilla la edición, eliminación, ordenación, filtrado y paginación de los datos entre otras cosas.

Para conectar un control enlazado a un DataSource sólo hay que establecer su propiedad DataSourceID indicando el nombre apropiado. Así de fácil.

Nota: Si usted ya conoce los controles enlazados a datos de ASP.NET 1.x como el

DataGrid, el DataRepeater, etc

pues al contrario que con aquellos no hay que escribir código alguno. Aunque estos controles "antiguos" no aparecen en el cuadro de herramientas siguen estando soportados y los puede seguir utilizando. Incluso si lo desea puede añadirlos al cuadro

de herramientas. De todos modos se recomienda utilizar el nuevo modelo declarativo de acceso a datos.

1.3.3.1.4. Uso de los controles de datos en la práctica

La mejor forma de conocer la manera de trabajar con estos controles es viéndolos actuar en la práctica. Los vídeos de esta lección muestran un ejemplo completo de uso de los controles en el que se explotan unos datos para su visualización, edición y eliminación. Por favor, examínelo con atención y luego trate de practicarlo por su cuenta con ejemplos similares.

estos controles le resultarán más cómodos de utilizar

1.3.3.2.

DataSet tipados

La clase DataSet, como cualquier otra clase no sellada de .NET, puede ser extendida mediante herencia para añadirle nuevas funcionalidades y aprovechar las ya existentes. Si creamos una nueva clase que herede de DataSet y ésta la especializamos en el tratamiento de un conjunto de datos determinado que conocemos de antemano nos encontramos un DataSet especializado.

Por ejemplo, podemos crear un DataSet especial que tengan en cuenta las particularidades de los datos que maneja para validarlos antes de permitir su inserción, que verifique las relaciones con otros datos o que los transforme o controle el acceso a los mismos. Nosotros sólo tendríamos que ocuparnos de estas tareas dejando a la clase DataSet de la que hemos heredado todos los puntos complejos que tienen que ver con la gestión de datos pura y dura.

Esta es, en esencia, la idea que subyace bajo los DataSet tipados de Visual Studio 2005. Se trata de clases especializadas derivadas de DataSet que ofrecen una forma más rápida y sencilla de acceder a los datos que albergan. Además Visual Studio nos permite crear este tipo de DataSet de forma visual y usando asistentes. En el vídeo se ilustra la manera de conseguirlo.

Los DataSet tipados parten del conocimiento preciso de la estructura de una base de datos para definir su funcionalidad. Esta es su principal diferencia con los DataSet normales puesto que éstos son genéricos y no saben nada acerca de los datos que albergan. Los tipados sólo sirven para albergar una información muy concreta. Ganan en especialización y pierden en versatilidad.

Para agregar un DataSet tipado a nuestro proyecto sólo hay que presionar con el botón secundario sobre la carpeta App_Code y en el diálogo que aparece elegir el icono de DataSet. La extensión del archivo generado es '.xsd' porque lo que en realidad albergan es un esquema XML que define la estructura de los datos en los que se van a especializar.

Una vez agregado el DataSet aparece una superficie de diseño y un asistente como el de la figura.

superficie de diseño y un asistente como el de la figura. Figura 4.5.- Primer paso del

Figura 4.5.- Primer paso del asistente de configuración de un TableAdapter.

Este asistente nos permite configurar un objeto TableAdapter que se encargará de trasegar datos entre el DataSet tipado que estamos creando y la base de datos. Un TableAdapter es una clase que encapsula a un DataAdapter especializado en los datos que vamos a procesar con la tabla del DataSet tipado que estamos definiendo. De hecho sus métodos son, por defecto, los mismos que los de un DataAdapter normal (Fill, Update

Con él dispondremos de métodos para recuperar información, crear nuevos registros, actualizarlos y eliminarlos, sólo que los correspondientes comandos estarán creados de manera automática o asistiéndonos en ello. Así, por defecto, se definen un par de métodos para obtener los datos subyacentes rellenando un DataSet que se le pase (método Fill) o devolviendo directamente un DataTable (método GetData). Además el método Update sirve para conciliar automáticamente los cambios del un DataSet tipado con la base de datos original.

No vamos a analizar desde el texto la definición de estos objetos adaptadores pero puede conocerlo viendo el vídeo de esta lección.

Truco: Podemos ver el código que se genera de manera automática para crear el

DataAdapter si hacemos doble-clic sobre él desde el examinador de objetos de Visual Studio (CTRL+ALT+J). Esto hará que se abra el archivo de código auto-generado por ASP.NET desde la ubicación real de ejecución (normalmente una ruta del estilo

C:\Windows\Microsoft.NET\

aprender el funcionamiento interno de los DataSet tipados.

).

Es muy interesante echarle un vistazo a este código para

1.3.3.2.1. Partes de un DataSet tipado

Al igual que un DataSet normal, uno tipado consta de un conjunto de tablas y relaciones entre ellas. En este caso, sin embargo, podemos acceder a las tablas y a sus campos utilizando directamente sus nombres en lugar de recorrer la colección de tablas, lo cual lo hace más fácil de usar.

Cada una de las tablas del DataSet lleva asociado como mínimo un TableAdapter. Entre los dos objetos (el DataTable y el o los TableAdapter relacionados) se reparten el trabajo. El DataTable tipado mantiene en memoria la información y el TableAdapter actúa de puente con la tabla real en la base de datos.

Nota: Como sabemos (y veremos en el vídeo también) las tablas de un DataSet no tienen porqué coincidir con tablas reales de una base de datos ya que pueden ser resultados obtenidos de una consulta compleja que involucre a varias tablas. En estos casos es más complicada la actualización y se suelen usar únicamente para recuperar datos. la alternativa habitual es tratar de replicar la estructura física de la base de datos en la estructura en memoria del DataSet de modo que se tiene acceso estructurado a la misma información y gracias a las relaciones y restricciones se conserva la consistencia de los datos también estando desconectados.

El DataSet tipado dispone de propiedades que coinciden con los nombres de los objetos que contienen. Así, por ejemplo, si tenemos una tabla "Clientes" con un campo "Nombre" podemos acceder a él directamente con este código:

ds.Clientes(0).Nombre

que nos daría el nombre del primer cliente de la tabla de clientes en el DataSet 'ds'. esta propiedad nombre además ya sería un campo de tipo String que es el tipo adecuado para la información albergada, por lo que se simplifica mucho su uso.

En un DataSet normal para obtener lo mismo tendríamos que haber escrito:

ds Tables(0).Rows(0)("Nombre").ToString()

que obviamente es mucho menos legible.

La cosa no termina aquí ya que además se definen clases específicas para representar los registros de las tablas. Por ejemplo si la tabla se llama 'Clientes' existirá una clase

ClientesRow que dispone de propiedades con el mismo nombre y tipo que los campos correspondientes en la tabla de la base de datos y que hemos usado en la línea de ejemplo. Así, para añadir un cliente podríamos escribir:

Dim cliente As New Cliente cliente.Nombre = "Pepe"

ds.Clientes.AddClientesRow(cliente)

Nótese que el método Add del DataTable se ha sustituido por uno con nombre especializado que nos ayuda a saber mejor por donde pisamos, pero su función es idéntica.

Para rellenar una tabla de un DataSet tipado se usa su correspondiente TableAdapter

así:

Dim clientes As New ClientesDS Dim ta As New ClientesTableAdapters.ClientesTableAdapter() ta.Fill(clientes.Clientes) clientes.Clientes(0).Nombre = "Pepe"

o también usando el método GetData para obtener la tabla directamente si sólo nos interesa esa parte concreta del DataSet (que puede constar de varias tablas):

Dim clientes As Clientes.ClientesDataTable Dim ta As New ClientesTableAdapters.ClientesTableAdapter() clientes = ta.GetData() clientes(0).Nombre = "Pepe"

Para un mismo DataTable tipado se pueden definir diversos TableAdapter especializados aparte del básico que permite llenar todos los datos: para filtrar por diversos parámetros normalmente.

El uso de DataSet tipados es muy recomendable puesto que simplifica mucho el trabajo puesto que podemos realizar casi todo el trabajo utilizando asistentes y accediendo a la información de manera muy cómodo. Además es un modo muy sencillo de separar la funcionalidad de la base de datos del resto del código. Así, si se hace necesario en el futuro, se puede exponer esta parte de manera independiente mediante un, por ejemplo, un servicio Web que utilice el DataSet tipado y sus TableAdapters para acceder

a los datos desde una ubicación remota.

2.

Creación de bases de datos con Visual Studio

La mayoría de aplicaciones comerciales (y principalmente las de gestión) utilizan datos que deben guardarse en algún sitio. Generalmente esta información se guarda en bases de datos. En éste módulo, verás cómo crear una base de datos para SQL Server 2005 Express, y crearás tablas para guardar la información a almacenar; aprenderás sobre los tipos de datos que forman las columnas de las tablas, cómo relaciona las tablas, integridad, y cómo realizar consultas sobre las tablas.

2.1. Objetos básicos del sistema

2.1.1. Crear una base de datos

Cuando se crea una base de datos, es importante comprender cómo SQL Server almacena los datos para poder calcular y especificar la cantidad de espacio en disco que hay que asignar a los archivos de datos y registros de transacciones. Aunque SQL Server automáticamente incrementa el tamaño asignado a los ficheros de bases de datos dinámicamente, para tener un rendimiento óptimo del sistema (evitar fragmentación de los ficheros), se recomienda ser "precisos" a la hora de definir el tamaño de la base de datos.

2.1.1.1. Ficheros de la base de datos

Todas las bases de datos tienen un archivo de datos principal (.mdf), y uno o varios archivos de registro de transacciones (.ldf). Una base de datos también puede tener archivos de datos secundarios (.ndf). La extensión definida para cada tipo de archivos es libre, pero como buenas prácticas se suele seguir el modelo de extensiones recomendado por Microsoft (mdf, ldf, y ndf). El proceso de creación de la base de datos, consiste en hacer una copia de la base de datos model, que incluye las tablas del sistema. La ubicación predeterminada para todos los archivos de datos y registros de transacciones es C:\Archivos de programa\Microsoft SQL Server\MSSQL.1\MSSQL\Data.

La base de datos puede configurarse con tres modelos de recuperación en caso de fallo del sistema (FULL, BULK_LOGGED, y SIMPLE); evalúa cada modelo en base a las necesidades del sistema que estés implementando.

2.1.1.2. Creación de base de datos desde SQL Server 2005 Management

Studio Express

SQL Server Management Studio Express es la herramienta de administración incluida con SQL Server Express; la herramienta no fue incluida en la primera distribución de SQL Server Express, y se puede descargar de forma gratuita de la siguiente url:

Microsoft SQL Server Management Studio Express - Community Technology Preview (CTP) November 2005:

http://www.microsoft.com/downloads/details.aspx?familyid=82afbd59-57a4-455e-a2d6-

1d4c98d40f6e&displaylang=en

Para crear una base de datos desde SQL Server 2005 Management Studio, expande la lista de bases de datos, de la lista de instancias de SQL Server 2005 registradas, y selecciona la opción "New database":

de datos, de la lista de instancias de SQL Server 2005 registradas, y selecciona la opción

Deberás rellenar el nombre de base de datos, nombres lógico y físico de los archivos relacionados, y tamaño de los ficheros, así como su crecimiento:

de datos, nombres lógico y físico de los archivos relacionados, y tamaño de los ficheros, así

Y deberás establecer el modo de recuperación de la base de datos; en la versión Express al crear una base de datos por defecto se establece como recuperación SIMPLE:

de datos por defecto se establece como recuperación SIMPLE: Para famializarte con la sintaxis del lenguaje

Para famializarte con la sintaxis del lenguaje T-SQL, te recomiendo que utilices la opción de menú script (en recuadro verde en las dos imágenes anteriores), que mostrará la instrucción T-SQL correspondiente a las operaciones que has ido configurando en las distintas opciones.

2.1.1.3. Consideraciones

Dependiendo de las necesidades a cubrir del sistema de base de datos a desarrollar, deberás configurar los archivos de una forma u otra; por ejemplo, en grandes sistemas llegarás a configurar niveles de redundancia de discos (RAID), y repartirás la información de las tablas en distintos ficheros. Aunque no está soportado en la versión Express, funcionalidades del producto como Particionado de datos, llegan a ser primordiales, y es un factor a considerar desde el momento de diseño del sistema.

Por otro lado, para familiarizarte con SQL Server, recomendaría entender las opciones de configuración de base de datos que aparecen en la pestaña opciones, que aunque no son necesarias para comenzar a diseñar bases de datos SQL Server, si ayudan a comprender un poco el funcionamiento interno de SQL Server (shrink, statistics, etc.):

2.1.2. Tipos de datos Empezaremos por los tipos de datos. Las tablas tienen columnas, y

2.1.2. Tipos de datos

Empezaremos por los tipos de datos. Las tablas tienen columnas, y las columnas se definen en base a un tipo de datos; los tipos de datos acotan el tipo y tamaño de la información que se guardará en una columna. La importancia de la elección de los tipos de datos reside en el almacenamiento que ocupa; para varios cientos de filas, el tamaño no es tan crucial, pero cuantas más filas se añadan a la tabla, mayor será la repercusión en el rendimiento de las operaciones de E/S.

Como veremos, habrá tipos de datos en los que habrá que seleccionar el tamaño, e incluso algunos tendrán la posibilidad de ofrecer tamaño variable; vamos a analizar los más significativos.

2.1.2.1. Tipos de datos numéricos

Los tipos de datos numéricos se utilizan para guardar valores numéricos enteros, o decimales. Los dividiremos en dos grandes grupos: enteros, y decimales.

2.1.2.1.1.

Tipos de datos numéricos enteros

Tipo de dato

Precisión

Almacenamiento

bigint

De -2^63 (-9.223.372.036.854.775.808) a 2^63-1 (9.223.372.036.854.775.807)

8

bytes

 

De -2^31 (-2.147.483.648) a 2^31-1

 

int

(2.147.483.647)

4

bytes

smallint

De -2^15 (-32.768) a 2^15-1 (32.767)

2

bytes

tinyint

De 0 a 255

1

bytes

bit

0,1

bit, mímino 1 bytes

1

Tradicionalmente el tipo de datos más usado es el int; el tipo de datos bigint apareció en SQL Server 2000, y es la alternativa al tipo de datos int, cuando los valores son muy grandes.

2.1.2.1.2. Tipos de datos numéricos decimales

Decimal, numeric. Tipo de datos numérico con precisión y escala fijas.

decimal[ (p[ ,s] )] y numeric[ (p[ ,s] )]

Números de precisión y escala fijas. Cuando se utiliza la precisión máxima, los valores permitidos están comprendidos entre - 10^38 +1 y 10^38 - 1. Numeric equivale funcionalmente a decimal

p (precisión). El número total máximo de dígitos decimales que se puede almacenar, tanto a la izquierda como a la derecha del separador decimal. La precisión debe ser un valor comprendido entre 1 y la precisión máxima de 38. La precisión predeterminada es

18.

s (escala). El número máximo de dígitos decimales que se puede almacenar a la derecha del separador decimal. La escala debe ser un valor comprendido entre 0 y p. Para especificar la escala es necesario haber especificado la precisión.

Y la relación entre precisión y almacenamiento es:

Precisión

Almacenamiento

1-9

5

10-19

9

20-28

13

29-38

17

money, smallmoney. Tipos de datos que representan valores monetarios o de moneda.

Tipo de dato

Precisión

Almacenamiento

money

De -922,337,203,685.477,5808 a 922,337,203,685.477,5807

8

bytes

smallmoney

De - 214.748,3648 a

4

bytes

214.748,3647

 

float(n), single. Tipos de datos numéricos y aproximados que se utilizan con datos numéricos de coma flotante. Los datos de coma flotante son aproximados; por tanto, no todos los valores del intervalo del tipo de datos se pueden representar con exactitud.

Tipo de dato

Precisión

Almacenamiento

float

De -1,79E 308 a -2,23E -308 , 0 y de 2,23E -308 a 1,79E 308

Depende del valor de n (4 u 8 bytes)

real

De -3,40E 38 a -1,18E -38 , 0 y de 1,18E -38 a 3,40E 38

4

bytes

2.1.2.2. Tipos de datos de caracteres

Los tipos de datos caracter se puede definir de longitud fija y de longitud variable.

Los de longitud fija son char(n) y su tamaño lo define el valor que tenga n. Por ejemplo, una columna char(15) ocupa 15 bytes.

Los de longitud variable son varchar(n), y su tamaño lo define la longitud de la columna guardada; por ejemplo una columna varchar(250), que guarda el valor "columna variable" el almacenamiento que ocupa es 16 bytes.

En caso de desear valores Unicode, deberás anteponer al tipo de datos la letra n, siendo los tipos nchar, o nvarchar. La principal diferencia con los tipos de datos no-unicode, es que utilizan el doble de bytes. Por ejemplo, el texto "Tutorial", en una columna varchar(100) ocuparía 8 bytes, mientras que siendo unicode ocuparía 16 bytes.

El tamaño de las columnas char, varchar, nchar, nvarchar está limitado a 8000 bytes de almacenamiento; en caso de necesitar mayor longitud puedes usar el tipo de datos varchar(max) o nvarchar(max), que es nuevo en SQL Server 2005.

2.1.2.3. Tipos de datos fecha

smalldatetime, datetime. Son los tipos de datos utilizados para representar la fecha y la hora. El valor internamente se almacena como un valor integer, y dependiendo de la precisión utilizará 4 u 8 bytes.

Tipo de dato

Precisión

Almacenamiento

smalldatetime

Del 1 de enero de 1900 hasta el 6 de junio de 2079

4 u 8 bytes

datetime

Del 1 de enero de 1753 hasta el 31 de diciembre de 9999

8

bytes

El tipo de datos smalldatetime almacena las fechas y horas del día con menor precisión que datetime. El Database Engine (Motor de base de datos) almacena los valores smalldatetime

como dos enteros de 2 bytes. Los dos primeros bytes almacenan el número de días después del 1 de enero de 1900. Los otros dos, almacenan el número de minutos desde medianoche.

Los valores datetime se redondean con incrementos de 0,000; 0,003 o 0,007 segundos, como se muestra en la siguiente tabla.

2.1.2.4. Otros tipos de datos

Se pueden crear tipos definidos de usuario que suele ayudar para unificar el diseño de las tablas; por ejemplo, se puede crear un tipo llamado NIF que corresponde al tipo de datos CHAR(20), y admite valores nulos.

CREATE TYPE NIF FROM char(20) NULL

2.1.2.4.1. XML

La versión 2005 de SQL Server incorpora el tipo de datos nativo XML. El tipo de datos obliga a que el dato sea por lo menos bien formado (well-formed). Adicionalmente, la columna puede asociarse a un esquema XSD. Esta es una característica muy interesante porque cada día se están guardando más datos en formato XML, y las aplicaciones cliente tienen que soportar el coste y codificación de validar el dato a guardar. La otra gran funcionalidad que incorpora el tipo de datos XML es que se puede hacer consultas XPath 2.0 contra la columna XML; además la columna se puede indexar para optimizar las consultas XPath.

2.1.2.4.2. Tipos de datos definidos de usuario en .NET

La integración del CLR, permite la posibilidad de definir tipos de datos con cualquier lenguaje .NET; durante el curso no se va a tratar este tema, pero como recomendación general, sólo deberían usarse para definir estructuras complejas; por ejemplo, coordenadas 3D, o 2D, números complejos, etc. nunca para implementar estructuras relacionales.

2.1.3. Crear tablas

Las tablas son objetos que contienen la información guardada en la base de datos. Una tabla es una colección de columnas; cada columna tendrá un tipo de dato y una serie de propiedades. La información está guardada fila por fila de forma similar a lo que gráficamente representa una hoja Excel: una colección de filas por columnas.

2.1.3.1. Creación de tablas

Para crear tablas podremos utilizar SQL Server 2005 Management Studio Express, o Visual Basic Express Edition; la forma es muy similar en ambas herramientas, y esta vez también utilizaremos SQL Server 2005 Management Studio Express.

Expandiendo la base de datos "DemoMSDN", vemos una lista de tipos de objetos entre las que se encuentra "tables"; pulsando el botón derecho del ratón, y selecciona "New Table" como aparecen en la siguiente imagen:

Aparecerá una ventana como la siguiente que describimos a continuación: En el cuadro marcado en

Aparecerá una ventana como la siguiente que describimos a continuación:

ventana como la siguiente que describimos a continuación: En el cuadro marcado en rojo, se añaden

En el cuadro marcado en rojo, se añaden cada una de las columnas que forman parte de la tabla a crear; se pone nombre a la columna (debe comenzar por un caracter alfabético), se selecciona el tipo de datos y precisión (ver lección anterior para más información), y se establece si la columna aceptará valores nulos o no.

En el cuadro de debajo (en color azul), se podrán establecer las propiedades de cada columna de la tabla; se podrán modificar las propiedades vistas anteriormente, si la columna es calculada o no, si tiene propiedad identidad (que veremos más adelante en el capítulo), etc.

A la derecha, en el cuadro verde, se podrán establecer propiedades de la tabla; como

esquema al que pertenece la tabla, nombre de la tabla, descripción de la tabla, y grupo de ficheros donde se almacenará la tabla.

Además toda tabla debe tener una columna o conjunto de columnas que identifique de manera única cada fila de la tabla; para ello selecciona la columna que deseas como clave primaria, y después de hacer click en el botón derecho del ratón, selecciona "Primary Key" como se muestra en la imagen (también se puede hacer sobre el botón marcado en rojo en

la imagen):

Key" como se muestra en la imagen (también se puede hacer sobre el botón marcado en

A continuación, para grabar los cambios, es decir, para generar la tabla, pulsarás sobre la

zona en color verde de la siguiente imagen, y seleccionarás la opción Save Clientes (que es

el nombre de la tabla):

la opción Save Clientes (que es el nombre de la tabla): 2.1.3.2. Propiedad identity en las

2.1.3.2. Propiedad identity en las columnas

La propiedad identity, establece que una columna numérica genere automáticamente valores consecutivos partiendo de una semilla inicial, y un incremento definido. Se suele utilizar como clave primaria, en lugar de establecer claves primarias de columnas o conjuntos de columnas muy grandes. Lo importante de esta propiedad es que "convierte" automáticamente el valor de una columna a un valor numérico siguiente al anteriormente insertado. Por ejemplo, si definimos la columna Id de la tabla clientes como identity, a la hora de insertar filas, de esa columna nos "olvidaremos" porque SQL Server lo hace por nosotros. Luego ese valor lo podremos usar como referencia principal (clave primaria), para identificar la fila insertada. Para establecer la propiedad identity, debes seleccionar la columna a la que deseas establecer la propiedad, y hacerlo desde la ventana de propiedades de columna (cuadro rojo de la siguiente imagen):

2.1.4. Relacionar tablas 2.1.4.1. Relacionar tablas Las tablas se relacionan entre sí; podría decirse que

2.1.4. Relacionar tablas

2.1.4.1. Relacionar tablas

Las tablas se relacionan entre sí; podría decirse que existe información de la fila que está guardada en varias tablas. El nexo de unión de las filas es la clave primaria en la tabla padre, y la clave primaria en la tabla hija. Por ejemplo, una relación entre clientes y pedidos; en la tabla pedidos existirá un identificador de cliente que está asociado a un identificador de cliente en la tabla clientes. La información estaría repartida como sigue:

 

Clientes

ID

Nombre

Apellidos

1

Julia

Herrera

2

Javier

Alonso

 

Pedidos

IDPedido

IDCliente

Importe

1

 

1 1200

2

 

1 1300

3

 

1 1200

4

 

2 1000

Fíjate que el cliente 1 (Julia Herrera), tiene los pedidos del 1 al 3, y el cliente 2 el pedido 4. La información la "interpretamos" como si las filas de cliente en "embebiera dentro de la tabla pedidos:

 

Pedidos

IDPedido

IDCliente

Nombre-Cliente

Apellido-Cliente

Importe

1

 

1 Julia

Herrera

1200

2

 

1 Julia

Herrera

1300

3

 

1 Julia

Herrera

1200

4

 

2 Javier

Alonso

1000

Visto el esquema, vamos a implementar la tabla Pedidos, definiendo la clave ajena a la tabla clientes. Para ello, después de haber añadido las columnas que definen la tabla pedidos (ID, IDCliente, y Cantidad), pulsaremos en el botón habilitado para establecer relaciones, o seleccionaremos la opción "Relationships ":

pulsaremos en el botón habilitado para establecer relaciones, o seleccionaremos la opción "Relationships ": 69

Aparecerá una ventana como la que se muestra a continuación, en la que expandirás la opción "Tables and columns specifications", y pulsarás en el botón encuadrado con bordes azules, para establecer la relación entre las tablas:

azules, para establecer la relación entre las tablas: A continuación, rellenarás los combos que aparecen para

A continuación, rellenarás los combos que aparecen para establecer la relación entre las tablas clientes y pedidos (columnas ID de clientes, e IDCliente de Pedidos):

para establecer la relación entre las tablas clientes y pedidos (columnas ID de clientes, e IDCliente

Para finalizar pulsa en aceptar, y expandes la opción "Insert and Update Expecifications", en la que se podrá especificar cómo establecer el valor de la columna en caso de que la fila padre haya sido borrada o modificada. En SQL Server 2005, se permiten dos opciones:

No Action. Que indica que no actual, que se deja la columna como estaba.

Cascade. Que se realiza la misma operación que se hizo en la fila de la tabla padre.

Set Null. Que establece a nulo el valor de las columnas afectadas.

Set Default. Que establece la columna a un valor por defecto.

Default. Que establece la columna a un valor por defecto. Con un ejemplo se verá más

Con un ejemplo se verá más claro; digamos que se borra el cliente 2 (Javier Alonso); al borrar al cliente, las opciones que acabamos de comentar se activarán, y actuarán según las hayamos configurado; Veamos como se comportaría en cada uno de los casos:

No Action. El pedido se quedaría igual, es decir, el pedido número 4 se quedaría con referencia al cliente 2, que en realidad ya no existe.

Cascade. Como se trataba de una operación de borrado de clientes, al borrar al cliente, también se borrarían los pedidos asociados al cliente 2; en nuestro ejemplo, el pedido número 4, se eliminaría.

Set Null. Al realizar el borrado, el pedido número 4 se quedaría con identificador de cliente a un valor nulo; es decir, el pedido seguiría existiendo, pero no estaría asociado a ningún cliente.

Set Default. Se establecería un valor por defecto; en nuestro caso no hemos definido ningún valor por defecto, pero podríamos establecer un valor por defecto para identificar los pedidos cuyos clientes han sido eliminados.

Todas estas operaciones que hemos realizado desde las herramientas gráficas, también se pueden hacer con sentencias T-SQL; de hecho, llegará un momento en que tu experiencia será tan profunda que te resultará más sencillo realizar gran parte de las operaciones mediante sentencias T-SQL. En concreto, las palabras clave para definir este tipo de sentencias son ALTER TABLE, CREATE TABLE, CONSTRAINT, FOREIGN KEY, PRIMARY KEY.

2.1.4.2.

Otras restricciones (UNIQUE, CHECK, DEFAULT)

Además, existen restricciones que "acotan", limitan, o establecen el valor de una columna en ciertas condiciones. Se llaman restricciones (CONSTRAINTS), y forman parte del estándar SQL-99. Estas restricciones que vamos a ver son: restricción UNIQUE, restricción CHECK, y restricción DEFAULT.

2.1.4.2.1. Restricción UNIQUE

La restricción UNIQUE establece que el valor de cada columna de una fila sea único, Por ejemplo, en la tabla clientes podemos tener una clave primaria como ID, y además tener una columna NIF que establecemos que sea única: lo que internamente implementa SQL Server, es que cada vez que se intente hacer una modificación, o inserción de un valor para esa columna, antes de realizar la operación, se asegura que el nuevo/modificado valor es único en el conjunto de filas de la tabla.

La restricción UNIQUE se implementa en T-SQL con la palabra clave UNIQUE; por ejemplo, si queremos que la columna Nombre de la tabla Clientes sea única, podríamos ejecutar el siguiente código T-SQL:

ALTER TABLE dbo.Clientes

ADD CONSTRAINT RestriccionNombreUnico UNIQUE (Nombre).

2.1.4.2.2. Restricción CHECK

La restricción CHECK establece que el valor de la columna se ajuste a ciertas condiciones. Se define para limitar el valor que pueda tener la columna; por ejemplo, se puede definir que la columna Importe sea de un valor positivo mayor que cero. SQL Server, se encargará de "validar" el valor que tendrá la columna cuando se intente hacer una modificación, o inserción de un valor para esa columna. En caso de que el valor no cumpla la restricción, se generará una excepción y se cancelará la operación en curso.

La restricción CHECK se implementa en T-SQL con la palabra clave CHECK; por ejemplo, si queremos que la columna Cantidad de la tabla Pedidos sea un valor mayor que cero, podríamos ejecutar el siguiente código T-SQL:

ALTER TABLE dbo.Pedidos

ADD CONSTRAINT RestriccionCantidad CHECK (Cantidad>0).

2.1.4.2.3. Restricción DEFAULT

La restricción DEFAULT establece el valor para una columna cuando no se ha especificado valor en la sentencia de inserción. SQL Server, comprobará si la sentencia de inserción establece un valor para la columna, y en caso negativo, establecerá el valor por defecto.

La restricción DEFAULT se implementa en T-SQL con la palabra clave DEFAULT; por ejemplo, si queremos añadir una columna Importe a la tabla Pedidos, y su valor por defecto sea cero, podríamos ejecutar el siguiente código T-SQL:

ALTER TABLE dbo.Pedidos

ADD Importe DEFAULT (0).

2.1.5. Creación de índices

Los índices son "estructuras" alternativa a la organización de los datos en una tabla. El propósito de los índices es acelerar el acceso a los datos mediante operaciones físicas más rápidas y efectivas. Para entender mejor la importancia de un índice pongamos un ejemplo; imagínate que tienes delante las páginas amarillas, y deseas buscar el teléfono de Manuel Salazar que vive en Alicante. Lo que harás será buscar en ese pesado libro la población Alicante, y guiándote por la cabecera de las páginas buscarás los apellidos que empiezan por S

de Salazar. De esa forma localizarás más rápido el apellido Salazar. Pues bien, enhorabuena, has estado usando un índice.

Pues el objetivo de definir índices en SQL Server es exactamente para conseguir el mismo objetivo: acceder más rápido a los datos. Además SQL Server tiene dos tipos de índices que analizaremos a continuación.

2.1.5.1. Índices agrupados

analizaremos a continuación. 2.1.5.1. Índices agrupados Los índices agrupados, definen el orden en que almacenan

Los índices agrupados, definen el orden en que almacenan las filas de la tabla (nodos hoja/página de datos de la imagen anterior). La clave del índice agrupado es el elemento clave para esta ordenación; el índice agrupado se implementa como una estructura de árbol b que ayuda a que la recuperación de las filas a partir de los valores de las claves del índice agrupado sea más rápida. Las páginas de cada nivel del índice, incluidas las páginas de datos del nivel hoja, se vinculan en una lista con vínculos dobles. Además, el desplazamiento de un nivel a otro se produce recorriendo los valores de claves.

2.1.5.1.1.

Columnas selectivas.

Columnas afectadas en consultas de rangos: BETWEEN, mayor que, menor que, etc.

Columnas accedidas "secuencialmente".

Columnas implicadas en JOIN, GROUP BY.

Acceso muy rápido a filas: lookups.

Consideraciones

2.1.5.2.

Índices no-agrupados

2.1.5.2. Índices no-agrupados Los índices no agrupados tienen la misma estructura de árbol b que los

Los índices no agrupados tienen la misma estructura de árbol b que los índices agrupados, con algunos matices; como hemos visto antes, en los índices agrupados, en el último nivel del índice (nivel de hoja) están los datos; en los índices no-agrupados, en el nivel de hoja del índice, hay un puntero a la localización física de la fila correspondiente en el índice agrupado. Además, la ordenación de las filas del índice está construida en base a la(s) columna(s) indexadas, lo cual no quiere decir (a diferencia de los índices agrupados), que la organización física de las páginas de datos corresponda con el índice.

2.1.5.2.1.

Columnas con datos muy selectivos

Consultas que no devuelven muchas filas.

Columnas en WHERE.

Evitar acceso a páginas de datos realizando el acceso sólo por el índice.

Covered queries (consultas cubiertas).

En SQL Server 2005, son nuevos los índices INCLUDE que son índices no-agrupados que en el nivel de hoja del índice (donde está el puntero al índice agrupado), se puede incluir más columnas; el objetivo de este nuevo tipo de índices es beneficiar el uso de las consultar cubiertas para evitar que se acceda a la página de datos del índice agrupado.

Consideraciones

2.2.

Consultas sobre la base de datos

En esta lección, conocerás los conceptos básicos para poder realizar consultas y modificaciones sobre la base de datos. La lección se dividirá en dos partes: cómo realizar consultas de selección, y cómo realizar consultas de modificación.

Durante la primera parte de la lección se introducirán distintas cláusulas que a pesar de estar en la sección de consultas de selección, también serán válidas para sentencias de modificación; por ejemplo, dentro de una operación de borrado se podrán usar cláusulas JOIN, WHERE, TOP, etc.

2.2.1. Consultas de selección

En esta lección veremos mecanismos para recuperar información de la base de datos. Veremos como elegir las columnas de las tablas a recuperar, y cómo aplicar distintos tipos de cláusulas como WHERE, JOIN, GROUP BY, y TOP. Además veremos dos novedades de la versión 2005 de SQL Server: funciones de Ranking, en las que se devuelve la posición relativa de las filas respecto al conjunto total, y Expresiones de tablas comunes que son una nueva funcionalidad para implementar consultas de una forma si cabe más sencilla, y a la ver aporta la posibilidad de realizar consultas recursivas que hasta la versión 2000 no era posible.

2.2.1.1. Selección de columnas y cláusula FROM

Usando la cláusula FROM cuales son las tablas, vistas, funciones, tablas derivadas o expresiones de tablas comunes que se utilizan en la instrucción SELECT.

A su vez, se deberá indicar las columnas a recuperar de la consulta. Si se quiere recuperar todas las columnas se puede usar el comodín * (asterísco), aunque deberemos ser cuidadosos con ello.

Como recomendación, deberás intentar ser lo más selectivo posible en las columnas a incluir en la cláusula SELECT. ¿Por qué razón? ¿Recuerdas la estructura de los índices? Si deseamos todas las columnas de una tabla, estaremos "forzando" a SQL Server a acceder al nivel de datos de las páginas (recuerda: abajo del todo), y estaremos limitando la efectividad de los índices diseñados en las tablas.

El contrapunto de éste comentario es la parte de desarrollo, si generamos código "genérico" que recupera todas las columnas porque las columnas serán necesarias unas veces si y otras no, estaremos penalizando el rendimiento del servidor SQL Server Express pero estaremos ganando tiempo de desarrollo

Para ejecutar una consulta desde SQL Server 2005 Management Studio Express, conectaremos a la base de datos DemoMSDN, desde una de las opciones marcada en la siguiente imagen en recuadro rojo:

A continuación deberás escribir el siguiente texto para rellenar unas cuantas filas en la tabla

A continuación deberás escribir el siguiente texto para rellenar unas cuantas filas en la

tabla clientes (veremos luego la sentencia INSERT):

INSERT Clientes SELECT 'Julia Herrera', 'Alicante'; INSERT Clientes SELECT 'Javier Álvarez', 'Madrid';

Para ello, copia el texto en la ventana de texto, y pulsa F5, o el botón Execute para ejecutar la sentencia.

Borra el texto de la sentencia, y escribe el siguiente texto:

SELECT Id AS Identificador, Nombre FROM Clientes;

Ejecuta la instrucción y verás como resultado todas las filas de la tabla Clientes. Fíjate que

la columna de base de datos Id, ahora parece que se llama Identificador. Esto es un alias

de columna; habrá ocasiones en las que necesites personalizar el nombre de columna que

se muestra.

Desde Management Studio, también se puede ver la información de la tabla de forma similar a como se presenta en Access; para ello, deberás seleccionar la tabla que quieres editar, botón derecho del ratón, y elegir la opción "Open Table" como aparece en la siguiente imagen:

del ratón, y elegir la opción "Open Table" como aparece en la siguiente imagen: Y como

Y como resultado tendremos:

del ratón, y elegir la opción "Open Table" como aparece en la siguiente imagen: Y como

Deberás tener cuidado con abrir tablas muy grandes, porque el proceso de carga es más costoso cuanto mayor sea el tamaño de la tabla. Sin embargo, en la versión 2005 de las herramientas administrativas, tenemos la posibilidad de cancelar consultas mientras se está realizando la petición; fíjate en el botón marcado en color rojo en la siguiente imagen; pulsando dicho botón se solicita al servidor que se cancele la consulta.

botón se solicita al servidor que se cancele la consulta. 2.2.1.2. Cláusula WHERE La cláusula WHERE

2.2.1.2. Cláusula WHERE

La cláusula WHERE se utiliza para aplicar filtros al conjunto de resultados; para ello existen operadores lógicos AND, OR, NOT, EXISTS con los condicionantes <, >, =, y BETWEEN. No hay límite para el número de condiciones a establecer. El orden de prioridad de los operadores lógicos es NOT, seguido de AND y OR. Se pueden utilizar paréntesis para suplantar esta prioridad en una condición de búsqueda. El orden de evaluación de los operadores lógicos puede variar dependiendo de las opciones elegidas por el optimizador de consultas.

Para más información sobre los operadores lógicos, debes consulta la ayuda on-line del producto:

http://msdn2.microsoft.com/es-es/library/ms203721(sql.90).aspx

2.2.1.3. Cláusula JOIN

Cuando se necesita recuperar información de más de una tabla, se suele especificar cuales son las filas coincidentes entre ambas tablas (columnas Clave Ajena / Clave Primaria que vimos en la lección anterior). Para ello, hay una serie de operadores que condicionan dicho filtro.

2.2.1.3.1. INNER JOIN

Especifica que se devuelvan todos los pares de filas coincidentes. Las filas no coincidentes se descartan del resultado. Si no se especifica ningún tipo de combinación, éste es el tipo por defecto.

2.2.1.3.2. FULL [ OUTER ] JOIN

Especifica que una fila de la tabla de la derecha o de la izquierda, que no cumpla la condición de combinación, se incluya en el conjunto de resultados y que las columnas que correspondan a la otra tabla se establezcan en NULL.

2.2.1.3.3. LEFT [ OUTER ] JOIN

Especifica que todas las filas de la tabla izquierda que no cumplan la condición de combinación se incluyan en el conjunto de resultados, con las columnas de resultados de la otra tabla establecidas en NULL, además de todas las filas devueltas por la combinación interna.

2.2.1.3.4. RIGHT [OUTER] JOIN

Especifica que todas las filas de la tabla derecha que no cumplan la condición de combinación se incluyan en el conjunto de resultados, con las columnas de resultados de la otra tabla establecidas en NULL, además de todas las filas devueltas por la combinación interna.

2.2.1.3.5. ON <search_condition>

Especifica la condición en la que se basa la combinación; se puede referenciar más de una columna, por ejemplo, tablas con claves primarias compuestas.

Un ejemplo de consulta podría ser el siguiente:

SELECT sh.SalesOrderID, sh.OrderDate, sh.CustomerID, sd.OrderQty, sd.ProductID, sd.UnitPrice FROM Sales.SalesOrderHeader sh INNER JOIN Sales.SalesOrderDetail sd ON sh.SalesOrderID = sd.SalesOrderID

2.2.1.4. Cláusula GROUP BY

El lenguaje T-SQL permite devolver la información agregada usando la cláusula GROUP BY; los condicionantes de la agregación se colocan después de la cláusula. Existen las siguientes funciones de agregado:

AVG, MIN, CHECKSUM, SUM, CHECKSUM_AGG, STDEV, COUNT, STDEVP, COUNT_BIG, VAR, GROUPING, VARP, MAX.

Por ejemplo, la siguiente sentencia T-SQL devolvería la cantidad de productos pedidos por cliente en la base de datos MSDN:

SELECT Id, COUNT(*) AS Cantidad FROM Pedido GROUP BY Id

En SQL Server 2005, se pueden diseñar funciones de agregado personalizadas con lenguajes .NET como Visual Basic o C#; para ello deberá implementar el método de agregación siguiendo unas "reglas" que define SQL Server (interfaces, método de acumulación, funciones varias, etc.), compilarlo, incluirlo en la base de datos con la sentencia (CREATE ASSEMBLY), y hacerlo referencia con la función T-SQL CREATE AGGREGATE.

2.2.1.5. Cláusula TOP (n) [PERCENT]

Cuando se necesita recuperar los n primeros elementos que cumplen una condición, se puede utilizar la función TOP. La función TOP tiene un argumento que puede representar un número o un porcentaje.

La única forma de garantizar que la sentencia TOP devuelva los n primeros elementos que cumplen una condición es usando la cláusula ORDER BY.

Además, SQL Server 2005, incorpora nuevas funciones de RANKING que devuelven posiciones relativas de las filas; las funciones son RANK, DENSERANK, TILE , y NTILE.

2.2.1.6. Expresiones de tablas comunes (CTE)

Las expresiones de tablas comunes son una de las novedades en el lenguaje T-SQL en la versión 2005; representan un conjunto temporal de datos, al que se puede hacer referencia varias veces en la sentencia en la que está incluida. Su ámbito es la sentencia en la que se ejecuta, y puede utilizarse en sentencias SELECT, INSERT, UPDATE, y DELETE. Además, también puede ser incluida en procedimientos almacenados, y en la definición de vistas.

Su sintaxis es:

[ WITH <common_table_expression> [ , <common_table_expression> ::= expression_name [ ( column_name [ , AS ( CTE_query_definition )

n

n

]

]

)

]

]

Aprovechando la consulta anterior, un ejemplo de CTE podría ser el siguiente:

WITH PedidosAgrupados (Id, Cantidad) AS (SELECT Id, COUNT(*) AS Cantidad FROM Pedidos GROUP BY Id) SELECT Id, Nombre FROM Clientes INNER JOIN PedidosAgrupados ON Clientes.Id = PedidosAgrupados.I

2.2.2. Consultas de modificación

2.2.2.1. Sentencia INSERT

La sentencia INSERT, inserta filas en una tabla; tiene dos formatos:

INSERT INTO <tabla> VALUES ( <lista de columnas> )

En la que se insertan la lista de valores en una tabla; corresponde a una inserción de una fila en la tabla.

INSERT INTO <tabla> <sentencia select>

Donde la sentencia select será un conjunto de filas que se insertará en la tabla destino.

Cuando una de las columnas de la tabla tenga establecida la propiedad IDENTITY, no será necesario especifica el valor de la columna porque SQL Server automáticamente lo hará por nosotros. En realidad, si se intenta insertar un valor, se generará una excepción porque la propiedad IDENTITY, no admite especificar valores para las columnas a menos que se use la opción SET IDENTITY_INSERT.

La sentencia INSERT se puede utilizar en combinación con las expresiones de tablas comunes; adicionalmente, existe una opción (OUTPUT), que devuelve Cuando una de las columnas de la tabla tenga establecida la propiedad IDENTITY, no será necesario especifica el valor de la columna porque SQL Server automáticamente lo hará por nosotros. En realidad, si se intenta insertar un valor, se generará una excepción porque la propiedad IDENTITY, no admite especificar valores para las columnas a menos que se use la opción SET IDENTITY_INSERT.

2.2.2.2.

Sentencia UPDATE, y DELETE

La sentencia UPDATE/DELETE, actualizan/borran filas de una tabla. Normalmente se le

aplican filtros a la sentencia para que filtre las filas que se van a modificar. Se puede hacer

la operación UPDATE/DELETE de dos formas, que explicaremos con un ejemplo:

DELETE Sales.SalesOrderDetail WHERE SalesOrderID = 3443

En el que se borran las filas de la tabla Sales.SalesOrderDetail cuyo identificador es el

3443.

La otra posibilidad es hacer la operación con un JOIN (no todos los gestores de bases de datos lo permiten); el ejemplo sería el siguiente:

UPDATE t SET AcumuladoImporte = t2.SumaImporte FROM TablaAcumulados t INNER JOIN (SELECT Pedidos.Id, SUM (LineasPedido.Importe) AS SumaImporte FROM Pedidos JOIN LineasPedido ON Pedidos.Id = LineasPedido.Id Pedidos.Fecha BETWEEN '20050101' AND '20060101' GROUP BY Pedidos.Id) t2 ON t.Id = t2.Id WHERE Fecha BETWEEN '20050101' AND '20060101'

Para leer esta consulta es mejor que empieces por el final; mira la consulta que representa

el alias t2: obtiene los en importe total de cada pedido del año 2005.

A continuación, ese resultado se va a cruzar (JOIN) con la tabla TablaAcumulados para

actualizar la columna AcumuladoPedidos, reemplazándolo por el valor de SumaImporte de la consulta anterior. Antes de aplicarlo, deberá acotar la actualización a los pedidos realizados en el año 2005.

Las sentencias DELETE, y UPDATE también pueden usar la opción (OUTPUT), para devolver las filas afectadas por la operación. Puedes consultar los libros en pantalla on-line del producto en:

http://msdn2.microsoft.com/es-es/library/ms203721(sql.90).aspx

3.

Programación de una base de datos

3.1. Procedimientos almacenados

Los procedimientos almacenados (stored procedures) no son mas que una sucesión ordenada de instrucciones T-SQL que pueden recibir y devolver parámetros provistos por el usuario y se pueden guardar en el servidor con un nombre, para luego poder invocarlos y ejecutarlos. En esta nueva versión (2005), también es posible utiliza procedimientos almacenados usando CLR. Un procedimiento almacenado CLR es una referencia a un método de un ensamble de .NET Framework que puede aceptar y devolver parámetros suministrados por el usuario.

3.1.1. Algunas ventajas de usar procedimientos almacenados

3.1.1.1. Compilación

La primera vez que se invoca un procedimiento almacenado, el motor lo compila y a partir de ahí, se sigue usando la versión compilada del mismo, hasta que se modifique o se reinicie el servicio de SQL. Esto hace que tengan un mejor rendimiento que las consultas directas que usan cadenas con instrucciones T-SQL, que el motor compila cada vez que se invoca.

3.1.1.2. Automatización

Si tenemos un conjunto de instrucciones T-SQL que queremos ejecutar en un orden, los stored procedures son el espacio ideal para hacerlo.

3.1.1.3. Administración

Si hacemos buen uso de los procedimientos almacenados, muchas veces algún cambio en nuestra aplicación, solo implica modificar un stored procedure y no toda la aplicación. Si nuestra aplicación llama a los stored procedures, con solo cambiarlo en el servidor, ya tenemos todo funcionado sin la necesidad de actualizar la aplicación en todos los equipos cliente.

3.1.1.4. Seguridad

Otra ventaja que tienen es que permiten aplicar un esquema de seguridad mas potente, haciendo que los usuarios que usen nuestra aplicación, solo tengan permisos para ejecutar procedimientos almacenados y no a todos los objetos de la base. De esta forma si un hacker encuentra una vulnerabilidad (SQL Injection) en nuestra aplicación, no podrá explotarla ejecutando comandos SQL directamente sobre la base, ya que el usuario con el cual se ejecuta la aplicación solo tiene derecho a la ejecución de los procedimientos almacenados en la base de datos.

3.1.1.5. Programabilidad

Los procedimientos almacenados admiten el uso de variables y estructuras de control como

IF, Bucles, Case, etc

procedures sean aplicables para escribir lógica del negocio en los mismos. Depende el modelo de programación que se utilice esto puede ser tanto una buena practica como una mala practica, pero la posibilidad de incluir las reglas del negocio en los mismos existe.

Como explicábamos mas arriba a partir de esta versión de SQL (2005), podemos utilizar CLR esto es (cualquier leguaje .NET como C# o Visual Basic) para escribir los stored procedures (además del tradicional T-SQL).

3.1.1.6. Trafico de Red

Pueden reducir el tráfico de la red, debido a que se trabaja sobre el motor (en el servidor), y si una operación incluye hacer un trabajo de lectura primero y en base a eso realizar algunas operaciones, esos datos que se obtienen no viajan por la red.

además de el manejo de transacciones, que permiten que los stored

3.1.2.

Crear procedimientos almacenados

Para crear un procedimiento almacenado es necesario ejecutar la instrucción CREATE PROCEDURE, como se muestra en el ejemplo:

CREATE PROCEDURE Clientes_GetAll AS SELECT Id, Nombre FROM dbo.Clientes

Si el procedimiento almacenado ya existe se debe usar la instrucción ALTER PROCEDURE, de la misma manera que si se estuviera creando. En el ejemplo, el stored procedure devuelve todos los registros de la tabla Clientes.

La creación de procedimientos almacenados basados en .NET Framework lo veremos en la lección 4 del módulo 4 (Conceptos avanzados -- Integración del CLR).

3.1.3. Ejecutar procedimientos almacenados

La ejecución de los procedimientos almacenados es muy simple. Desde T-SQL hay que utilizar la instrucción EXEC, para ejecutar los dos procedimientos almacenados que creamos arriba debería escribirse esto:

EXEC Clientes_GetAll

¿Y cómo se haría la llamada desde una aplicación .NET?; para ello deberemos definir un objeto conexión que conecta a la base de datos, y un objeto command que ejecute el comando:

'Creamos una nueva conexión. Dim myConn As SqlClient.SqlConnection = New SqlClient.SqlConnection("Data Source=(localhost)\SQLEXPRESS;Initial Catalog=DemoMSDN;Integrated Security=True")

'Creamos un nuevo comando Dim myComm As SqlClient.SqlCommand = New SqlClient.SqlCommand()

'Le asignamos la conexion. myComm.Connection = myConn

'especificamos que el comando es un stored procedure myComm.CommandType = CommandType.StoredProcedure

'y escribimos el nombre del stored procedure a invocar myComm.CommandText = "Clientes_GetAll"

'Creamos un nuevo DataAdapter con nuestro comando. Dim myDA As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter(myComm)

'Creamos un dataset para soportar los datos devueltos por el stored procedure Dim ClientesDs As DataSet = New DataSet

'Pedimos al Data Adapter que llene el dataset (Esto llama a nuestro comando) myDA.Fill(ClientesDs)

'Y lo mostramos por pantalla For Each row As Data.DataRow In ClientesDs.Tables(0).Rows Console.WriteLine(row!Id.ToString() + " " + row!Nombre)

Next

3.1.4.

Argumentos en procedimientos almacenados

Los procedimientos almacenados pueden recibir y devolver datos a quien lo llame. Supongamos que ahora necesitamos obtener una sola categoría y no todas como el ejemplo anterior. Podemos crear un nuevo stored procedure llamado Clientes_GetOne que reciba como parámetro el Id y devuelva solo ese registro.

CREATE PROCEDURE Clientes_GetOne @Id int AS SELECT Id, Nombre FROM dbo.Clientes WHERE Id = @Id

Como se puede ver, ahora este stored procedure espera un parámetro que luego se usa para hacer la consulta en el select.

Para llamar a este store procedure se usa la misma sintaxis pero solo agregando el valor del parámetro. (En este caso, queremos obtener el registro cuyo Id = 4).

EXEC Clientes_GetOne 4

3.1.4.1. Desde ADO.NET

'Creamos una nueva conexión. Dim myConn As SqlClient.SqlConnection = New SqlClient.SqlConnection("Data Source=(localhost)\SQLEXPRESS;Initial Catalog=DemoMSDN;Integrated Security=True")

'Creamos un nuevo comando Dim myComm As SqlClient.SqlCommand = New SqlClient.SqlCommand()

'Le asignamos la conexion. myComm.Connection = myConn

'especificamos que el comando es un stored procedure myComm.CommandType = CommandType.StoredProcedure

'y escribimos el nombre del stored procedure a invocar myComm.CommandText = "Clientes_GetOne"

'Creamos un nuevo parámetro Dim myParam As SqlClient.SqlParameter = New SqlClient.SqlParameter() myParam.ParameterName = "@Id" myParam.SqlDbType = SqlDbType.Int myParam.Value = 4

'Y se lo agregamos a la coleccion de parametros del comando myComm.Parameters.Add(myParam)

'Creamos un nuevo DataAdapter con nuestro comando. Dim myDA As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter(myComm)

'Creamos un dataset para soportar los datos devueltos por el stored procedure Dim ClientesDs As DataSet = New DataSet

'Pedimos al Data Adapter que llene el dataset (Esto llama a nuestro comando) myDA.Fill(ClientesDs)

'Y lo mostramos por pantalla For Each row As Data.DataRow In ClientesDs.Tables(0).Rows Console.WriteLine(row!Id.ToString() + " " + row!Nombre)

Next

3.2.

Triggers

Los TRIGGERS o disparadores son muy similares en su concepto a los procedimientos almacenados, es decir son piezas de código Transact-SQL, sin embargo son radicalmente distintas en la ejecución, mientras un procedimiento almacenado se ejecuta por la petición de un cliente, un TRIGGER responde a un evento, ya sea de manipulación de datos como los TRIGGERS DML o por la manipulación de esquemas como los TRIGGERS DDL.

Los TRIGGERS son un tipo de objetos muy especiales en SQL Server 2005, ya que realmente son muy parecidos a lo que las rutinas de atención a eventos en código en cualquier lenguaje de programación como por ejemplo .NET. Como objetos especiales que son también reciben parámetros de una forma muy especial, en forma de unas tablas virtuales, llamadas inserted y deleted. Estas tablas especiales (inserted y deleted) contienen la información de los registros que se han eliminado o insertado, con exactamente las mismas columnas que la tabla base que está sufriendo esa modificación. Las tablas inserted y deleted estarán o no rellenas de datos en función de cual sea el tipo de operación que ha dado lugar a su ejecución. Por ejemplo, un TRIGGER que se dispare por la inserción en una tabla tendrá tantos registros en la tabla virtual inserted como registros estén siendo insertados y cero registros en la tabla deleted; un TRIGGER que se dispare por la eliminación de registros en una tabla tendrá cero registros en la tabla inserted y tantos registros en la tabla deleted como registros estén siendo eliminados, y un TRIGGER que responda a una operación de update, tendrá el mismo número de registros en la tabla inserted y en la tabla deleted que además coincidirá con el número de registros actualizados en la tabla que da lugar al evento.

Uno de los errores más comunes cuando se desarrollan TRIGGERS está relacionado con esta arquitectura que acabamos de describir, un TRIGGER no se dispara una vez para cada fila modificada, sino que se dispara una sola vez por cada operación, independientemente del número de registros afectados por la operación, y todos los registros afectados están contenidos en las tablas inserted y deleted dentro del TRIGGER. Muchos desarrolladores parten de la premisa de que las tablas inserted y deleted contendrán solamente un registro a lo sumo y como acabamos de explicar esto no es cierto. El segundo error más común tiene que ver este primero y con el rendimiento, muchos desarrolladores para solventar este problema crean cursores dentro de los TRIGGERS, los cursores como tal están fuera del alcance de este capítulo, pero en general un cursor dentro de un TRIGGER es casi un garantía de obtener problemas de rendimiento, por lo que deberíamos intentar evitarlos lo más posible

Los TRIGGERS son usados para añadir lógica o restricciones a la base de datos, por ejemplo pueden ser usados para establecer reglas de integridad con bases de datos externas (no grabar un pedido en la base de datos de pedidos si el cliente indicado no está dado de alta en la base de datos de pedidos por ejemplo); también son usados para mantener tablas de acumulados como por ejemplo la tabla que mantienen el stock de una determinada compañía o para guardar el acumulado de ventas en la ficha de un cliente.

DML es el acrónimo de Data Manipulation Language (Lenguaje de Manipulación de datos), los TRIGGERS DML son como indica su nombre aquellos que responden a operaciones de manipulación de datos, tales como sentencias INSERT, UPDATE o DELETE mientras que DDL es el acrónimo de Data Definition Language (Lenguaje de definición de datos) los TRIGGERS DDL se dispararán por operaciones que impliquen cambios en los esquemas (en la definición de los objetos).

3.2.1. Triggers DML: INSTEAD OF

Los TRIGGERS de tipo Instead OF son TRIGGERS que se disparan en lugar de la operación que los produce, es decir, una operación de borrado de registros con la instrucción delete sobre

una tabla que tiene un TRIGGER de tipo INSTEAD OF no se llega a realizar realmente, sino que SQL Server 2005 cuando detecta esta operación invoca al TRIGGER que es el responsable de actuar sobre los registros afectados, en el ejemplo que estamos siguiendo, el TRIGGER sería el responsable de borrar los registros de la tabla que ha disparado el evento. Si el TRIGGER no se encarga de esta tarea, el usuario tendrá la sensación de que SQL Server no hace caso a sus comandos ya que por ejemplo una instrucción DELETE no borrará los registros.

Como ejemplo de TRIGGER de tipo INSTEAD OF vamos a ver como se implementaría la siguiente regla: ”no se pueden borrar los clientes cuyo Crédito Total sea mayor que cero, sin embargo si dentro de una operación de borrado hay clientes con Riesgo Total cero y otros con Riesgo Total distinto de cero, los que tengan cero si deben resultar eliminados”. El TRIGGER que implementaría esa regla sería el siguiente.

CREATE TRIGGER TR_BorradoSelectivo on Clientes INSTEAD OF DELETE AS BEGIN

DELETE c FROM Clientes C INNER JOIN DELETED d ON C.idCliente = D.idCliente WHERE C.CreditoTotal = 0

END

Repasando el código podemos ve que estamos utilizando una sintaxis de JOIN para un

DELETE, esta sintaxis es perfectamente válida y lo que hará será borrar todos los registros de

C (Clientes) que existan también en D (DELETED) uniendo estas tablas por el código de cliente

en donde el Crédito Total sea 0. El resto de los registros solicitados para borrar (es decir todos los que su campo crédito total no sea cero) simplemente no se borrarán, ya que como hemos mencionado los triggers de tipo INSTEAD OF delegan la responsabilidad de la operación en el cuerpo del TRIGGER.

En algunos escenarios los TRIGGERS de tipo INSTEAD OF pueden usarse como mecanismo para evitar operaciones no controladas en tablas, por ejemplo si queremos prevenir que se borren datos de una tabla, basta con crear un INSTEAD OF TRIGGER y no poner ningún código en su cuerpo, de esta forma, a menos que el usuario deshabilite el TRIGGER, no podrá borrar ningún dato, sin embargo sea cuidadoso y documente bien este tipo de operaciones ya que pueden dar la falsa sensación de que la base de datos no se está comportando adecuadamente.

3.2.2. Triggers DML: AFTER

Todos los TRIGGERS sirven en general para implementar restricciones de negocio avanzadas, como ejemplo vamos a ver como se construiría un TRIGGER que impidiese que se aumentase

el Crédito total de un cliente que tenga pagos pendientes, para ello vamos a suponer una tabla

de clientes con identificador idCliente y con un campo llamado CreditoTotal y una tabla de recibos conteniendo el idCliente y el estado del recibo (estos son solamente los campos que

son relevantes para nuestro ejemplo).

CREATE TRIGGER TR_CompruebaCreditoTotal ON Clientes AFTER UPDATE AS BEGIN

IF UPDATE(CreditoTotal) -- Se está actualizando el campo crédito total, comprobemos -- las restricciones.

BEGIN

IF EXISTS(SELECT IdCliente FROM RECIBOS WHERE Estado=’PEN’ AND idCliente IN (SELECT idCliente FROM DELETED))

BEGIN

ROLLBACK

-- Deshacemos la transacción impidiendo que se actualize RAISERROR (‘No se pueden actualizar el crédito de clientes con recibos pendientes’,16,1)

END

END

END

Veamos algunas particularidades sobre el código del TRIGGER, la cláusula UPDATE, permite comprobar si se está actualizando una columna en particular, de esta forma nuestro TRIGGER resultará inocuo para el resto de operaciones de actualización. En la segunda parte se comprueba que el cliente tenga recibos pendientes con una cláusula EXISTS y con una cláusula in. La cláusula IN permite comprobar que el código de cliente esté en la lista de códigos de cliente borrados (todos ellos), la cláusula EXISTS permite comprobar si existen recibos pendientes en esos clientes.

Algunos administradores de base de datos prefieren implementar estas reglas de negocio en procedimientos almacenados e impedir el acceso de los usuarios directamente a la modificación de tablas. Ambas alternativas son perfectamente válidas y el usar unas u otras dependerá de las políticas que se establecen en el desarrollo. Lo que en cualquier caso suele ser buena idea es manipular las restricciones de los datos en la base de datos, por que esta es la única forma de garantizar totalmente la consistencia de la información.

3.2.3. Triggers DDL: a nivel de base de datos

Los TRIGGERS DML (Data Manipulation Language) responden a la necesidad de garantizar la integridad y consistencia de los datos dentro de nuestras tablas de usuario, sin embargo no ayudan a mantener las reglas de diseño de nuestra base de datos. El nombre coincide pero el propósito es distinto, los triggers DDL (Data Definition Language) nos proporcionarán mecanismos para garantizar que nuestra base de datos está diseñada e implementada de acuerdo a los estándares que hayamos definido.

Los TRIGGERS DDL tienen dos alcances diferenciados, a nivel de servidor y a nivel de base de datos. Estos alcances están enlazados con el tipo de evento que los dispare, en esta primera parte los eventos que vamos a ver son a nivel de Base de datos y algunos ejemplos de ellos son:

CREATE / ALTER /DROP View

CREATE / ALTER /DROP Table

CREATE / ALTER /DROP Schema

Aunque hay muchos más, relacionados con estadísticas, sinónimos, usuarios (no confundir con logins que son a nivel de servidor), procedimientos, etc. Puede consultar los libros en pantalla de SQL Server 2005 para obtener una relación completa de todos los eventos a los que puede responder.

Los TRIGGERS DDL tienen una particularidad adicional sobre los de tipo DML y es que no tiene mucho sentido las tablas inserted y deleted ya que el tipo de operaciones que disparan los triggers son radicalmente diferentes. Sin embargo, como ellos necesitan recibir información acerca del evento que ha ocasionado que el trigger se dispare, para ello existe la función EVENTDATA(), esta función devuelve un valor XML que responde al siguiente esquema:

<EVENT_INSTANCE> <EventType>type</EventType> <PostTime>date-time</PostTime> <SPID>spid</ SPID> <ServerName>name</ServerName> <LoginName>name</LoginName> <UserName>name</UserName>

<DatabaseName>name</DatabaseName>

<SchemaName>name</SchemaName>

<ObjectName>name</ObjectName>

<ObjectType>type</ObjectType>

<TSQLCommand>command</TSQLCommand>

</EVENT_INSTANCE>

Utilizando las funciones XML para manipular esa función podemos obtener toda la información necesaria para crear TRIGGERS que garanticen que en nuestras bases de datos se siguen los estándares marcados. Supongamos que uno de esos estándares indica que todas las tablas deben estar documentadas, su propósito, la fecha de creación etc en una tabla llamada “TablasDocumentadas”, podríamos crear un TRIGGER DDL a nivel de base de datos que nos garantizase que antes de crear la tabla, ésta, ya ha sido documentada. El código que lo hace es el siguiente:

CREATE TRIGGER TablasDocumentadas ON DATABASE FOR CREATE_TABLE AS BEGIN

DECLARE @TabName Sysname SELECT

@TabName=EventData().value(‘(/EVENT_INSTANCE/ObjectName)[1]’,’sys

name’)

IF NOT EXISTS(SELECT * FROM TablasDocumentadas WHERE TableName = @TabName)

BEGIN

ROLLBACK -- Deshacemos la transacción impidiendo que se actualize RAISERROR (‘No se pueden crear tablas indocumentadas en nuestro sistema’,16,1)

END

END

Si lo miramos en detalle tan solo estamos obteniendo el nombre del objeto que se ha insertado (en nuestro caso la tabla que se acaba de crear) y estamos comprobando que existe un registro con ese nombre en la tabla TablasDocumentadas, sin embargo este procedimiento podría tener mucha más complejidad y garantizar no solamente que existe sino que los datos que contiene esa tabla son de cierta calidad.

3.2.4. Triggers DDL: a nivel de servidor

Los triggers DDL a nivel de servidor son muy similares a los triggers a nivel de base de datos en su concepción, pero responden a los eventos que son propios del servidor y no a los que están en el alcance de base de datos. A esta categoría de eventos pertenecen entre otros los de CREATE LOGIN o CREATE/ALTER/DROP Database y los relativos por ejemplo a los nuevos ENDPOINTS. Las utilidades de este tipo de TRIGGERS también están muy alineadas con las de los que son a nivel de base de datos e incluso la información

Como ejemplo de uso vamos a crear un TRIGGER que se encarge de garantizar que todos los LOGINS comienzan con tres letras y un guión bajo, esto podría servir para organizarlos por aplicaciones o por políticas de seguridad o por cualquier otro concepto.

CREATE TRIGGER LoginsConTresLetrasYGuionBajo ON ALL SERVER FOR DDL_LOGIN_EVENTS AS BEGIN

DECLARE @ObjName Sysname

SELECT

@ObjName=EventData().value(‘(/EVENT_INSTANCE/ObjectName)[1]’,’sys

name’) IF NOT @ObjName LIKE ‘[A-Z][A-Z][A-Z][_]%’ BEGIN

ROLLBACK RAISERROR (‘Todos los logins deben comenzar por tres letras y un guión bajo’,16,1)

END

END

La cláusula ON en la instrucción CREATE TRIGGER indica el alcance al que el trigger afectará, ALL SERVER o bien DATABASE, el comando que se sitúa detrás de FOR, en el ejemplo DDL_LOGIN_EVENTS puede indicar un evento o un grupo de eventos a los que se responderá, en nuestro caso es un grupo de eventos, en concreto todos los relacionados con LOGIN.

3.3. Funciones definidas de usuario

Una herramienta adicional dentro de la programación de base de datos son las funciones definidas por el usuario. Estas funciones, que pueden ser de tres tipos, reciben parámetros como los procedimientos almacenados, y además pueden ser usadas como valores escalares o como tablas dentro de cláusulas FROM, lo que las hace tremendamente útiles en determinadas circunstancias.

3.3.1. Funciones escalares

Las funciones escalares son aquellas que devuelven un único valor en función de los parámetros que reciben. Pueden servir en múltiples circunstancias haciendo de la programación una experiencia más agradable. Un ejemplo de función escalar podría ser la siguiente:

CREATE FUNCTION Rangos (@id tinyint) RETURNS nvarchar(10) AS

BEGIN DECLARE @Valor nvarchar(10) SELECT @Valor = CASE WHEN @id<20 THEN '1-19' WHEN @id>=20 AND @id<40 THEN '20-39' WHEN @id>=40 AND @id<60 THEN '40-59' WHEN @id>=60 AND @id<80 THEN '60-79' WHEN @id>=80 AND @id<100 THEN '80-99' ELSE '>100' END RETURN @Valor END

Esta función devolverá un string que contiene el rango al que pertenece el número que recibe como parámetro.

Desde las funciones se puede acceder a tablas también, pero siempre cuando se hace hay que ser muy cuidadoso para no implementar lo que se llaman “lookups” dentro de las udfs por que eso puede ser muy peligroso para el rendimiento ya que por cada fila devuelta se ejecutará una sentencia sin que el optimizador de consultas pueda hacer nada para mejorar la forma de ejecutarse.

3.3.2. Funciones de tabla en línea

Las funciones de tabla en línea son muy parecidas a las vistas, con la excepción de que admiten parámetros de tal forma que pueden resultar tremendamente útiles en determinadas circunstancias. Veamos un ejemplo:

CREATE FUNCTION ClientesPorTipo (@Tipo tinyint) RETURNS TABLE

AS

RETURN (SELECT * FROM Clientes WHERE Tipo = @tipo)

Esta función puede ser usada en sentencias más complejas, un ejemplo de uso podría ser el siguiente:

SELECT * FROM ClientesPorTipo(1) Cl INNER JOIN Facturas F ON CL.idcliente = F.IdCliente

De esta forma podemos parametrizar consultas que vamos a usar en cláusulas FROM, de esta forma escribir código T-SQL resulta más sencillo y el resultado es más comprensible. Si además combinamos esta funcionalidad con las nuevas funciones APPLY podemos obtener funcionalidades aún más interesantes, ya que podemos conseguir que las funciones en línea o de tipo tabla reciban los parámetros desde los valores de otra tabla, la sintaxis sería algo así:

SELECT * FROM ClientesSeleccionados C CROSS APPLY ClientesPorTipo(C.tipo)

Esta consulta devolverá todos los registros de la tabla Clientes Seleccionados y los regritros de la vista ClientesPortipo para cada Tipo de la tabla primaria.

3.3.3. Funciones de tabla multi-sentencia

Sin embargo, algunas veces necesitamos cierta manipulación de la información dentro de las funciones para poder devolver los datos tal y como necesitamos, esto puede hacerse también dentro de las funciones definidas por el usuario en las llamadas funciones de tabla o funciones de tabla multi-sentencia. Básicamente se comportan igual que un procedimiento almacenado pero devuelven una tabla que puede ser usada en cláusulas FROM o ser usadas como veíamos anteriormente combinadas con funciones APPLY.

Un ejemplo de función de tabla multisentencia puede ser el siguiente:

CREATE FUNCTION ClientesPorTipoConSaldos (@Tipo tinyint) RETURNS @T TABLE (idCliente Int, NombreCliente nvarchar(100), Saldo money) AS

BEGIN

INSERT INTO @t (idCliente, NombreCliente) SELECT idCliente,Nombre FROM Clientes WHERE Tipo = @Tipo UPDATE T SET Saldo = Total FROM @T T INNER JOIN (SELECT idCliente, SUM(Saldo) Total FROM Recibos GROUP BY idCliente) S ON T.idCliente = S.idCliente RETURN

END

Esta función se encarga de devolver todos los clientes por tipo, pero además les aporta el Saldo en su cuenta sumando los recibos. Toda esta funcionalidad puede ponerse en una sola sentencia, pero de esta forma queda mucho más clara para el ejemplo. Cualquier manipulación sobre la variable de tipo tabla @t podría ser hecha dentro del cuerpo de la función que se comportaría igual que un procedimiento almacenado, estando precompilada y pudiendo reaprovechar los planes de la ejecución en determinadas circunstancias.

Nota: Las funciones definidas por el usuario son una magnífica herramienta de programación, pero no son útiles para todas las circunstancias, simplemente cada vez que necesite realizar una implementación piense en que recurso es el más adecuado para resolver la problemática que se le presenta y úselo.

4.

Conceptos avanzados

En este módulo se verán las novedades relativas a seguridad en SQL Server 2005 Express; además daremos un repaso por los niveles de aislamiento, y conoceremos en qué consiste el nuevo nivel de aislamiento de instantánea introducido en esta nueva versión. Para finalizar el curso, veremos dos novedades importantes: el soporte nativo de XML, y la integración del CLR en SQL Server. ¿En qué consiste la integración del CLR? Pues básicamente que desde la versión 2005, se pueden crear objetos tales como procedimientos almacenados, funciones, etc. con un lenguaje orientado a objetos como VB.NET, o C#.

4.1.

Seguridad

La Arquitectura de seguridad es uno de los aspectos que más cambios ha sufrido y en el que más mejoras se han introducido en SQL Server 2005. En el presente apartado veremos los aspectos básicos de seguridad que será necesario tener en cuenta a la hora de acceder a una base de datos SQL Server Express.

4.1.1. Seleccionar un Modo de Autenticación

SQL Server 2005 soporta dos modos de autenticación, tal y como vimos en el apartado de instalación: Autenticación Windows y Autenticación Mixta. Cuando trabajamos en modo Autenticación Windows, que es el predeterminado y aconsejado, solo los usuarios autorizados de sistema operativo podrán conectarse al servidor SQL Server. En el modo de Autenticación Windows, podemos proporcionar acceso tanto a usuarios, como a grupos de sistema operativo.

Para cambiar el Modo de Autenticación, una vez instalada la instancia de SQL Server Express, lo más sencillo es utilizar la herramienta SQL Server Management Studio Express. Para ello debes de seguir los siguientes pasos como muestra la figura:

1. Inicia SQL Server Management Studio Express desde el Menú Inicio.

2. Desde el Object Explorer, haz clic con el botón de la derecha en la instancia y selecciona Propiedades.

3. Desde la pestaña de Security podrás modificar el Modo de Autenticación.

Nota: Para que este cambio surta efecto deberás de reiniciar el servicio de SQL Server.

Nota: Para que este cambio surta efecto deberás de reiniciar el servicio de SQL Server.

Para decidir que Modo de Autenticación es el más conveniente, deberás de tener en cuenta:

El Modo de Autenticación Windows es el más seguro y el recomendado. En este modo de Autenticación, SQL Server confía en la autenticación realizada por el Sistema Operativo. En el Modo de Autenticación Windows no viajan contraseñas por la red, ni será necesario especificarlas en una cadena de conexión.

El Modo de Autenticación Mixto deberemos de utilizarlo únicamente en aquellos casos en los que, por la estructura de nuestra red, no podamos utilizar autenticación Windows.

4.1.2. Acceso a una Base de Datos SQL Server

SQL Server utiliza una autenticación en dos pasos: En primer lugar, la cadena de conexión especifica una autenticación a nivel de Instancia de SQL Server Express. Una vez se ha autenticado el inicio de sesión, se comprueba si ese inicio de sesión tiene acceso a la base de datos a la que se pretende acceder. Para ello, SQL Server mantiene una asociación entre Inicio de Sesión a nivel de Instancia y Usuario a nivel de Base de Datos.

4.1.2.1. Crear Inicios de Sesión

El primer paso, para proporcionar acceso a una Base de Datos es crear un inicio de sesión para el usuario que necesita el acceso. Podemos crear dos tipos de inicio de sesión: Inicio de Sesión Windows (usuarios o grupos) e Inicios de Sesión SQL Server (que solo podrán crearse en Autenticación Mixta).

Para crear un Inicio de Sesión Windows:

CREATE LOGIN [EXPRESS\Usuario] FROM WINDOWS

En este ejemplo EXPRESS hace referencia al nombre de Dominio o Equipo en el que está instalado SQL Server Express y Usuario hace referencia a un usuario de Sistema Operativo.

Nota: Para que el anterior ejemplo funcione correctamente es necesario que el usuario Usuario exista a nivel de Sistema Operativo.

Para crear un Inicio de Sesión SQL Server:

CREATE LOGIN SQLUser WITH PASSWORD = 'Pa$$w0rd'

También podemos utilizar SQL Server Management Studio Express. En Object Explorer, despliega la Instancia, despliega la carpeta de Security y haz clic en Logins. Si haces clic con botón de la derecha, New Login… En la figura puedes ver el cuadro de diálogo.

New Login… En la figura puedes ver el cuadro de diálogo. 4.1.2.2. Crear Usuarios de Base

4.1.2.2. Crear Usuarios de Base de Datos

Una vez hemos creado el Inicio de Sesión para poder acceder a la instancia, debemos de proporcionar acceso a ese Inicio de Sesión a la base de datos deseada. Para ello deberemos de crear un usuario en la base de datos y asociarlo al inicio de sesión. El siguiente código es un ejemplo de este proceso, en el que estamos proporcionando acceso al Inicio de Sesión [EXPRESS\Usuario] a la base de datos DemoMSDN:

USE DemoMSDN GO CREATE USER Usuario FOR LOGIN [EXPRESS\Usuario]

Desde SQL Management Studio podemos realizar esta operación de de formas. Desde las Propiedades del Inicio de Sesión o en la base de datos, creando un usuario. LA forma más sencilla es realizarlo desde las propiedades del Inicio de Sesión. Para ello en las

propiedades de un Inicio de Sesión vete a la opción de User Mapping y selecciona las bases de datos a las que quieres proporcionar acceso. La siguiente figura muestra dicha opción:

acceso. La siguiente figura muestra dicha opción: 4.1.3. Esquemas de Base de Datos SQL Server 2005

4.1.3. Esquemas de Base de Datos

SQL Server 2005 introduce el concepto ANSI de Esquema, a través del cual podemos agrupar los objetos de base de datos, tablas, vistas, procedimientos almacenados, etc., siguiendo el criterio que mejor se adecue a nuestras necesidades. El nombre de esquema formará parte del nombre completo de los objetos que pertenecen a dicho esquema. Por ejemplo, si creamos un Esquema denominado Ventas, y dentro de él una tabla denominada Pedidos, deberemos de calificar la tabla utilizando el nombre del esquema, al estilo Ventas.Pedidos. Otra de las grandes ventajas del uso de Esquemas, es que podremos asignar permisos a este nivel, en lugar de tener que asignar permisos a los diferentes objetos de forma individual. Las siguientes sentencias ilustran este ejemplo

USE MASTER GO -- Creamos un Inicio de Sesión de SQL Server CREATE LOGIN Alumno WITH PASSWORD = 'Pa$$w0rd' GO -- Creamos una Base de Datos para pruebas CREATE DATABASE TestDB GO -- Nos conectamos a la Base de Datos USE TestDB GO -- Creamos el Schema Ventas CREATE SCHEMA Ventas

GO --Creamos la Tabla Pedidos en el Esquema Ventas CREATE TABLE Ventas.Pedidos (idPedido int primary key, FechaPedido smalldatetime, idCliente int, Estado tinyint) GO -- Creamos el usuario alumno enlazado con el Inicio de Sesión Alumno CREATE USER Alumno FOR LOGIN Alumno GO -- Otorgamos el permiso de Select al usuario Alumno sobre el Esquema Ventas. GRANT SELECT ON SCHEMA::Ventas TO Alumno

Nota: Para ejecutar este ejemplo, la instancia de SQL Server Express debe de estar ejecutándose en modo Autenticación Mixta.

4.2. Niveles de aislamiento

Cuando trabajamos con bases de datos tenemos que tener en cuenta que no estamos “solos”, es decir hay más usuarios accediendo a los mismos datos, y manipulando los mismos datos. Esta concurrencia ha de ser administrada por los sistemas gestores de base de datos, dotando a las bases de datos de las cuatro características ACID, es decir Atomicidad, dentro de una transacción las operaciones que se realizan deben poder considerarse como una sola; C. Consistencia, cualquier operación que sea validada o cancelada no puede dejar datos inconsistentes (por ejemplo violando reglas de integridad referencial). I (Isolation en inglés) Aislamiento, El gestor de la base de datos debe aislar los datos 'sucios' para evitar que otros usuarios usen información no confirmada o validada. D. Durabilidad, los datos confirmados no pueden perderse.

4.2.1. Niveles de aislamiento

Si nos detenemos en la definición de aislamiento podemos observar que no es muy concreta, es decir que da lugar a distintas interpretaciones o implementaciones de ese aislamiento. Esos niveles y exactamente lo que significa el aislamiento es lo que vamos a tratar en los siguientes apartados.

4.2.1.1. READ UNCOMMITED

Lecturas no confirmadas, realmente lo que sucede en este nivel de aislamiento es que los usuarios pueden leer datos que aún no están confirmados, y que por tanto pueden llegar a no existir nunca. Los problemas que presenta este nivel de aislamiento son los siguientes:

Lecturas sucias: Lee datos que no han llegado a validarse.

Lecturas no repetibles: Dos sentencias SELECT iguales y consecutivas podrían devolver datos diferentes.

Datos fantasmas: En dos sentencias SELECT iguales y consecutivas podrían aparecer y desaparecer filas.

Hay que ser muy cuidadoso si se utiliza este nivel de aislamiento ya que podría hacer que los usuarios se basen en datos que realmente no han existido nunca y que por tanto son erróneos. En muchas ocasiones se usa para evitar bloqueos, si así lo hacemos hemos de estar seguros que las consecuencias son admisibles.

4.2.1.2. READ COMMITED

Lecturas confirmadas, este es el nivel de aislamiento por defecto en SQL Server, no lee datos que no estén confirmados, sino que esperaría (se quedaría bloqueado) a que esos datos estén confirmados. Los bloqueos que establece mientras se lee información, tan solo permanecen activos durante el tiempo de la ejecución no durante toda la transacción y no se ve bloqueado por lecturas. Los problemas que tiene este nivel de aislamiento son:

Lecturas no repetibles: Dos sentencias SELECT iguales y consecutivas podrían devolver datos diferentes.

Datos fantasmas: En dos sentencias SELECT iguales y consecutivas podrían aparecer y desaparecer filas.

En este nivel de aislamiento las lecturas solo se ven bloqueadas por las escrituras, y las escrituras rara vez se ven bloqueadas por lecturas y además solo durante un muy corto periodo de tiempo de tal forma que no suelen producirse muchos interbloqueos.

4.2.1.3. REPETEABLE READ

Lecturas repetibles. Este nivel de aislamiento garantiza que dos select consecutivas dentro de una transacción devolverán la misma información, y lo hace creando bloqueos compartidos sobre los registros que lee de tal forma que no pueden ser modificados. El único problema que presenta es:

Datos fantasmas: En dos sentencias SELECT iguales y consecutivas podrían aparecer filas.

En este nivel de aislamiento las lecturas solo se ven bloqueadas por las escrituras, pero las escrituras se ven bloqueadas por lecturas durante el tiempo que dura la transacción que lee de tal forma que es más frecuente encontrar problemas de bloqueos.

4.2.1.4. SERIALIZABLE

Serializable. Este nivel de aislamiento no tiene ni siquiera el problema de datos fantasma, por que cuando realiza un select crea bloqueos compartidos no solamente sobre los registros que existen sino sobre los nuevos que pudiesen llegar (inserts) de tal forma que dos instrucciones select consecutivas dentro de la misma transacción devolverán exactamente los mismos datos. En muchas ocasiones los desarrolladores eligen este nivel por que es lo más parecido a “estar solo” en el sistema, sin embargo este nivel de aislamiento necesita crear un número considerable de bloqueos para poder garantizar estas lecturas repetibles evitando datos fantasma.

En este nivel de aislamiento las lecturas solo se ven bloqueadas por las escrituras, pero las escrituras se ven bloqueadas por lecturas incluso escrituras de tipo INSERT que en el nivel de aislamiento REPEATABLE READ no se verían afectas.

Este es el nivel máximo de aislamiento y también genera el nivel máximo de bloqueos, analice si realmente necesita estas características antes de usarlo.

4.2.2. Nuevo nivel de aislamiento de instantánea

El nuevo nivel de aislamiento de instantánea (el SNAPSHOT Isolation Level), cumple los mismos requisitos que el serializable es decir no tiene ninguno de los problemas de lecturas sucias, ni lecturas no repetibles ni lecturas fantasma, pero no se basa en una estrategia de bloqueo para conseguirlo sino en una estrategia de versiones de las filas. Esta estrategia permite evitar todos los problemas descritos, sin necesidad de bloquear las filas, de tal forma que una sentencia SELECT devolverá exactamente los mismos datos cada vez que se ejecute.

Dentro de estos nuevos niveles de aislamiento existen dos versiones, el propiamente llamado SNAPSHOT ISOLATION y el READ COMMITTED SNAPSHOT.

Para habilitarlos debemos usar el comando:

ALTER DATABASE <Base de Datos> SET ALLOW_SNAPSHOT_ISOLATION ON o ALTER DATABASE <Base de Datos> SET READ_COMMITTED_SNAPSHOT ON

El nivel de aislamiento de instantáneas puede mostrar conflictos en actualizaciones, el motivo es sencillo, si las sentencias select devuelven siempre la misma información pero no han impedido que otros usuarios cambien la información, en el momento en que la transacción actual vaya a cambiar algún dato, debe comprobar que los datos que están validados en la base de datos son los mismos que existían cuando fueron leídos y versionados la primera vez, en caso contrario podríamos sobrescribir modificaciones de otros usuarios pero basados en datos que ya no existen.

Nota: Como el lector habrá podido observar no hay un nivel de aislamiento perfecto para todas las situaciones, sino que cada uno presenta ventajas e inconvenientes diferentes, lo importante en este caso es ser capaz de entender como funciona cada uno de ellos y poder elegir el más adecuado para la problemática que estemos resolviendo.

4.3. Soporte XML

SQL 2005 aporta el nuevo tipo de datos XML nativo; éste nuevo tipo de datos puede formar parte de columnas de una tabla, ser una variable, o ser argumento de un procedimiento almacenado; antes de seguir adelante, deberás quitarte la idea de que por el hecho de que el XML se represente como una cadena de caracteres, internamente vaya ser guardado como texto: SQL Server 2005 lo guardará internamente en formato binario, siendo su acceso más efectivo y teniendo la posibilidad de definir la estructura de los datos de manera más eficiente. ¿Por qué esto? ¿Parece que no está de acuerdo con W3C? Pues no es así W3C se encarga de definir la estructura del lenguaje XML, no la estructura de su almacenamiento.

4.3.1. Tipo de datos XML

El tipo de datos xml permite almacenar documentos y fragmentos XML en SQL Server. Desde la versión 2005, se pueden crear columnas y variables de tipo xml y almacenar instancias XML en las mismas. Como única limitación el tamaño de la columna XML puede ser superior a 2 GB.

También se puede asociar una colección de esquemas XML con una columna, un parámetro o una variable del tipo de datos XML. Los esquemas de la colección se utilizan para validar y asignar un tipo a las instancias XML. En este caso, se dice que el XML tiene un tipo. En caso de no estar validado contra un esquema XML, la implementación de XML en SQL Server garantiza que la columna está bien formada ("well-formed") ( http://www.w3.org/TR/REC-xml/#sec- well-formed ).

Empezaremos con las columnas XML sin validar contra ningún esquema XML.

4.3.1.1. XML Nativo

El tipo de datos XML nativo, es un tipo de datos de SQL Server al igual que lo es int, char, varchar, o bigint. Con este tipo de datos, se pueden crear variables, o columnas para tablas. Un ejemplo de cómo crear una columna de tipo XML en una tabla sería lo siguiente:

CREATE TABLE EjemploXML (id int, valor xml)

Para insertar una fila en la tabla, podremos hacerlo a través de un valor tipo tabla, o podremos usar un valor de tipo caracter que se convierta de manera explícita a tipo xml:

-- conversión explícita insert EjemploXML values (1, cast('<pedido cliente="1">1</pedido>' as xml))

-- conversión implícita insert EjemploXML values (2, '<pedido cliente="1">2</pedido>')

Estas dos inserciones se han realizado correctamente porque el valor de lacolumna XML está bien formado; sin embargo, si intentas insertar la siguiente fila:

-- error en columna XML insert EjemploXML values ( 3, '<pedido cliente="1">2</pedidos>')

Mostrando el error que se muestra a continuación que indica que el XML no está bien formado; fíjate que el elemento pedido no está cerrado (se intenta cerrar con un elemento pedidos:

Msg 9436, Level 16, State 1, Line 2 XML parsing: line 1, character 31, end tag does not match start tag

Parte del beneficio de usar columna XML nativa, es que la aplicación cliente no necesitará parsear el dato XML con los correspondientes objetos de .NET Framework, porque forma parte del proceso de inserción del dato. Fíjate en el error anterior: el servidor devuelve una excepción a la aplicación cliente que tendrá que ser gestionado de la manera adecuada.

En cuanto al almacenamiento del dato, en comparación a guardarlo como texto "plano", la columna XML nativa sin asociación a esquema XML, el espacio utilizado es menor porque sólo se guardará una ocurrencia del valor de la etiqueta (o atributo) por cada fila; por ejemplo, si un atributo se llama Pedidos_Realizados_Por_Cliente, el coste de almacenamiento de guardar esa clave será mayor cuanto mayor sea la clave. SQL Server guardará la clave una vez en la fila, y para el resto de ocurrencias en la misma fila, en lugar del valor de la clave se usará un puntero a la posición donde está almacenada la clave.

En el siguiente punto veremos la diferencia entre asociar la columna a un esquema XML o no.

4.3.1.2. XML Nativo con Esquemas

Asociar un esquema XML a la columna de tipo XML aporta la posibilidad de hacer más "restrictiva" la validación de la columna XML. Un buen ejemplo para aplicar con este modelo de esquema-columnaXML es la comunicación que realizan clientes y proveedores a través de un modelo de documentos XML estandarizados: ambas partes conocen el modelo, y basándose en dicho modelo realizan la gestión del proceso definido.

Aquí aparece un punto discrepante entre el modelo relacional y el modelo XML; si se es capaz de modelar un proceso, es muy posible que se pueda definir un modelo relacional que lo cumpla: SQL Server es un servidor de bases de datos relacionales, y te encontrarás con casos que a pesar de poder implementar el esquema relacional, debido a la complejidad del modelo, haya partes que deban implementarse siguiendo un esquema XML; por ejemplo, XML es muy eficiente gestionando jerarquías, mientras que SQL Server, acaba de introducirlas con las Expresiones de Tablas Comunes.

Para crear un esquema XML lo haremos de la siguiente forma:

CREATE XML SCHEMA COLLECTION schema1 AS

N'<?xml version="1.0" ?> <xsd:schema targetNamespace="http://schemas.mi_base.com/schema1" xmlns="http://schemas. mi_base.com/schema1" elementFormDefault="qualified" attributeFormDefault="unqualified"

xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<xsd:element name="Pedido"> <xsd:complexType> <xsd:sequence> <xsd:element name="Numero" type="xsd:string" minOccurs="1"

maxOccurs="1"/>

<xsd:element name="Cliente" type="xsd:string" minOccurs="1"

maxOccurs="1"/>

<xsd:element

maxOccurs="1"/>

name="Importe"

</xsd:sequence>

</xsd:complexType>

</xsd:element>

</xsd:schema>'

type="xsd:int"

minOccurs="1"

Fíjate que el esquema define la estructura de cómo será el documento XML; llega hasta tal nivel de detalle que especifica, el número de ocurrencias que deberá tener cada atributo, los nombres de los atributos, y elementos, e incluso los tipos de datos de cada uno de ellos.

Y a continuación se crea una tabla que use el esquema recién creado:

CREATE TABLE dbo.EjemploEsquema (Pedido xml (schema1))

Fíjate que en el tipo de datos que se define para la columna Pedido, se especifica el esquema XML creado anteriormente. A partir de este momento, se podrán insertar filas en la tabla, haciendo referencia al esquema XML asociado al documento:

INSERT INTO dbo.EjemploEsquema (Pedido) VALUES ('<?xml version="1.0" ?> <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>2231-AX</Numero>

<Cliente>Microsoft</Cliente>

<Importe>250</Importe>

</Pedido>');

INSERT INTO dbo.EjemploEsquema (Pedido) VALUES ('<?xml version="1.0" ?> <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>3533-SQ</Numero>

<Cliente>Solid Quality Learning</Cliente>

<Importe>750</Importe>

</Pedido>');

De forma similar a como hemos visto anteriormente, si intentamos insertar un importe que no es número entero (por ejemplo un caracter, o un valor numérico):

INSERT INTO dbo.EjemploEsquema (Pedido) VALUES ('<?xml version="1.0" ?> <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>3533-SQ</Numero>

<Cliente>Solid Quality Learning Iberoamericana</Cliente>

<Importe>750AA</Importe>

</Pedido>');

La inserción no se realizará, y además obtendremos el siguiente mensaje de error, que nos indica que el valor 750aa no es un tipo de datos válido para el elemento Importe:

Msg 6926, Level 16, State 1, Line 1 XML Validation: Invalid simple type value: '750aa'. Location:

/*:Pedido[1]/*:Importe[1]

Lo mismo sucederá si se intenta insertar un XML cuyo esquema no se ajusta al definido. Por ejemplo, ¿qué pasaría si el documento XML tuviera un elemento llamado fecha?:

INSERT INTO dbo.EjemploEsquema (Pedido VALUES ('<?xml version="1.0" ?> <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>3538-SQ</Numero>

<Cliente>Solid Quality Learning Iberoamericana</Cliente>

<Importe>750</Importe>

<FechaPedido>2006-07-25</FechaPedido>

</Pedido>');

Aunque el XML está bien formado (fíjate que el elemento FechaPedido está correctamente definido), obtendremos la siguiente excepción indicando que el elemento FechaPedido no se esperaba en el XML:

Msg 6923, Level 16, State 1, Line 2 XML Validation: Unexpected element(s):

http://schemas.mi_base.com/schema1:FechaPedido. Location:

/*:Pedido[1]/*:FechaPedido[1]

Desde el punto de vista de almacenamiento, el uso de esquemas XML, ayuda a SQL Server

a no tener que almacenar en las columnas donde se almacenan los datos información

relativa a los atributos, y elementos del dato XML porque dicha información se encuentra en el esquema XML.

4.3.2. Índices XML

Las columnas XML pueden indexarse para optimizar el acceso a las columnas XML, y mejorar el rendimiento de las consultas XQuery que veremos a continuación.

Para resolver consultas XQuery, si no existe índice XML, SQL Server, tiene que crear la estructura jerárquica la columna XML en memoria de cada columna, mientras que si está indexado, dicha estructura jerárquica se encuentra ya serializada en el índice, por lo que no será necesaria dicha "jerarquización" en memoria, con el consiguiente ahorro de uso de CPU.

Para poder indexar columnas XML, por diseño, es necesario que la tabla tenga definida un índice agrupado por la clave primaria de la tabla; éste requerimiento se debe a que en caso de que la tabla se encuentra particionada (consultar particionado de datos en la documentación del producto), el índice primario XML se pueda particionar usando el mismo esquema y función de particionado.

Para definir el índice XML primario, en la tabla EjemploEsquema añadiremos una columna entera con la propiedad identity, que haremos clave primaria, sobre la que definiremos el índice agrupado:

ALTER TABLE dbo.EjemploEsquema ADD identificador int IDENTITY GO ALTER TABLE dbo.EjemploEsquema ADD CONSTRAINT ci_EjemploEsquema PRIMARY KEY CLUSTERED (identificador) GO

4.3.2.1. Índice Primario

El índice primario XML indexa la columna XML manteniendo referencias al dato XML, y al

índice agrupado (el que indica la posición de la fila de datos).

Para ello, usaremos la sentencia CREATE INDEX como en el siguiente ejemplo:

CREATE PRIMARY XML INDEX xml_index_EjemploEsquema ON dbo.EjemploEsquema (Pedido) GO

Una vez creado el índice primario XML, el índice agrupado (el que hemos creado para la clave primaria), no podrá modificarse; en caso de necesitar cambiar el índice agrupado, será necesario borrar antes los índices XML.

Una columna XML no puede formar parte de un índice "normal" (los tradicionales índices agrupado, no agrupado). Además, si la columna XML forma parte de una vista, no se podrá indexar la columna XML, es decir, las vistas indexadas no soportan índices XML

4.3.2.2. Índice Secundario

Los índices secundarios XML requieren que se haya creado un índice primario XML. Éste tipo de índices se definen para optimizar tipos de consultas XQuery; el índice XML secundario se definirá para optimizar tipos de consultas concretos:

VALUE: cuando la consulta XQuery hace referencia a cualquier elemento del nodo; por ejemplo, //Cliente=”Microsoft”: este caso devolvería todos los elementos cuyo autor sea José Antonio, independientemente de que sea hijo de artículo u otro elemento.

PATH: beneficiará consultas XQuery que usan consultas tipo que buscan por el path del XML, por ejemplo, /Pedido[Cliente=”Solid Quality Learning”]

PROPERTY: cuando se accede a propiedades del primer elemento de un nodo XML; por ejemplo /Pedido/@Fecha

Por ejemplo, si queremos definir un índice secundario para optimizar las consultas XQuery que busquen valor de los ele

CREATE XML INDEX Path_EjemploEsquema_xml_index ON dbo.EjemploEsquema(Pedido) USING XML INDEX xml_index_EjemploEsquema FOR PATH GO

4.3.3.

XQuery

XQuery es el lenguaje utilizado para realizar consultas sobre instancias XML. Al igual que T- SQL se utiliza para realizar consultas sobre tablas de la base de datos, XQuery se utiliza para consultar sobre datos de instancias XML nativas; es un lenguaje estandarizado por los fabricantes y sus especificaciones se pueden consultar en W3C

(http://www.w3.org/XML/Query);

Algunos de los métodos que se implementan en el lenguaje son:

query() para realizar consultas sobre una instancia XML; el valor devuelto no se ajusta a ningún esquema, pero si es bien formado; por ejemplo:

select Pedido.query (' declare namespace my="http://schemas.mi_base.com/schema1"; /my:Pedido/my:Cliente') from dbo.EjemploEsquema

Devolverá:

<my:Cliente xmlns:my="http://schemas.mi_base.com/schema1"> Microsoft<my:/Cliente> <my:Cliente xmlns:my="http://schemas.mi_base.com/schema1"> Solid Quality Learning</my:Cliente>

value() para recuperar valores de la instancia XML.

exist() para comprobar si una consultas devuelve un resultado no vació; por ejemplo:

select Pedido from dbo.EjemploEsquema where Pedido.exist (' declare namespace my="http://schemas.mi_base.com/schema1"; /my:Pedido[(my:Cliente = "Microsoft")]') = 1

Devolverá:

<my:Cliente xmlns:my="http://schemas.mi_base.com/schema1"> Microsoft<my:/Cliente>

modify() para hacer modificaciones; podremos usar las funciones XQuery insert, delete y replace value of; veamos un ejemplo; reemplazar el nombre de cliente Solid Quality Learning por Solid Quality Learning Iberoamericana en la columna Pedido de la tabla EjemploEsquema:

update dbo.EjemploEsquema set Pedido.modify (' declare namespace my="http://schemas.mi_base.com/schema1"; replace value of (/my:Pedido/my:Cliente)[1] with "Solid Quality Learning Iberoamericana" ') where Pedido.exist ( '

declare namespace my="http://schemas.mi_base.com/schema1"; /my:Pedido[(my:Cliente = "Solid Quality Learning")]') = 1

Que indicará que se ha modificado una fila; el contenido de la tabla quedará como sigue:

select Pedido from dbo.EjemploEsquema GO <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>2231-AX</Numero>

<Cliente>Microsoft</Cliente>

<Importe>250</Importe>

</Pedido> <Pedido xmlns="http://schemas.mi_base.com/schema1">

<Numero>2232-SQ</Numero>

<Cliente>Solid Quality Learning Iberoamericana</Cliente>

<Importe>250</Importe>

</Pedido>

4.4. Integración del CLR

Una de las novedades más sonadas de SQL Server 2005 (también incluida en la versión Express) es la integración de .NET Framework en el gestor de bases de datos, en otras palabras, la