Introducción a los servidores vinculados en SQL Server

Un servidor vinculado consiste en un enlace a una fuente de datos externa (remota). La fuente de datos remota puede ser Oracle, Access, Excel o cualquier fuente de datos a la que se pueda acceder mediante OLE DB (por ejemplo, Index Server).

Servidores Remotos

  • No usan proveedores OLE DB
  • Una configuración de servidor remoto permite a un cliente conectado a una instancia SQL Server ejecutar un procedimiento almacenado en otra instancia de SQL Server sin establecer otra conexión.
  • Tanto procedimientos almacenados como consultas distribuidas están permitidas contra servidores vinculados, sin embargo, sólo se permiten procedimientos almacenados contra servidores remotos.
  • Conviene usar Servidores Vinculados en vez de Servidores Remotos. Estos últimos existen para mantener la compatibilidad hacia atrás.

Nota: Los servidores enlazados pueden ejecutar procedimientos almacenados remotos de SQL Server mientras que no pueden ejecutar procedimientos almacenados de Oracle.

Añadiendo Servidores Vinculados

Sintaxis

sp_addlinkedserver [ @server= server [ , [ @srvproduct= product_name ]

, [ @provider= provider_name ]

, [ @datasrc= data_source ]

, [ @location= location ]

, [ @provstr= provider_string ]

, [ @catalog= catalog ]

En este ejemplo vamos a ver como crear una conexión a Oracle como servidor enlazado a SQL Server. Crearemos un servidor vinculado llamado OraLinkServ el cual usa el proveedor Microsoft OLE DB y asume que el alias SQL*Net para la base de datos es OraProduction.

USE masterGOEXEC sp_addlinkedserver @server = ‘OraLinkServ’, @srvproduct = ‘Oracle’, @provider = ‘MSDAORA’, @datasrc = ‘OraProduction’

GO

Añadiendo datos de login a un Servidor Vinculado

sp_addlinkedsvrlogin

Crea o actualiza un mapeado entre logins de la instancia local de SQL Server y logins remotos en el Servidor Vinculado.

Sintaxis

sp_addlinkedsrvlogin [ @rmtsrvname = rmtsrvname

, [ @useself = useself ]

, [ @locallogin = locallogin ]

, [ @rmtuser = rmtuser ]

, [ @rmtpassword = rmtpassword

Consultando Servidores Vinculados

A continuación podemos ver varios métodos de ejecutar una consulta sobre diferentes servidores vinculados:

NombreServidor.NombreBD.Propietario.NombreObjeto

Select * from LinkSqlServ.Northwind.dbo.Employees

Select * from OraLinkServ..SCOTT.Stores

Select * from LinkMdb…customers

OpenQuery: Ejecuta una consulta de paso a través en el servidor vinculado proporcionado el cual debe ser una fuente de datos OLE DB (por ejemplo, para su uso con Index Server).

Sintaxis:

OpenQuery (servidor_vinculado, ‘consulta’)

Ejemplos:

SELECT * FROM OPENQUERY(OraLinkServ, ‘SELECT Name, Title FROM Scott.Titles’)

INSERT OPENQUERY(LinkSqlServ, ‘select * from pubs.dbo.jobs’) values (15, ‘Technical Editor’, 100, 300)

Ejemplo de consulta sobre Index Server:

SELECT Path, Filename FROM OpenQuery(IDX, ‘Select Path, Directory, FileName, Size FROM SCOPE() ORDER BY Rank, Size DESC’)

OpenRowSet: Este método consiste en una forma ad-hoc de conectarse y accceder a datos remotos usando OLE DB. Crea un servidor vinculado al vuelo.

Sintaxis

OPENROWSET ( ‘provider_name
datasource‘ ; ‘user_id‘ ; ‘password
provider_string‘ }
{ [ catalog] [ schemaobject
query‘ }

Eliminando Servidores Vinculados y sus Logins

sp_dropserver : Elimina un servidor de la lista de servidores vinculados y remotos en el SQL Server local.

sp_droplinkedsvrlogin: Elimina un mapeado existente entre un login en el servidor local con SQL Server y un login en el servidor vinculado.

Obteniendo Metadatos

sp_tables_ex: Devuelve una tabla con información acerca de las tablas del servidor vinculado especificado.

sp_columns_ex: Devuelve información de columna, una fila por columna, para la/s tabla/s dada/s de un servidor vinculado. Sólo devuelve información de una sola columna siempre que haya sido especificada.

sp_table_privileges_ex: Devuelve información de privilegios acerca de la tabla especificada del servidor vinculado.

sp_column_privileges_ex: Deuelve privilegios de columna para la tabla especificada del servidor vinculado.

sp_linkedservers: Devuelve la lista de servidores vinculados en el servidor local.

sp_helpserver: Muestra información sobre un servidor remoto o de réplica, o sobre todos los servidores de ambos tipos.  Proporciona el nombre del servidor, el nombre de red del servidor, el estado de réplica del servidor, el número de identificación del servidor, nombre de la intercalación y valores de time-out, tanto de conexión como de consultas.

sysservers: Contiene una fila para cada servidor al que SQL Server puede acceder como fuente de datos OLEDB. Esta tabla es almacenada en la base de datos master.

sysoledbusers: Contiene una fila por cada mapeado de usuario y contraseña para el servidor vinculado especificado. Esta tabla está contenida en la base de datos master.

xp_enum_oledb_providers: Proporciona información sobre proveedores OLEDB

sp_serveroption: Ajusta opciones en servidores remotos y vinculados.

sp_serveroption se ha ampliado con dos nuevas funcionalidades, use remote collation y collation name, que permiten soporte para intercalación en servidores vinculados.

sp_setnetname: Ajusta nombres de red en sysservers para los nombres actuales de equipos de red y nombres de instancias remotas de SQL Server. Este procedimiento puede ser usado para permitir ejecución de llamadas a procedimientos almacenados remotos para ordenadores que tienene nombres de red que contienen identificadores SQL Server no válidos.

* Traducción propia de «An Introduction to Linked Servers» por Krishna Sonti publicado en www.sqlservercentral.com

http://www.sqlservercentral.com/columnists/ksonti/anintroductiontolinkedservers.asp

Genéricos en C# (y II)

Una de las utilizaciones más comunes de genéricos se basa en la implementación y manipulación de colecciones fuertemente tipadas. En este apartado veremos muestras de cómo trabajar con los tipos de colecciones genéricas disponibles a partir de la versión 2.0 de NET Framework: la clase Dictionary<>, la clase List<>, la clase Queue<> y la clase Stack<>.

Clase Dictionary

Esta clase está diseñada para almacenar valores asociados a un valor de búsqueda. Tradicionalmente se ha usado esta clase para almacenar valores con cadenas, tales como nombres, palabras clave o GUIDs, pero además ahora podemos usar un objeto de cualquier tipo como clave y otro objeto de cualquier tipo como valor.

Ahora podemos especificar la clave y el tipo del valor al mismo tiempo de la creación del objeto usando parámetros de tipo, como se muestra en el siguiente fragmento de código:

// Diccionario de Prueba

Dictionary<string, int> diasMes = new Dictionary<string, int>();

diasMes[“Enero”] = 31;

diasMes[“Febrero”] = 28;

diasMes[“Marzo”] = 31;

Console.WriteLine(“Marzo tiene “ + diasMes[“Marzo”].ToString() + ” días.”);

Este ejemplo crea y manipula una clase Dictionary<> en la que todas las claves son cadenas, y todos los valores son números, almacenando el número de dias en cada mes.

Clase List

La clase List<> se explica por si misma fácilmente. Su único propósito es almacenar una lista de items. Sin genéricos, las listas eran responsabilidad de un conjunto de tipos System.Object y cuando el desarrollador necesitaba un tipo de datos fuera de ella, debía incluir operaciones de conversión de tipos y código potencialmente enrevesado. En el siguiente ejemplo vemos como crear y usar arbitrariamente listas de datos fuertemente tipados nunca había sido tan fácil:

// Clase List

List<Cliente> listaClientes = new List<Cliente>();

Cliente nuevoCli = new Cliente();

nuevoCli.Apellido = “Hoffman”;

nuevoCli.Nombre = “Kevin”;

nuevoCli.IDCliente = 1;

listaClientes.Add(nuevoCli);

Console.WriteLine(string.Format(“Hay {0} clientes, el primero es {1} {2}”, listaClientes.Count, listaClientes[0].Nombre, listaClientes[0].Apellido));

El código precedente produce la siguiente salida:

Hay un clientes, el primero es Kevin Hoffman

Conviene fijarse en el hecho de que ahora podemos escribir código como el siguiente sin tener que escribir ninguna de nuestras propias clases para ello:

listaClientes[0].Nombre

Antes, para obtener una lista como la mostrada en este ejemplo, los desarrolladores solían pasar muchas horas creando sus propias clases de colección fuertemente tipadas. Ahora podemos crearlas en tiempo de ejecución sin pérdida de rendimiento y sin necesidad de hacer conversión de tipos de forma que el código es más sencillo y reutilizable.

Clase Queue

La clase Queue no es más que una cola FIFO (First In First Out). Esta clase nos permite especificar el tipo de datos de los elementos en una cola al momento de la instanciación, como se muestra en el siguiente ejemplo:

Queue<Cliente> cClientes = new Queue<Cliente>();

cClientes.Enqueue(nuevoCli);

Cliente dqCli = cClientes.Dequeue();

Console.WriteLine(string.Format(“Extraido de la cola el siguiente cliente: {0} {1}”,dqCli.Nombre, dqCli.Apellido));

Clase Stack

Al contrario que la clase Queue<>, la clase Stack<> es una colección LIFO (Last-in First-out), es decir, una pila.  El siguiente código muestra como colocar elementos en una pila genérica y como obtenerlos. Para asegurarnos de saber cómo trabaja la pila, podemos jugar con el cambio de orden en el que colocamos los items para ver los resultados:

Stack<Cliente> pilaClientes = new Stack<Cliente>();

Cliente cli1 = new Cliente();

cli1.Nombre = “John”;

cli1.Apellido = “Doe”;

cli1.IDCliente = 99;

pilaClientes.Push(nuevoCli);

pilaClientes.Push(cli1);

Cliente popCli = pilaClientes.Pop();

Console.WriteLine(string.Format(“El Cliente extraido de la pila es {0} {1}”,popCli.Nombre, popCli.Apellido));

 

* Basado en traducción propia de Visual C# 2005 Unleashed de Kevin Hoffman

Buscar en documentos con Microsoft Index Server

Si deseamos realizar una búsqueda de texto en documentos, .NET nos permite aprovechar la potencia del Servicio de Microsoft Index Server para búsqueda e indexación de documentos. Si además necesitamos filtrar por un tipo de documento concreto lo podemos usar en combinación con Ifilter.

Primero hemos de asegurarnos de que el Servicio de Microsoft Index Server está iniciado. De no ser así, accederemos al listado de servicios de nuestra máquina y lo pondremos en marcha. Si deseamos que permanezca más allá de una sesión, habilitaremos el inicio automático del servicio.

Microsoft .NET permite el acceso a Index Server mediante OleDb, de forma que podemos crear consultas SQL sobre los índices de los documentos que nos permitan crear datasets sobre esos resultados como cualquier otro origen de datos. En el IDE, desde el explorador de servidores se puede crear una conexión con dicho servicio.

Un ejemplo de consulta SQL sobre Index Server podría ser el siguiente:

SELECT Rank, VPath, DocTitle, Filename, Characterization, Write

FROM SCOPE()

WHERE FREETEXT(‘diseño’)

ORDER BY RANK, SIZE DESC

Rank es el campo que establece el rango de coincidencia del patrón dado por FREETEXT(). Si añadimos más palabras a esta función, la consulta nos devolverá aquellos documentos que tengan al menos una de las palabras definidas en el patrón.

Otra función de búsqueda interesante es CONTAINS

SELECT Rank, VPath, DocTitle, Filename, Characterization, Write

FROM SCOPE()

WHERE NOT CONTAINS(VPath, ‘”_vti_” OR “.config”‘)

AND CONTAINS(Contents, ‘”keyword1″ AND “keyword2″‘)

AND CONTAINS(DocTitle, ‘”keyword1″ AND “keyword2″‘)

La función SCOPE nos permite limitar nuestra consulta a uno varios directorios particulares, y definir si queremos o no que incluya subdirectorios.

Usando funciones de rango y particionado en SQL Server

Una de las nuevas funcionalidades que nos ofrece SQL Server 2005 es la de poder añadir expresiones de ranking a nuestra consulta. Esto viene muy bien en aplicaciones .NET para paginar y ordenar en un grid además de en cualquier otro escenario. Vamos a ver estas nuevas funcionalidades en la base de datos de ejemplo AdventureWorks.

ROW_NUMBER()

La función más básica de ranking es ROW_NUMBER(). Esta función devuelve una columna como una expresión que contiene el número de fila dentro del conjunto de resultados. Este número solo se usa en el contexto de la consulta, si el resultado de ella cambia, ROW_NUMBER cambiará. La expresión ROW_NUMBER toma una sentencia ORDER BY con la columna que queremos usar para contar las filas con un operador OVER como se muestra a continuación:

SELECT SalesOrderID, CustomerID, Row_Number() Over (Order By SalesOrderID) as RunningCount

FROM Sales.SalesOrderHeader

WHERE SalesOrderID > 10000

ORDER BY SalesOrderID

DevolverÍa el siguiente resultado:

SalesOrder     ID Customer   ID RunningCount
——–     ———-   ————–
43659            676                  1
43660            117                   2
43661            442                  3
43662            227                  4
43663           510                    5
43664           397                   6
43665           146                    7
43666          511                      8
43667          646                     9
More

Alternativamente, si tenemos una clÁusula ORDER BY en nuestro conjunto de resultados diferente de la ORDER BY en nuestra expresión ROW_NUMBER()

Row_Number usando un valor único, diferente order by

SELECT SalesOrderID, CustomerID, Row_Number() Over (Order By SalesOrderID) as RunningCount

FROM Sales.SalesOrderHeader

WHERE SalesOrderID > 10000

Order By

CustomerID Diferente ORDER BY que en Row_NUMBER

Muestra lo siguiente:

SalesOrder     ID Customer   ID RunningCount
——–     ———-   ————–
43860             1                       202
44501             1                       843
45283             1                      1625
46042             1                      2384
46976             2                     3318
47997             2                     4339
49054             2                     5396
More

Si elegimos la function ROW_NUMBER() contra una columna no única, se producirá un RunningCount de forma que no haya filas que tengan el mismo número. Por ejemplo, CUSTOMERID se puede repetir en este ejemplo:

SELECT SalesOrderID, CustomerID, Row_Number() Over (Order By CustomerID) as RunningCount

FROM Sales.SalesOrderHeader

WHERE SalesOrderID > 10000

Order By

CustomerID

Esta consulta muestra lo siguiente:

SalesOrder     ID Customer   ID RunningCount
——–     ———-   ————–
43860            1                       1
44501             1                      2
45283            1                       3
46042           1                       4
46976           2                      5
47997           2                      6
49054           2                      7
50216            2                      8
51728            2                      9
57044           2                      10
63198           2                       11
69488          2                      12
44124          3                       13
. . . More

También podemos usar RANK como expresión en una cláusula WHERE. SQL Server devuelve un error cuando intentamos referirnos a una función de Ranking en una cláusula WHERE. Podemos solucionar esto usando una Common Table Expression.

  • usar una common table expression si se quiere
  • filtrar por una de las filas que contenga
  • una función de ranking, ya que estas
  • no están permitidas en cláusulas where o having

WITH NumberRows

AS

(

SELECT SalesOrderID, CustomerID, Row_Number() Over (Order By SalesOrderID) as RowNumber

FROM Sales.SalesOrderHeader

)

SELECT * FROM NumberRows

WHERE RowNumber BETWEEN 100 AND 200

ORDER BY SalesOrderID

El resultado se muestra a continuación:

SalesOrder     ID Customer   ID RunningCount
——–     ———-   ————–
43758            27646              100
43759            13257               101
43760           16352               102
43761            16493               103
43762            27578               104
43763            16525                105
43764            16612                106
43765            11010                107
43766            16518               108
43767             11001                109
43768 27649 110
43769 21659 111
43770 27614 112
�More

De la misma forma, podemos usar una CTE (Common Table Expression) si necesitamos establecer el rango por un agregado GROUP BY. En este caso, usaremos SUM() en una CTE y aplicaremos entonces ROW_NUMBER() despu�s del agregado.

�rango por la suma de totaldue

�necesitamos una CTE para hacer una suma

�de forma que este ejemplo tendr� un

�customerID sumado con todos de sus propios pedidos

With CustomerSum

As

(

Select

CustomerID, Sum(TotalDue) As totalamt

From

Sales.SalesOrderHeader

Group By

CustomerID

)

�a�ade un row_number al final de los resultados

Select

*, Row_Number() Over (Order By totalamt Desc) as RowNumber

From

CustomerSum

Resultado:

CustomerID totalamt RowNumber
���� ������� �������
678 1179857.4657 1
697 1179475.8399 2
170 1134747.4413 3
328 1084439.0265 4
514 1074154.3035 5
155 1045197.0498 6
72 1005539.7181 7
227 984324.0473 8
433 983871.933 9
166 979881.3491 10
146 964134.7777 11
670 946105.7121 12
506 937466.3027 13
�More

RANK()

RANK() trabaja de forma similar a ROW_NUMBER() excepto porque no deshace empates, es decir, no vamos a obtener un �nico valor en caso de empate.

Select

SalesOrderID, CustomerID, RANK() Over (Order By CustomerID) as RunningCount

From

Sales.SalesOrderHeader

Where

SalesOrderID > 10000

Order By

CustomerID

El resultado devuelto:

SalesOrderID CustomerID RunningCount
���� ���� �������
43860 1 1
44501 1 1
45283 1 1
46042 1 1
46976 2 5
47997 2 5
49054 2 5
50216 2 5
51728 2 5
57044 2 5
63198 2 5
69488 2 5
44124 3 13
. . . More

El siguiente ejemplo redondea el campo TotalDue a la centena m�s cercana usando CTE y RANK sobre el campo derivado:

�rango por TotalDue, sumado y redondeado a la centena

�necesitamos una CTE para hacer la suma y redondear

�asi que este ejemplo obtendr� el total de todos

�los pedidos por CustomerID

With CustomerSum

As

(

Select

CustomerID, Round(Convert(Int, Sum(TotalDue))/100,8) *100 As totalamt

From

Sales.SalesOrderHeader

Group By

CustomerID

)

Select

*, Rank() Over (Order By totalamt Desc) as Rank

From

CustomerSum

La consulta mostrar� lo siguiente:

CustomerID totalamt Rank
���� ���� �������
678 1179800 1
697 1179400 2
170 1134700 3
328 1084400 4
514 1074100 5
155 1045100 6
72 1005500 7
227 984300 8
433 983800 9
166 979800 10
146 964100 11
670 946100 12
�More

DENSE_RANK() y NTILE(n)

DENSE_RANK() trabaja ex�ctamente como lo hace RANK() pero eliminando el salto entre n�meros en caso de empate.

Select

SalesOrderID, CustomerID, DENSE_RANK() Over (Order By CustomerID) as RunningCount

From

Sales.SalesOrderHeader

Where

SalesOrderID>10000

Order By

CustomerID

As� queda el resultado de la consulta:

SalesOrderID CustomerID RunningCount
���� ���� �������
43860 1 1
44501 1 1
45283 1 1
46042 1 1
46976 2 2
47997 2 2
49054 2 2
50216 2 2
51728 2 2
57044 2 2
63198 2 2
69488 2 2
44124 3 3
. . . More

NTile(n) divide todos los resultados en partes aproximadamente iguales y asigna cada parte al mismo n�mero dentro del resultado de la consulta. Esto puede ser muy �til en porcentajes. Veamos un ejemplo:

Select

SalesOrderID, CustomerID, NTILE(10000) Over (Order By CustomerID) as RunningCount

From

Sales.SalesOrderHeader

Where

SalesOrderID > 10000

Order By

CustomerID

Nos dar�a algo as�:

SalesOrderID CustomerID RunningCount
���� ���� �������
43860 1 1
44501 1 1
45283 1 1
46042 1 1
46976 2 2
47997 2 2
49054 2 2
50216 2 2
51728 2 3
57044 2 3
63198 2 3
69488 2 3
44124 3 4
. . . More

Un �ltimo ejemplo nos muestra c�mo usar todas estas funciones juntas en una sola consulta y de paso nos ense�a la diferencia entre las cuatro funciones de rango.

Select

SalesOrderID as OrderID,

CustomerID,

Row_Number() Over (Order By CustomerID) as RowNum,

RANK() Over (Order By CustomerID) as Rank,

DENSE_RANK() Over (Order By CustomerID) as DRank,

NTILE(10000) Over (Order By CustomerID) as NTile

From

Sales.SalesOrderHeader

Where

SalesOrderID > 10000

Order By

CustomerID

OrderID CustomerID RowNum Rank DRank NTile
���� ���� ��- �� �� ���
43860 1 1 1 1 1
44501 1 2 1 1 1
45283 1 3 1 1 1
46042 1 4 1 1 1
46976 2 5 5 2 2
47997 2 6 5 2 2
49054 2 7 5 2 2
50216 2 8 5 2 2
51728 2 9 5 2 3
57044 2 10 5 2 3
63198 2 11 5 2 3
69488 2 12 5 2 3
44124 3 13 13 3 4
44791 3 13 13 3 4
�More

PARTITION BY

Las funciones de rango tambi�n se pueden combinar con funciones de particionado. Este tipo de funciones divide un conjunto de resultados en particiones iguales basadas en los valores de una sentencia PARTITION BY en conjunci�n con la cl�usula OVER de la funci�n de rango. Es como aplicar un GROUP BY a nuestra funci�n de rango obteniendo un rango separado para cada partici�n. El siguiente ejemplo usa ROW_NUMBER con PARTITION BY para contar el n�mero de pedidos por fecha de pedido y por vendedor. Hacemos esto con un PARTITION BY SalesPersonID OVER OrderDate. Puede ser usado con cualquiera de las cuatro funciones de rango.

Select

SalesOrderID, SalesPersonID, OrderDate, Row_NUMBER() Over (Partition By SalesPersonID Order By OrderDate) as OrderRank

From

Sales.SalesOrderHeader

Where

SalesPersonID is not null

Resultado:

SalesOrderID SalesPersonID OrderDate OrderRank
���� ����- �������� ���
43659 279 2001-07-01 00:00:00.000 1
43660 279 2001-07-01 00:00:00.000 2
43681 279 2001-07-01 00:00:00.000 3
43684 279 2001-07-01 00:00:00.000 4
43685 279 2001-07-01 00:00:00.000 5
43694 279 2001-07-01 00:00:00.000 6
43695 279 2001-07-01 00:00:00.000 7
43696 279 2001-07-01 00:00:00.000 8
43845 279 2001-08-01 00:00:00.000 9
43861 279 2001-08-01 00:00:00.000 10
. . . More
48079 287 2002-11-01 00:00:00.000 1
48064 287 2002-11-01 00:00:00.000 2
48057 287 2002-11-01 00:00:00.000 3
47998 287 2002-11-01 00:00:00.000 4
48001 287 2002-11-01 00:00:00.000 5
48014 287 2002-11-01 00:00:00.000 6
47982 287 2002-11-01 00:00:00.000 7
47992 287 2002-11-01 00:00:00.000 8
48390 287 2002-12-01 00:00:00.000 9
48308 287 2002-12-01 00:00:00.000 10
. . . More

PARTITION BY soporta adem�s otras funciones de agregado incluyendo MIN y MAX.

* Traducci�n de Using Ranking and Windowing Functions in SQL Server 2005 de Stephen Forte publicada en www.sqljunkies.com

Colecciones genéricas en C#

FUNCIONES GENRICAS

Una funcin genrica define un conjunto general de operaciones que se aplicarn a diferentes tipos de datos. Tiene como parmetro el tipo de datos que le son pasados para que opere sobre ellos. Esto nos permite que pueda aplicarse el mismo procedimiento general sobre un amplio rango de datos.

Veamos como funciona con un ejemplo:

public static void intercambia<X>(ref X a,ref X b)

{

X temporal;

temporal = a;

a=b;

b= temporal;

}

public static void Main(string[] args)

{

int i = 10, j=20;

float x=10.1F, y=23.3F;

Console.WriteLine(Original i, j: {0} , {1} \n,i,j);

Console.WriteLine(Original x, y: {0} , {1} \n,x,y);

intercambia(ref i,ref j);

intercambia(ref x,ref y);

Console.WriteLine(Nuevo i, j: {0} , {1} \n,i,j);

Console.WriteLine(Nuevo x, y: {0} , {1} \n,x,y);

string fin = Console.ReadLine();

}

La lnea void intercambia<X>(ref X a,ref X b) le indica al compilador que se est comenzando una definicin genrica. X es el tipo de datos de los valores que sern intercambiados. En main se llama a la funcin intercambia() utilizando dos tipos diferentes de datos: enteros y flotantes. Puesto que es una funcin genrica, el compilador crea automticamente dos versiones de ella una que intercambia valores enteros y otra con valores flotantes.

Podemos definir ms de un tipo genrico siempre que los separemos por comas. Veamos otro ejemplo:

public static void mifunc<tipo1,tipo2>(tipo1 x, tipo2 y)

{

Console.WriteLine( {0} , {1},x,y);

}

public static void Main(string[] args)

{

mifunc(10,hola);

mifunc(0.23,10L);

string fin = Console.ReadLine();

}

Las funciones genricas son equivalentes a las sobrecargadas excepto que son ms restrictivas. Cuando las funciones se sobrecargan, dentro de ellas pueden ocurrir acciones diferentes, pero una genrica debe realizar la misma funcin general para todas las versiones.

Aunque una funcin plantilla puede sobrecargarse a s misma, si es necesario tambin es posible sobrecargarla de forma explcita. En este caso, la funcin sobrecargada redefine (u oculta) la funcin genrica relativa a esa versin especfica. Veamos una versin diferente del primer ejemplo:

public static void intercambia<X>(ref X a,ref X b)

{

X temporal;

temporal = a;

a=b;

b= temporal;

}

// Redefinicin de la versin genrica de intercambia()

public static void intercambia(int a, int b)

{

Console.WriteLine(Esto est dentro de intercambia(int, int)\n);

}

public static void Main(string[] args)

{

int i = 10, j=20;

float x=10.1F, y=23.3F;

Console.WriteLine(Original i, j: {0} , {1} \n,i,j);

Console.WriteLine(Original x, y: {0} , {1} \n,x,y);

intercambia(i,j); // llamada a la versin de intercambia() sobrecargada explcitamente

intercambia(ref x,ref y); // intercambio de floats

Console.WriteLine(Nuevo i, j: {0} , {1} \n,i,j);

Console.WriteLine(Nuevo x, y: {0} , {1} \n,x,y);

string fin = Console.ReadLine();

}

Cuando llamamos a intercambia(i,j) se invoca a la versin sobrecargada explcitamente en el programa. Esta sobrecarga permite confeccionar una versin de una funcin genrica para que se adapte a una situacin especial. Sin embargo, en general, si necesitamos tener diferentes versiones para una funcin para diferentes tipos de datos, deberamos usar funciones sobrecargadas y no plantillas.

CLASES GENRICAS

Adems de las funciones genricas podemos definir una clase genrica, esto es, una clase que define todos los algoritmos usados por ella, pero el tipo de datos que, realmente, va a ser manipulado se especificar como un parmetro cuando se creen los objetos de esa clase.

Son tiles cuando una clase contiene una lgica generalizable. Por ejemplo, el mismo algoritmo que almacena una cola de enteros funciona tambin para una cola de caracteres. Lo mismo podramos decir con una lista enlazada. El compilador generar automticamente el tipo adecuado de objeto en funcin del tipo que se especifique cuando se crea el objeto.

La forma general de la declaracin de una clase genrica se muestra aqu:

public class Generic<T>

<T> es el nombre del tipo de resguardo o parmetro de tipo que se especificar cuando se instancie la clase. Si es necesario, puede definirse ms de un tipo de datos genricos usando una lista separada por comas.

Una vez que se ha creado una clase genrica se puede crear una instancia especfica de esa clase usando la siguiente forma general:

Nombredeclase <Tipo> ob;

Tipo es el nombre del tipo de datos sobre el que trabajar la clase. Los miembros de una clase genrica son, por s mismos, genricos.

La declaracin de una clase genrica es similar a la de una funcin genrica. El tipo real de datos almacenado por la lista, en la declaracin de la clase es genrico.

Una clase plantilla puede tener ms de un tipo genrico de datos (parmetro de tipo). Simplemente hay que declarar todos los tipos de datos requeridos por la clase en una lista separada por comas. Veamos el siguiente ejemplo:

class miclase<Tipo1,Tipo2>

{

Tipo1 i;

Tipo2 j;

public miclase(Tipo1 a, Tipo2 b)

{

i=a;

j=b;

}

public void Mostrar()

{

Console.WriteLine({0} {1}\n,i,j);

}

}

class MainClass

{

public static void Main(string[] args)

{

miclase<int, double> ob1= new miclase<int, double>(10,0.23);

miclase<char, string> ob2= new miclase<char, string>(X,Esto es una prueba);

ob1.Mostrar();

ob2.Mostrar();

string fin= Console.ReadLine();

return;

}

}

}

}

PARMETROS DE TIPO

Cuando una clase genrica es instanciada, lo hace con un parmetro de tipo, enlazando la implementacin de clase incompleta y sin forma con un tipo real, creando una instancia de clase genrica concreta, como se muestra en el siguiente ejemplo:

Cliente<ItemPedido> cliente = new Cliente<ItemPedido>();

Los parmetros de tipo se especifican siempre usando el nuevo operador de genricos <>.

Aunque podemos nombrar nuestros parmetros de tipo como deseemos, por convenio se suele definir la letra T como el nombre estndar para el primer parmetro de tipo. Si nuestra clase requiere parmetros adicionales, el convenio dicta que continuemos con las letras del alfabeto siguientes: U, V Si llegamos a la Z mediante parmetros de tipo, tendramos un problema de diseo tan grande que posiblemente los genricos por s solos no lo podran solucionar.

Para situaciones en las cuales el propsito del parmetro de tipo no es suficientemente explicativo por si mismo, Microsoft recomienda usar un nombre descriptivo para el parmetro de tipo predecido de la T mayscula, como se muestra en el siguiente cdigo:

public class MiGenerico<TClaseAcesoDatos>

Para especificar mltiples parmetros de tipo en una definicin de clase, simplemente debemos separarlas con comas:

public class MiGenerico<T, U, V>

Y para instanciar una clase que requiere mltiples parmetros de tipo, separaremos los tipos con comas como sigue:

MiGenerico<int, string, object> x = new MyGenerics<int, string, object>();

APLICANDO RESTRICCIONES A LOS PARMETROS DE TIPO

Cuando tenemos un parmetro de tipo especificado en nuestra definicin de clase, en realidad hay muy poco que podamos hacer con ello por defecto. Por ejemplo, cmo podemos crear una nueva instancia de ese objeto?. Podramos pensar que el siguiente cdigo debera estar libre de errores de compilacin:

public class Cliente<T>

{

public void AgregarItemDetalle(string nombreItem)

{

T nuevoItem = new T();

items.Add(nuevoItem);

}

}

Desgraciadamente, C# no tiene forma de conocer si el tipo de datos especificado por el parmetro T tiene un constructor por defecto, as que el compilador no permitir que usemos ese tipo en una nueva sentencia sin parmetros.

Para evitar esto, debemos usar restricciones en los parmetros de tipo. Estas restricciones especifican ciertos requerimientos que el parmetro de tipo ha de cumplir. Por ejemplo, podemos especificar que cualquier tipo pasado a nuestra clase genrica deba implementar un constructor (sin parmetros) por defecto.

Todas las restricciones en las clases genricas se indican con la palabra clave where y podemos usar mltiples restricciones en el mismo parmetro separndolas con una coma. Una muestra de cmo podemos especificar una restriccin se puede ver en el siguiente cdigo:

public class Cliente<TItemDetalle> where TItemDetalle : new()

La restriccin precedente indica que el parmetro de tipo especificado por TItemDetalle debe implementar un constructor por defecto. Las siguientes lneas muestran otras formas de especificar restricciones:

public class ListaClientes<T> where T: class, IListaItems
public class Cliente<T,U> where T: new() where U: IDatoCliente

La siguiente tabla muestra todas las restricciones que podemos aplicar a los parmetros de tipo genricos.

Restricciones de Parmetros de tipo Genricos

Restriccin

Descripcin

where T:struct

Indica que T debe ser un tipo valor (excepto Nullable)

where T:class

Indica que T debe ser un tipo referencia, incluyendo cualquier clase, delegado o interface.

where T:new()

Indica que T debe implementar un constructor por defecto. La restriccin new() debe ser especificada la ltima si se usa con varias restricciones en el mismo tipo.

where T: (clase base)

Indica que T debe ser o derivar de la clase base indicada.

where T: (interface)

Indica que T debe ser o implementar el interfaz indicado.

Un patrn de diseo muy utilizado en la OOP es el patrn factory o fbrica. En este patrn, el cdigo solicita nuevas instancias de un tipo dado desde esa fbrica de tipos ms que instanciarlos directamente. Esto da a la fbrica control completo sobre el proceso de instanciacin, adems de la posibilidad de hacer cosas como cachs de tipos preinstanciaos, ejecutar llamadas a Remoting o servicios Web para obtener datos de soporte, y mucho ms.

Para prevenir que la fbrica no llegue a tener demasiadas conversiones de tipos, el patrn ofrece llamadas para el desarrollo de una fbrica nica para cada clase. De esta forma, si tenemos una clase Cliente, siempre tendremos una clase FbricaClientes. Este modelo se usa en sistemas de ORM (Object-Relational Mapping) o en Container-Managed Persistente (CMP).

Para ver como podemos hacer un uso rpido de genricos y restricciones de parmetro de tipo, echemos un vistazo al siguiente cdigo, el cual crea una clase fbrica que puede servir tanto al tipo Cliente como al tipo ClienteEspecial:

public class FabricaClientes<TCliente, U> where TCliente : Cliente, new()
{
   public TCliente ObtenerCliente()
   {
     return new TCliente();
   }
}

El cdigo precedente restringe al tipo TCliente requiriendo que sea o derive del tipo Cliente adems de implementar un constructor por defecto. Ya que el argumento TCliente ha sido restringido con new(), el cdigo puede explcitamente crear una nueva instancia de ese tipo. A travs de la potencia de los genricos, este nuevo ejemplar no es otra cosa que el tipo exacto especificado. No se requiere entonces ninguna conversin de tipos en la clase fbrica, lo cual es un gran beneficio para los desarrolladores OOP.

INTERFACES GENRICOS

Un interface genrico funciona de una forma muy similar a como lo hace una clase genrica. El interface en s mismo acepta uno o ms parmetros de tipo de a misma forma que lo hace una clase genrica. En vez de utilizar el parmetro de tipo en la definicin de la clase, el parmetro de tipo es entonces usado en las sentencias de declaracin de miembros de las que consta el interface.

Un importante beneficio de permitir parmetros de tipo genricos en los interfaces es que las clases que implementan dichos Interfaces no necesitan ejecutar ningn boxing/unboxing innecesarios. Lo mismo podramos decir de las conversiones de tipos. Boxing/unboxing son las operaciones que permiten cambiar entre tipos valor y referencia, y son operaciones costosas. Veamos un ejemplo de interface genrico:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace Generics1
{
  public interface IManejadorDatos<TTipoFila> where TTipoFila : new()
  {
      void AgregarRegistro (TTipoFila registro);
      void BorrarRegistro(TTipoFila registro);
      void ActualizarRegistro(TTipoFila registro);
      void SeleccionarRegistro(TTipoFila registro);
      List<TTipoFila> SeleccionaRegistros();
  }
}

Lo que hace el anterior interface es indicar que cualquier clase que implemente IManejadorDatos<TTipoFila> debe proporcionar los mtodos estndar Agregar, Borrar, Actualizar y Seleccionar tpicamente asociados con objetos de acceso a datos independientemente del tipo de datos subyacente que sea almacenado. Este es un interfaz extremadamente til que nos va a permitir una gran flexibilidad.

 

* Basado en traduccin propia de Visual C# 2005 Unleashed de Kevin Hoffman

Constructor de copia y asignación de objetos en C++

Cuando se pasa un objeto a una funcin, o esta lo devuelve, se pueden presentar dificultades. El modo de evitar estos problemas consiste en definir un constructor de copia. Cuando se pasa un objeto a una funcin, se realiza una copia bit a bit de ese objeto y se guarda en el parmetro de la funcin que recibe el objeto. Sin embargo, hay casos en los que no es deseable la copia exacta del objeto. Por ejemplo, si el objeto contiene un puntero a la memoria asignada, la copia apuntar a la misma memoria que el objeto original. Por consiguiente, si la copia realiza un cambio sobre el contenido de esta memoria, esta ltima tambin cambiar para el objeto original. Cuando la funcin finaliza se destruye la copia mediante una llamada a su destructor, lo cual puede ocasionar efectos colaterales que afectaran al objeto original.

Se produce una situacin parecida cuando una funcin devuelve un objeto. Normalmente el compilador genera un objeto temporal que contiene una copia del valor devuelto por la funcin. ?‰ste desaparece una vez que se devuelve su valor a la rutina que provoc la llamada, mediante una llamada al destructor temporal. Sin embargo, si el destructor elimina alguna informacin necesaria por esa rutina (por ejemplo, liberar memoria asignada dinmicamente) continuarn los problemas.

Estos problemas derivan de la copia bit a bit, de tal forma que para prevenirlos, necesitamos definir de forma exacta que sucede cuando se hace una copia de un objeto de manera que podamos evitar estos efectos colaterales. La forma de llevar a cabo esto es mediante la creacin de un constructor de copia.

C++ define dos tipos distintos de situaciones en las que se da el valor de un objeto a otro. La primera es la asignacin. La segunda, la inicializacin, que puede tener lugar de tres formas:

Cuando se usa un objeto para inicializar otro en una sentencia de declaracin

Cuando se pasa un objeto como parmetro a una funcin

Cuando se crea un objeto temporal para ser usado como el valor devuelto por una funcin

El constructor de copias solo se aplica a la inicializacin, no a la asignacin.

Veamos este ejemplo:

#include <iostream.h>

class info{

private:

int a,b;

public:

void obtener(int *,int *);

info(int dato1,int dato2){

a=dato1;

b=dato2;

};

};

void info::obtener (int *dato1,int *dato2){

*dato1=a;

*dato2=b;

}

int main(){

info i1(1,2);

info i2=i1;

int d1,d2;

i1.obtener(&d1,&d2);

cout << «Atributos i1: a= » << d1 << » b= » << d2 << endl;

i2.obtener(&d1,&d2);

cout << «Atributos i2: a= » << d1 << » b= » << d2 << endl;

return 0;

}

Toda clase contiene un constructor de copia implcito que realiza una copia bit a bit del objeto origen al objeto destino como citamos anteriormente. Es lo que sucede en el ejemplo anterior. Por supuesto, es posible crear un nuevo constructor que oculte al existente por defecto. Para definir un constructor de copia debemos seguir estrictamente la siguiente sintaxis:

nombre_clase::nombre_clase (const nombreclase &)

Supongamos una clase mi_cadena. En ella, el constructor de copia por defecto creara problemas. El puntero a carcter del objeto recin creado apuntara a la misma cadena que el objeto existente. Al borrar cualquiera de los dos se liberara por la cadena, por lo que el puntero del otro objeto apuntar a una zona de memoria disponible para el programa. Cuando se procediese a borrar el segundo objeto, se producira un claro problema. El siguiente cdigo evitara males mayores:

mi_cadena::mi_cadena(const mi_cadena &origen){

clave=origen.clave;

cadena=new char [strlen(origen.cadena)+1];

strcpy(cadena, origen.cadena);

}

Por supuesto, aparte del cdigo anterior, habra que aadir a la parte pblica la siguiente lnea:

mi_cadena (const mi_cadena&);

Tambin es posible hacer una copia de un objeto en otro mediante el siguiente procedimiento:

clase objeto1,objeto2;

objeto2=objeto1;

En este caso, entra en accin el operador «=», que por defecto, tambin hace una copia miembro a miembro. Por fortuna, puede ser sobrecargado para conseguir el comportamiento adecuado. Veamos un ejemplo:

#include <iostream.h>

class miclase{

int a,b;

public:

void asigna(int i,int j){a=i,b=j;}

void muestra(){cout << a << ‘ ‘ << b << endl;}

};

main()

{

miclase o1,o2;

o1.asigna(10,4);

// asigna 01 a 02

o2=o1;

o1.muestra();

o2.muestra();

return 0;

}

Aqu el objeto o1 tiene sus variables miembro a y b fijadas con los valores 10 y 4 respectivamente. A continuacin, o1 se asigna a o2. Esto hace que el valor actual de o1.a se asigne a o2.a y o1.b se asigne a o2.b. Debemos tener en cuenta que una asignacin entre dos objetos simplemente hace que los datos de esos objetos sean idnticos. Los dos objetos estn completamente separados. Por ejemplo, despus de la asignacin, la llamada a o1.muestra() para establecer el valor de o1.a no tiene efecto en o2 o en su valor a.

Slo se pueden usar objetos del mismo tipo en una sentencia de asignacin. Si los objetos no son del mismo tipo, se informa de un error en tiempo de compilacin. No es suficiente, adems, con que los tipos sean fsicamente similares (mismos metodos, variables miembro…) han de tener el mismo nombre de tipo.

Es importante entender que todos los miembros de un objeto se asignan a otro cuando realizamos la copia, incluyendo arrays. Imaginemos un objeto pila s1 al que hemos introducido ya tres elementos; si una vez hecho esto, creamos otros objeto copia de s1, ya contendr esos tres elementos. Si la copia la hacemos antes de introducir los elementos, no los contendr la copia.

#include <iostream>

#define TAM 10

using namespace std;

// Declara una clase pila de caracteres

class pila {

char pil[TAM]; // guarda la pila

int cab; // ndice de la cabeza de la pila

public:

pila();

void push(char ch);

char pop();

};

// Inicializa la pila

pila::pila()

{

cout << «Construyendo una pila\n»;

cab=0;

}

void pila::push(char ch)

{

if (cab==TAM){

cout << «La pila est llena\n»;

return;

}

pil[cab]=ch;

cab++;

}

char pila::pop()

{

if (cab==0){

cout << «La pila est vaca\n»;

return 0;

}

cab–;

return pil[cab];

}

main()

{

pila s1,s2;

int i;

s1.push(‘a’);

s1.push(‘b’);

s1.push(‘c’);

s2=s1; // ahora s1 y s2 son idnticos

for (i=0;i<3;i++) cout << «Saca de s1: » << s1.pop() << endl;

for (i=0;i<3;i++) cout << «Saca de s2: » << s2.pop() << endl;

system(«PAUSE»);

return 0;

}

Debemos tener cuidado al asignar un objeto a otro y asegurarnos de no destruir informacin que pueda necesitarse posteriormente.

Veamos otro ejemplo que ilustra la necesidad de un constructor de copia. Este programa crea un tipo restringido de array de enteros ?seguro??? que previene que se sobrepasen sus lmites. Se asigna el espacio de cada array usando new y dentro de cada objeto array se mantiene un puntero a la memoria.

#include <iostream>

using namespace std;

class matriz {

int *p;

int tam;

public:

matriz(int ta){

p= new int[ta];

if (!p) exit(1);

tam=ta;

cout << «Uso del constructor ‘normal’\n»;

}

~matriz(){delete [] p;}

// constructor de copia

matriz (const matriz &a);

void put(int i, int j){

if (i>=0 && i<tam) p[i]=j;

}

int get(int i){

return p[i];

}

};

/* En el caso siguiente se asigna especificamente memoria

para la copia, y la direccin de esta memoria se asigna a p.

Por tanto, p no est apuntando a la misma memoria asignada

dinmicamente al objeto original */

matriz::matriz(const matriz &a){

int i;

p=new int [a.tam]; // asignacin de memoria para la copia

if (!p) exit(1);

for (i=0; i<a.tam;i++) p[i]=a.p[i]; // contenido de la copia

cout << «Uso del constructor de copia\n»;

}

int main ()

{

matriz num(10); // esta sentencia llama al constructor «normal»

int i;

// colocacin de algunos valores en el array

for (i=0; i<10; i++)

{

num.put(i,i);

}

// presentacin de num

for (i=9;i>=0;i–)

{ cout << num.get(i);}

cout << «\n»;

// creacin de otro array e inicializacin con num

matriz x = num; // esta entencia invoca al constructor de copia

// presentacin de x

for (i=0; i<10; i++)

{ cout << x.get(i);}

cout << «\n»;

system(«PAUSE»);

return 0;

}

Cuando num se usa para inicializar x, se llama al constructor de copia, se asigna memoria para el nuevo array y se almacena en x.p y el contenido de num se copia en el array de x. De esta forma, x y num tienen arrays que contienen los mismos valores, pero cada array es independiente y distinto, es decir, num.p y x.p no apuntan a la misma zona de memoria. Si el constructor de copia no hubiera sido creado, entonces la inicializacin bit a bit matriz x = num habra dado lugar a que los arrays de x y num compartieran la misma memoria.

Como ya dijimos antes, el constructor de copia solo es llamado para las inicializaciones. Por ejemplo, la siguiente secuencia no llama al constructor de copia definido en el ejemplo anterior:

matriz a(10);

matriz b(10);

b=a; // no llama al constructor de copia

En este caso, b=a realiza la operacin de asignacin.

Veamos de nuevo el ejemplo de tipo cadena que nos muestra como el constructor de copia ayuda a prevenir algunos problemas sobrevenidos con el paso de tipos de objetos a funciones, observemos el siguiente programa (incorrecto):

// Este programa tiene un error

#include <iostream>

using namespace std;

class tipocad{

char *p;

public:

tipocad(char *s);

~tipocad(){delete [] p;}

char *obtener() {return p;}

};

tipocad::tipocad(char *s)

{

int l;

l=strlen(s);

p=new char[l];

if (!p){

cout << «Error de asignacin\n»;

exit(1);

}

strcpy(p,s);

}

void mostrar(tipocad x)

{

char *s;

s=x.obtener();

cout << s << endl;

}

int main()

{

tipocad a(«Hola»),b(«mundo»);

mostrar(a);

mostrar(b);

system(«PAUSE»);

return 0;

}

En este programa, cuando un objeto tipocad se pasa a mostrar(), se hace una copia bit a bit y se guarda en el parmetro x. De este modo, cuando finaliza la funcin, x pierde su valor y se elimina. Esto, por supuesto, da lugar a una llamada al destructor de x, que libera x.p. Sin embargo, la memoria que se ha liberado es la misma que todava est siendo utilizada por el objeto empleado para llamar a una funcin. Esto conduce a un error. La solucin consiste en definir un constructor de copia para la clase tipocad que asigne memoria a la copia cuando se cree. Este es el enfoque usado en el siguiente programa corregido:

/* Este programa usa un constructor de copia para permitir que los objetos

tipocad sean pasados a funciones */

#include <iostream>

using namespace std;

class tipocad{

char *p;

public:

tipocad(char *s);

tipocad(const tipocad &o); // constructor de copia

~tipocad(){delete [] p;}

char *obtener() {return p;}

};

tipocad::tipocad(char *s)

{

int l;

l=strlen(s);

p=new char[l];

if (!p){

cout << «Error de asignacin\n»;

exit(1);

}

strcpy(p,s);

}

// Constructor de copia

tipocad::tipocad(const tipocad &o)

{

int l;

l=strlen(o.p);

p=new char[l]; // asigna memoria para la nueva copia

if (!p){

cout << «Error de asignacin\n»;

exit(1);

}

strcpy(p,o.p); // copia de la cadena en la copia

}

void mostrar(tipocad x)

{

char *s;

s=x.obtener();

cout << s << endl;

}

int main()

{

tipocad a(«Hola»), b(» mundo»);

mostrar(a);

mostrar(b);

system(«PAUSE»);

return 0;

}

Ahora, cuando finaliza mostrar() y x pierde su valor, la memoria apuntada por x.p (que se liberar) no es la misma que la usada por el objeto pasado a la funcin.

Vamos a proponer un experimento. Qu sucede si un objeto de una clase derivada se asigna a otro objeto de la misma clase derivada?. Se copia tambin la informacin asociada con la clase base?. La respuesta es s, la informacin de la clase base tambin se copia cuando un objeto de una clase derivada se asigna a otro. El siguiente ejemplo lo demuestra:

#include <iostream>

using namespace std;

class base {

int a;

public:

void carga_a(int n) {a=n;}

int obtiene_a() {return a;}

};

class derivada : public base {

int b;

public:

void carga_b(int n) {b=n;}

int obtiene_b() {return b;}

};

int main()

{

derivada ob1, ob2;

ob1.carga_a(5);

ob1.carga_b(10);

// asigna ob1 a ob2

ob2=ob1;

cout << «Aqu est a y b de ob1: «;

cout << ob1.obtiene_a() << ‘ ‘ << ob1.obtiene_b() << «\n»;

cout << «Aqu est a y b de ob2: «;

cout << ob2.obtiene_a() << ‘ ‘ << ob2.obtiene_b() << «\n»;

// Como es de suponer, la salida es igual para ambos

system(«PAUSE»);

return EXIT_SUCCESS;

Conexión JDBC contra SQL Server y clases de acceso básico

CONEXI?“N DE JAVA CON SQL SERVER

Para realizar una conexin contra SQL Server 2005, es necesario descargar y usar el driver que para este cometido ha desarrollado Microsoft (Controlador JDBC de Microsoft SQL Server 2005).

El controlador JDBC no forma parte del SDK de Java. Por consiguiente, debemos establecer la ruta de clase para que incluya el archivo sqljdbc.jar si deseamos usarlo. Si en la ruta de clase falta una entrada para sqljdbc.jar, la aplicacin genera la excepcin comn «Clase no encontrada».

El archivo sqljdbc.jar se instala en la siguiente ubicacin:

<directorio de instalacin>\sqljdbc_<version>\<language>\sqljdbc.jar

El siguiente es un ejemplo de la instruccin CLASSPATH usada para una aplicacin de Windows:

CLASSPATH =.;C:\Program Files\Microsoft SQL Server 2005 JDBC Driver\sqljdbc_1.1\enu\sqljdbc.jar

El siguiente es un ejemplo de la instruccin CLASSPATH usada para una aplicacin de Unix/Linux:

CLASSPATH =.:/home/usr1/mssqlserver2005jdbc/Driver/sqljdbc_1.1/enu/sqljdbc.jar

Nota: En los sistemas de Windows, los nombres de directorio mayores que 8,3 o los nombres de carpetas con espacios pueden causar problemas con las rutas de clase. Si vemos que se da este tipo de problema, debemos mover temporalmente el archivo sqljdbc.jar a un nombre de directorio sencillo como C:\Temp, cambiar la ruta de clase y determinar si de este modo se corrige el problema.

Aplicaciones que se ejecutan directamente en el smbolo del sistema

La ruta de clase se configura en el sistema operativo. Anexamos sqljdbc.jar a la ruta de clase del sistema. Adems, podemos especificar la ruta de clase en la lnea de comandos de Java que ejecuta la aplicacin con la opcin java -classpath .

Aplicaciones que se ejecutan en un IDE

Cada proveedor de IDE ofrece un mtodo distinto para establecer la ruta de clase en el IDE. Establecer sin ms la ruta de clase en el sistema operativo no funciona. Debemos agregar sqljdbc.jar a la ruta de clase del IDE.

Servlets y JSP

Los servlets y JSP se ejecutan en un motor de servlet/JSP como Tomcat. La ruta de clase se debe establecer de acuerdo con la documentacin del motor de servlet/JSP. Establecer sin ms la ruta de clase en el sistema operativo no funciona. Algunos motores de servlet/JSP incluyen pantallas de configuracin que puede utilizar para establecer la ruta de clase del motor. En este caso, debemos adjuntar el archivo JAR del controlador JDBC correcto a la ruta de clase del motor y reiniciar el motor. En otras situaciones, podemos configurar el controlador copiando sqljdbc.jar en un directorio especfico, con extensin lib, durante la instalacin del motor. La ruta de clase del controlador del motor tambin se puede especificar en un archivo de configuracin especfico del motor.

Enterprise Java Beans

Enterprise Java Beans (EJB) se ejecuta en un contenedor EJB. Los contenedores EJB son distribuidos por varios proveedores. Los subprogramas Java se ejecutan en un explorador, pero se descargan desde un servidor web. Tenemos que copiar sqljdbc.jar en la raz del servidor web y especificar el nombre del archivo JAR en la ficha de archivo HTML del subprograma (por ejemplo, <applet ... archive=sqljdbc.jar>).

Establecer una conexin sencilla con una base de datos

Para establecer una conexin a una base de datos con la clase DriverManager, debemos registrar primero el controlador como en el ejemplo siguiente:

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");

Una vez cargado el controlador, puede establecer una conexin con una URL de conexin:

String connectionUrl = "jdbc:sqlserver://localhost:1433;" +
   "databaseName=AdventureWorks;user=MyUserName;password=*****;";
Connection con = DriverManager.getConnection(connectionUrl);

Si conectamos contra un servidor SQL Express, seguramente tengamos que aadir el nombre de la instancia:

String url = «jdbc:sqlserver://localhost;instanceName=SQLEXPRESS;databaseName=nombrebd;user=usuario;password=********;»;

CLASES JDBC DE ACCESO B??SICO A BASES DE DATOS

Las clases fundamentales de JDBC son, Statement y ResultSet. La clase java.sql.Statement es la ms bsica de las tres que representan sentencias SQL. El mtodo executeQuery() toma una cadena SQL como argumento y devuelve un objeto ResultSet. Por otro lado, las sentencias de actualizacin se ejecutan con el mtodo executeUpdate() el cual adems devuelve el nmero de filas afectadas.

La clase Statement proporciona un mtodo execute() para operaciones en las que no se sepa si el cdigo SQL es una consulta o una actualizacin. Esto suele pasar cuando la aplicacin est ejecutando sentencias SQL creadas dinmicamente. Si la sentencia devuelve una fila de la base de datos, el mtodo devuelve true, y false en caso contrario. La aplicacin puede entonces usar el mtodo getResultSet() para obtener la fila devuelta.

java.sql.Resultset es un conjunto de filas de datos que devuelve una consulta. La clase proporciona simplemente una serie de mtodos para recuperar columnas de los resultados de una consulta. Los mtodos para obtener una columna tienen la siguiente sintaxis:

type get type(int | String)

El argumento representa el nmero o el nombre de la columna deseada. Un curioso efecto colateral de este diseo es que se pueden almacenar valores de un tipo en la base de datos y recuperarlos en otro tipo de valor distinto. Por ejemplo, si necesitamos que un dato Date (fecha) de la base de datos aparezca como cadena (String) podemos llamar a result_set.getString(1) en vez de a result_set.getDate(1).

Como la clase ResultSet slo maneja una fila de la base de datos cada vez, la clase proporciona el mtodo next() para invocar a la siguiente fila. Si devuelve true, tiene otra fila que procesar, y cualquier llamada subsiguiente que realizara al objeto ResultSet har referencia a la siguiente fila. Si no quedan ms filas, devolver false.

* Extrado de la documentacin de la clase JDBC de acceso a SQL Server de Microsoft y del libro Programacin de bases de datos con JDBC y Java de George Reese

Sobrecarga de funciones constructoras en C++

Es posible sobrecargar la funcin constructora de una clase, en cambio, no lo es con una funcin destructora. La necesidad de esta sobrecarga se fundamenta en tres razones: ganar flexibilidad, permitir arrays y crear constructores de copia.

Uno de los usos ms frecuentes de las funciones constructoras sobrecargadas es el de ofrecer la opcin de inicializar o no un objeto. Por ejemplo, en el siguiente ejemplo, o1 recibe un valor inicial y o2 no. Si se elimina el constructor que tiene la lista de argumentos vaca, el programa no se compilar porque no hay ningn constructor que coincida con un objeto sin inicializar de tipo ?muestra???. Lo contrario tambin es cierto: Si se elimina un constructor parametrizado, el programa no compilar porque no hay un modelo para el objeto inicializado. Se necesitan ambos para que el programa compile correctamente.

#include <iostream>

using namespace std;

class miclase {

int x;

public:

// dos formas de sobrecarga de un constructor

miclase() {x=0;} // sin inicializador

miclase(int n) {x=n;} // inicializador

int obtenerx() {return x;}

};

int main(int argc, char *argv[])

{

miclase o1(10); //declaracin con valor inicial

miclase o2; // declaracin sin inicializador

cout << «o1: « << o1.obtenerx() << endl;

cout << «o2: « << o2.obtenerx() << endl;

system(«PAUSE»);

return EXIT_SUCCESS;

}

Otra razn tpica por la que se sobrecarga un constructor es para permitir que tanto objetos como arrays de objetos aparezcan dentro de un programa. Para inicializar arrays de objetos sin inicializar, junto con objetos inicializados, debe incluirse un constructor que permita la inicializacin y otro no.

Por ejemplo, partiendo de la clase anterior, estas dos declaraciones son vlidas:

Miclase ob(10);

Miclase ob(5);

El hecho de proporcionar constructores de inicializacin y de no inicializacin da lugar a que las variables puedan inicializarse o no, segn se necesite. Por ejemplo, este programa declara dos arrays del tipo miclase: uno inicializado y otro no:

#include <iostream>

using namespace std;

class miclase {

int x;

public:

// dos formas de sobrecarga de un constructor

miclase() {x=0;} // sin inicializador

miclase(int n) {x=n;} // inicializador

int obtenerx() {return x;}

};

int main(int argc, char *argv[])

{

miclase o1[10]; //declaracin del array sin inicializadores

// declaracin con inicializadores

miclase o2[10] = {1,2,3,4,5,6,7,8,9,10};

int i;

for (i=0; i<10; i++){

cout << «o1[« << i << «]: « << o1[i].obtenerx() << endl;

cout << «o2[« << i << «]: « << o2[i].obtenerx() << endl;

}

system(«PAUSE»);

return EXIT_SUCCESS;

}

En este ejemplo, la funcin constructora pone a cero todos los elementos de o1. Los elementos de o2 se inicializan segn se muestra en el programa.

Otra razn para sobrecargar los constructores es permitir que el programador seleccione el mtodo ms conveniente de inicializar un objeto. Para ver cmo se lleva a cabo, primero veamos el siguiente ejemplo, que crea una clase que contiene las fechas de un calendario. El constructor fecha() se sobrecarga de dos formas: en una de ellas, aceptando como una cadena de caracteres la fecha y en la otra, pasndola como tres enteros.

#include <iostream>

#include <stdio.h> // Para usar sscanf

using namespace std;

class fecha {

int dia, mes, anyo;

public:

fecha(char *cad);

fecha(int d, int m, int a) {

this->dia=d;

this->mes=m;

this->anyo=a;

}

void mostrar(){

cout << dia << ‘/’ << mes << ‘/’ << anyo << endl;

}

};

fecha::fecha(char *cad)

{

sscanf(cad, «%d%*c%d%*c%d»,&dia, &mes, &anyo);

}

int main(int argc, char *argv[])

{

// construccin del objeto fecha usando una cadena

fecha sfecha(«1/11/95»);

// construccin del objeto fecha usando enteros

fecha ifecha(1,11,95);

sfecha.mostrar();

ifecha.mostrar();

system(«PAUSE»);

return EXIT_SUCCESS;

}

Aunque es posible sobrecargar un constructor tantas veces como queramos, realizarlo en exceso tiene un efecto destructivo sobre la clase. Desde el punto de vista del estilo, lo ms adecuado es sobrecargar un constructor para mejorar solamente aquellas situaciones con alta probabilidad de ocurrir frecuentemente. Por ejemplo, sobrecargar fecha() una tercera vez de manera que se pueda introducir la fecha como enteros en octal, no tiene mucho sentido. Sin embargo, si lo tiene sobrecargarla para aceptar un objeto time_t, lo que podra ser muy til.

Existe otra situacin donde ser necesario sobrecargar el constructor: cuando se asigna un array dinmico de esa clase. Ya que un array dinmico no puede inicializarse, si la clase contiene un constructor que tiene un inicializador, debe incluirse una versin sobrecargada que no tenga inicializador. Por ejemplo, a continuacin vemos un programa que asigna un array de objetos dinmicamente:

#include <iostream>

using namespace std;

class miclase {

int x;

public:

// dos formas de sobrecargar un constructor

miclase() {x=0;}

miclase(int n){x=n;}

int obtenerx() {return x;}

void ajustarx(int n){x=n;}

};

int main(int argc, char *argv[])

{

miclase *p;

miclase ob(10); // inicializacin de una nica variable

p = new miclase[10]; // aqu no pueden usarse inicializadores

if (!p){

cout << «Error de asignacin\n»;

return 1;

}

int i;

// inicializacin de todos los elementos de ob

for(i=0;i<10;i++) p[i]=ob;

for (i=0;i<10;i++){

cout << «p[« << i << «]: « <<p[i].obtenerx() << endl;

}

system(«PAUSE»);

return EXIT_SUCCESS;

}

Sin la versin sobrecargada de miclase() que no tiene inicializador, la sentencia new habra generado un error en tiempo de compilacin.

* Transcrito del libro de Herbert Schildt sobre Programacin en C++