Anda di halaman 1dari 52

Conectar con datos y recuperarlos en ADO.

NET
La principal funcin de cualquier aplicacin de base de datos es conectarse a un origen de datos y recuperar los datos contenidos . Los proveedores de datos de .NET Framework para A !.NET sirven como puente entre una aplicacin y un origen de datos" permiti#ndole e$ecutar comandos y recuperar datos mediante un DataReader o un DataAdapter.

En esta seccin

Conectar a orgenes de datos


En A !.NET se utili%a un ob$eto Connection para conectar con un determinado origen de datos mediante una cadena de cone&in en la que se proporciona la informacin de autenticacin necesaria. El ob$eto Connection utili%ado depende del tipo de origen de datos. 'ada proveedor de datos de .NET Framework incluido en .NET Framework cuenta con un ob$eto Connection( el proveedor de datos de .NET Framework para !LE ) incluye un ob$eto !le b'onnection" el proveedor de datos de .NET Framework para *+L *erver incluye un ob$eto *ql'onnection" el proveedor de datos de .NET Framework para ! )' incluye un ob$eto !dbc'onnection y el proveedor de datos de .NET Framework para !racle incluye un ob$eto !racle'onnection.

En esta seccin

Establecimiento de una conexin


,ara conectarse a -icrosoft *+L *erver ../ o posterior" utilice el ob$eto *ql'onnection del proveedor de datos de .NET Framework para *+L *erver. ,ara conectarse a un origen de datos !LE ) o a -icrosoft *+L *erver 0.x o una versin anterior" utilice el ob$eto !le b'onnection del ). ,ara conectarse a un origen de datos ! )'" proveedor de datos de .NET Framework para !LE

utilice el ob$eto !dbc'onnection del proveedor de datos de .NET Framework para ! )'. ,ara conectarse a un origen de datos !racle" utilice el ob$eto !racle'onnection del proveedor de datos de .NET Framework para ! )'. ,ara almacenar y recuperar de forma segura cadenas de cone&in" vea ,roteger cadenas de cone&in.

Cierre de conexiones
1ecomendamos cerrar siempre la cone&in cuando termine de utili%arla" para que la cone&in pueda regresar al grupo. El bloque Using de 2isual )asic o '3 elimina autom4ticamente la cone&in cuando el cdigo sale del bloque" incluso en el caso de una e&cepcin no controlada. Tambi#n puede utili%ar los m#todos Close o Dispose del ob$eto de cone&in correspondiente al proveedor que est# utili%ando. Es posible que las cone&iones que no se cierran e&pl5citamente no se puedan agregar ni puedan regresar al grupo. ,or e$emplo" una cone&in que se 6a salido del 4mbito pero que no se 6a cerrado e&pl5citamente slo se devolver4 al grupo de cone&in si se 6a alcan%ado el tama7o m4&imo del grupo y la cone&in a8n es v4lida. ,ara obtener m4s informacin" vea escripcin de agrupacin de cone&iones.

Nota

No llame a Close o a Dispose en un objeto Connection, un objeto DataReader o cualquier otro objeto administrado en el mtodo Finalize de la clase. En un finalizador, libere slo los recursos no administrados que pertenezcan directamente a su clase. Si la clase no dispone de recursos no administrados, no incluya un mtodo Finalize en la definicin de clase. Para obtener ms informacin, vea ecoleccin de elementos no utilizados.

Conexin a SQ Ser!er
El proveedor de datos de .NET Framework para *+L *erver admite un formato de cadena de cone&in que es similar al de !LE cadena" vea 'onnection*tring. El siguiente cdigo de e$emplo demuestra cmo crear y abrir una cone&in a una base de datos *+L *erver ../ o posterior. '3 'opiar cdigo ) 9A !:. ,ara ver los nombres y valores v4lidos de formato de

// Assumes connectionString is a valid connection string. using (SqlConnection connection = new SqlConnection(connectionString)) { connection.Open(); // Do wor !ere. "
Seguridad integrada y ASP.NET La seguridad integrada de *+L *erver 9tambi#n conocida como cone&iones de confian%a: ayuda a proteger las cone&iones a *+L *erver dado que no e&pone el ;d. y la contrase7a de un usuario en la cadena de cone&in y es el m#todo recomendado para autenticar una cone&in. La seguridad integrada utili%a la identidad de seguridad actual" o s5mbolo 9token:" del proceso en e$ecucin" que en aplicaciones de escritorio" es normalmente la identidad del usuario que actualmente 6a iniciado la sesin. La identidad de seguridad para aplicaciones A*,.NET se puede establecer en una de varias opciones diferentes. ,ara comprender me$or la identidad de seguridad que utili%a una aplicacin A*,.NET al establecer una cone&in a *+L *erver" vea *uplantacin de A*,.NET" Aut6entication and A*,.NET ;mpersonation y 'mo( !btener acceso a *+L *erver mediante la seguridad integrada de <indows.

Conexin a un origen de datos O E D"


El proveedor de datos de .NET Framework para !LE datos e&puestos mediante !LE *+L!LE )" el proveedor de !LE ) proporciona conectividad a or5genes de

) y a -icrosoft *+L *erver 0. x o anterior 9a trav#s de ) para *+L *erver:" utili%ando el ob$eto OleDbConnection. )" el formato de cadena de cone&in es

En el proveedor de datos de .NET Framework para !LE

id#ntico al utili%ado en A !" con las siguientes e&cepciones(

*e necesita la palabra clave Proveedor. No se admiten las palabras clave URL" Proveedor remoto y Servidor remoto. )" vea el tema

,ara obtener m4s informacin acerca de las cadenas de cone&in !LE 'onnection*tring.

Nota El objeto OleDbConnection no admite la confi!uracin ni la recuperacin de propiedades dinmicas espec"ficas de un

proveedor de #$E %&. Slo se admiten las propiedades que se pueden proporcionar en la cadena de cone'in para el proveedor de #$E %&. El siguiente cdigo de e$emplo demuestra cmo crear y abrir una cone&in a un origen de datos !LE '3 'opiar cdigo ).

// Assumes connectionString is a valid connection string. using (OleD#Connection connection = new OleD#Connection(connectionString)) { connection.Open(); // Do wor !ere. "
No utilice arc#i!os de !nculo de datos uni!ersal
*e puede proporcionar informacin de cone&in para un ob$eto OleDbConnection en un arc6ivo de v5nculos de datos universal 9= L:" pero conviene evitarlo. Los arc6ivos = L no est4n cifrados y e&ponen la informacin de cadena de cone&in en te&to sin cifrar. =n arc6ivo = L no se puede proteger mediante .NET Framework" ya que se trata de un recurso basado en un arc6ivo e&terno a la aplicacin.

Conexin a un origen de datos OD"C


El proveedor de .NET Framework para ! )' proporciona conectividad a or5genes de datos e&puestos mediante ! )' utili%ando el ob$eto OdbcConnection. En el proveedor de datos de .NET Framework para ! )'" el formato de cadena de cone&in est4 dise7ado para que coincida lo m4s posible con el de ! )'. Tambi#n puede proporcionar un nombre de origen de datos 9 *N: ! )'. ,ara obtener informacin m4s detallada acerca de OdbcConnection" vea la 'lase !dbc'onnection.

Nota El proveedor de datos de .NE( )rame*or+ para #%&, no se incluye en .NE( )rame*or+ -... Si necesita utilizar el proveedor de datos de .NE( )rame*or+ para #%&, y est utilizando .NE( )rame*or+ -.., puede descar!arlo en en este sitio /eb de 0icrosoft. El espacio de nombres del proveedor de datos de .NE( )rame*or+ para #%&, descar!ado es Microsoft.Data.Odbc. El siguiente cdigo de e$emplo demuestra cmo crear y abrir una cone&in a un origen de datos ! )'. '3 'opiar cdigo

// Assumes connectionString is a valid connection string. using (Od#cConnection connection = new Od#cConnection(connectionString)) { connection.Open(); // Do wor !ere. "
Conexin a un origen de datos Oracle
El proveedor de .NET Framework para !racle proporciona conectividad a or5genes de datos !racle utili%ando el ob$eto OracleConnection.

En el proveedor de datos de .NET Framework para !racle" el formato de cadena de cone&in est4 dise7ado para que coincida lo m4s posible con el del proveedor de !LE !racle'onnection. El siguiente cdigo de e$emplo demuestra cmo crear y abrir una cone&in a un origen de datos !racle. '3 'opiar cdigo ) para !racle 9-* A!1A:. ,ara obtener informacin m4s detallada acerca de OracleConnection" vea la 'lase

// Assumes connectionString is a valid connection string. using (OracleConnection connection = new OracleConnection(connectionString)) { connection.Open(); // Do wor !ere. " OracleConnection nwindConn = new OracleConnection($Data Source=%&OracleServer;'ntegrated Securit&=&es;$); nwindConn.Open();

Traba$o con cadenas de conexin


=na cadena de cone&in contiene informacin de iniciali%acin que se transfiere como un par4metro a un origen de datos. La cadena de cone&in se anali%a inmediatamente una ve% que se 6a configurado. Los errores de sinta&is generar4n una e&cepcin de tiempo de e$ecucin" pero otros errores slo podr4n 6allarse despu#s de que el origen de datos 6a validado la informacin de la cadena de cone&in. =na ve% validada" el origen de datos establece varias opciones que permiten la cone&in.

%alabras cla!e de cadena de conexin


El formato de una cadena de cone&in es una lista de pares de par4metros de clave y valor delimitados por punto y coma. 'opiar cdigo

e&word(=value; e&word)=value
Los espacios se omiten y las palabras clave no distinguen entre may8sculas y min8sculas" si bien los valores podr5an presentar esta distincin" seg8n la diferenciacin entre may8sculas y min8sculas del origen de datos. ,ara incluir valores que contengan un punto y coma" un car4cter de comilla sencilla o un car4cter de comilla doble" el valor debe colocarse entre comillas dobles. La sinta&is v4lida de cadena de cone&in var5a seg8n el proveedor y 6a evolucionado a lo largo de los a7os desde las primeras A,; como ! )'. El proveedor de datos de .NET Framework para *+L *erver incorpora muc6os elementos de la sinta&is antigua y" por lo general" es m4s tolerante con la sinta&is com8n de cadena de cone&in. ,ara obtener m4s informacin acerca de las palabras clave de cadena de cone&in de los proveedores de datos .NET" vea 'onnection*tring" 'onnection*tring" 'onnection*tring y 'onnection*tring.

%ersist Security &n'o


El valor predeterminado de la palabra clave *ersist Securit& 'n+o en una cadena de cone&in es false. 'opiar cdigo

*ersist Securit& 'n+o=+alse;

*i se establece en true o yes" permitir4 obtener informacin de seguridad confidencial" incluidos el ;d. de usuario y la contrase7a" de la cone&in una ve% que est# abierta. *i es necesario proporcionar un ;d. de usuario y una contrase7a al reali%ar una cone&in" estar4 completamente protegido si esa informacin se descarta una ve% que se 6aya utili%ado para abrir la cone&in> esto sucede cuando Persist Security Info se establece como false o no. Esto es especialmente importante si proporciona una cone&in abierta a un origen que no sea de confian%a o guarda la informacin de cone&in en disco. *i se mantiene el valor Persist Security Info como false" se contribuye a que un origen que no sea de confian%a no tenga acceso a la informacin de seguridad confidencial de la cone&in y contribuye" adem4s" a que no se guarde en el disco ning8n tipo de informacin de seguridad confidencial que contenga informacin de su cadena de cone&in.

(so de autenticacin de )indo*s


,ara conectarse a bases de datos de servidor se recomienda utili%ar la autenticacin de <indows" conocida com8nmente como seguridad integrada. ,ara especificar la autenticacin de <indows" puede utili%ar cualquiera de los dos siguientes pares de clave y valor con el proveedor de datos de .NET Framework para *+L *erver( 'opiar cdigo

'ntegrated Securit&=true; 'ntegrated Securit&=SS*';


*in embargo" slo el segundo funciona con el proveedor de datos de .NET Framework para !le b. *i establece 'ntegrated Securit&=true para una ConnectionString" se producir4 una e&cepcin. ,ara especificar la autenticacin de <indows en el proveedor de datos de .NET Framework para ! )'" deber4 utili%ar el siguiente par de clave y valor. 'opiar cdigo

,rusted-Connection=&es;
Creacin de cadenas de conexin
'ada uno de los proveedores de datos de .NET Framework proporciona una clase generadora de cadenas de cone&in con establecimiento infle&ible de tipos que se 6ereda de b'onnection*tring)uilder. Los generadores de cadenas de cone&in permiten a los programadores crear mediante programacin cadenas de cone&in sint4cticamente correctas basadas en la informacin introducida por el usuario" as5 como anali%ar y reconstruir cadenas de cone&in e&istentes. ,ara obtener m4s informacin" vea *ql'onnection*tring)uilder" !le b'onnection*tring)uilder" !dbc'onnection*tring)uilder y !racle'onnection*tring)uilder.

Almacenamiento y recuperacin de cadenas de conexin


*e recomienda no incrustar cadenas de cone&in en el cdigo. *i la ubicacin del servidor cambiara alguna ve%" ser5a necesario volver a compilar la aplicacin. Adem4s" las cadenas de cone&in sin cifrar compiladas en el cdigo fuente de una aplicacin se pueden ver con el esensamblador -*;L 9ildasm.e&e:. Almacenar cadenas de conexin en archivos de configuracin ,ara evitar almacenar cadenas en el cdigo" puede almacenarlas en el arc6ivo web config en una aplicacin A*,.NET y en el arc6ivo app config en una aplicacin para <indows.

La cadena de cone&in se puede almacenar en el arc6ivo de configuracin en el elemento !connectionStrings". Las cadenas de cone&in se almacenan como pares de clave y valor" donde el nombre se puede utili%ar para buscar el valor almacenado en el atributo connectionString en tiempo de e$ecucin. En el siguiente e$emplo de arc6ivo de configuracin" se muestra una cadena de cone&in llamada atabase'onnection que 6ace referencia a una cadena de cone&in que se conecta a una instancia local de *+L *erver. 'opiar cdigo

.connectionStrings/ .add name=$Data#aseConnection$ connectionString=$*ersist Securit& 'n+o=0alse;'ntegrated Securit&=SS*';data#ase=1ort!wind;server=(local);$ provider1ame=$S&stem.Data.SqlClient$ // ./connectionStrings/


Recuperar cadenas de conexin de archivos de configuracin El espacio de nombres *ystem.'onfiguration proporciona clases para traba$ar con informacin de configuracin almacenada en arc6ivos de configuracin. La clase 'onnection*tring*ettings tiene dos propiedades que se corresponden con los nombres que se muestran en el anterior e$emplo de seccin !connectionStrings". 'onnection*tring 'adena de cone&in. Name Nombre de la cadena de cone&in en la seccin !connectionStrings".

Nota de seguridad #pcionalmente, puede utilizar confi!uracin prote!ida para cifrar las cadenas de cone'in almacenadas en arc1ivos de confi!uracin. 2ea ,ifrar informacin de confi!uracin mediante una confi!uracin prote!ida, Encryptin! and %ecryptin! ,onfi!uration Sections y (utorial3 ,ifrar la informacin de confi!uracin mediante la confi!uracin prote!ida. Ejemplo En el siguiente e$emplo se recupera la cadena de cone&in del arc6ivo de configuracin pasando el nombre de la dic6a cadena al 'onfiguration-anager" que devuelve un ob$eto ConnectionStringSettings. La propiedad ConnectionString sirve para mostrar el valor. '3 'opiar cdigo

using S&stem; using S&stem.Con+iguration; class *rogram { static void %ain() { ConnectionStringSettings settings; settings = Con+iguration%anager.ConnectionStrings2$Data#aseConnection$3; i+ (settings 4= null) { Console.5rite6ine(settings.ConnectionString); " " "
Cadenas de conexin S+lClient
La propiedad ConnectionString de una *ql'onnection permite obtener o establecer una cadena de cone&in para una base de datos *+L *erver ../ o posterior. *i necesita conectarse a una versin anterior de *+L *erver" deber4 utili%ar el proveedor de datos .NET para !le b. Sintaxis de cadena de conexin S l!lient La sinta&is para conectarse a una base de datos *+L *erver es fle&ible. En cada una de las siguientes formas de sinta&is se utili%ar4 seguridad integrada para conectarse a la base de datos

Adventure#or$s en un servidor local. Especifique siempre el servidor por el nombre o por la palabra clave (local). 'opiar cdigo

$*ersist Securit& 'n+o=0alse;'ntegrated Securit&=true;'nitial Catalog=Adventure5or s;Server=%SS76($ $*ersist Securit& 'n+o=0alse;'ntegrated Securit&=SS*';data#ase=Adventure5or s;server=(local)$ $*ersist Securit& 'n+o=0alse;,rusted-Connection=,rue;data#ase=Adventure5or s;server=(local)$
,ara for%ar un protocolo" agregue uno de los siguientes prefi$os(

np8(local)9 tcp8(local)9 lpc8(local)


En la autenticacin de *+L *erver" utilice esta sinta&is para especificar un nombre de usuario y una contrase7a" donde los asteriscos representan un nombre de usuario y una contrase7a v4lidos. 'opiar cdigo

$*ersist Securit& 'n+o=0alse;:ser 'D=;;;;;;*assword=;;;;;;'nitial Catalog=Adventure5or s;Server=%&SqlServer$


,ara obtener una lista completa de palabras clave v4lidas de cadena de cone&in" vea ConnectionString. !onexin a instancias con nom"re ,ara conectarse a una instancia con nombre de *+L *erver ?/// o posterior" utilice la sinta&is nombre de servidor\nombre de instancia. 'opiar cdigo

Server=%&SqlServer<%SS76(;$
!onfiguracin de la "i"lioteca de red =tilice esta sinta&is para conectarse mediante una direccin ;," donde la biblioteca de red es <in@? <insock T',A;, y BC@@ es el puerto que se va a utili%ar 9el predeterminado:. 'opiar cdigo

1etwor 6i#rar&=d#mssocn;Data Source====.===.===.===9(>??;


*+L *erver permite utili%ar las siguientes bibliotecas de red al establecer una cone&in. dbnmpntw 'anali%aciones con nombre <in@? dbmssocn <in@? <insock T',A;, dbmssp&n <in@? *,DA;,D dbmsvinn <in@? )anyan 2ines dbmsrpcn -ultiprotocolo <in@? 9<indows 1,':

Cadenas de conexin OleDb

La propiedad ConnectionString de una !le b'onnection permite obtener o establecer una cadena de cone&in para un origen de datos !LE )" como -icrosoft Access o *+L *erver 0.E o anterior. En *+L *erver ../ o superior" utilice una S%lConnection. Sintaxis de cadena de conexin #le$" ,ara una cadena de cone&in OleDbConnection e&istente" debe proporcionar un nombre de proveedor. La siguiente cadena de cone&in conecta a una base de datos -icrosoft Access mediante el proveedor Fet. Tenga en cuenta que las palabras clave UserID y Password son opcionales si la base de datos no est4 protegida 9el valor predeterminado:. 'opiar cdigo

*rovider=%icroso+t.@et.O6ADB.>.=; Data Source=d8<1ort!wind.md#;:ser 'D=Admin;*assword=;


*i la base de datos est4 protegida" deber4 proporcionar la ubicacin del arc6ivo de informacin del grupo de traba$o. 'opiar cdigo

*rovider=%icroso+t.@et.O6ADB.>.=;Data Source=d8<1ort!wind.md#;@et O6ADB8S&stem Data#ase=d8<1ort!windS&stem.mdw;:ser 'D=;;;;;;*assword=;;;;;;


En *+L *erver 0.E o anterior" utilice la palabra clave s%loledb. 'opiar cdigo

*rovider=sqloled#;Data Source=%&SqlServer;'nitial Catalog=pu#s;:ser 'd=;;;;;;*assword=;;;;;;


No utilice arc#i!os de !nculo de datos uni!ersal
*e puede proporcionar informacin de cone&in para un ob$eto OleDbConnection en un arc6ivo de v5nculos de datos universal 9= L:" pero conviene evitarlo. Los arc6ivos = L no est4n cifrados y e&ponen la informacin de cadena de cone&in en te&to sin cifrar. =n arc6ivo = L no se puede proteger mediante .NET Framework" ya que se trata de un recurso basado en un arc6ivo e&terno a la aplicacin. !onexin a Excel ,ara conectarse a un libro de E&cel se utili%a el proveedor -icrosoft Fet. En la siguiente cadena de cone&in" la palabra clave &'tended Properties establece propiedades que son espec5ficas de E&cel. GH 1IJesG" indica que la primera fila contiene nombres de columna" no datos" y G;-EDIBG" indica al controlador que siempre lea las columnas de datos Gentreme%cladosG como te&to. !bserve que 'opiar cdigo

*rovider=%icroso+t.@et.O6ADB.>.=;Data Source=D8<%&ACcel.Cls;ACtended *roperties=$$ACcel D.=;EDF=Ges;'%AH=($$


Tenga en cuenta que el car4cter de comilla doble necesario en &'tended Properties tambi#n debe ir incluido entre comillas dobles. Sintaxis de cadena de conexin del proveedor de formas de datos =tilice las palabras clave Provider y Data Provider cuando use el proveedor de formas de datos de -icrosoft. En el siguiente e$emplo se utili%a el proveedor de formas para conectarse a una instancia local de *+L *erver.

'opiar cdigo

$*rovider=%SDataS!ape;Data *rovider=S76O6ADB;Data Source=(local);'nitial Catalog=pu#s;'ntegrated Securit&=SS*';$


Cadenas de conexin Odbc
La propiedad ConnectionString de una !dbc'onnection permite obtener o establecer una cadena de cone&in para un origen de datos !LE 'ontrolador de te&to de -icrosoft. 'opiar cdigo ). En la siguiente cadena de cone&in se utili%a el

Driver={%icroso+t ,eCt Driver (;.tCt; ;.csv)";DB7=d8<#in


,ara obtener m4s informacin acerca de la sinta&is de cadena de cone&in ! )'" vea ConnectionString.

Cadenas de conexin Oracle


La propiedad ConnectionString de una !racle'onnection permite obtener o establecer una cadena de cone&in para un origen de datos !LE 'opiar cdigo ).

Data Source=OracleIi;:ser 'D=;;;;;;*assword=;;;;;;


,ara obtener m4s informacin acerca de la sinta&is de cadena de cone&in ! )'" vea ConnectionString.

Traba$ar con e!entos de conexin


Todos los proveedores de datos de .NET Framework tienen ob$etos Connection con dos eventos que se pueden utili%ar para recuperar mensa$es informativos de un origen de datos o para determinar si 6a cambiado el estado de un ob$eto Connection. En la tabla siguiente se enumeran los eventos del ob$eto Connection.

Evento

Descripcin

InfoMessage Se produce cuando se devuelve un mensaje informativo desde un ori!en de datos. $os mensajes informativos son aquellos procedentes de or"!enes de datos que no inician una e'cepcin. StateC ange Se produce cuando cambia el estado del objeto Connection.

Traba$ar con el e!ento &n'o,essage


'on el evento ;nfo-essage del ob$eto *ql'onnection puede recuperar advertencias o mensa$es informativos desde un origen de datos. *i se devuelven errores desde el origen de datos con un nivel de seguridad entre BB y B0" se inicia una e&cepcin. *in embargo" el evento Info(essage se puede utili%ar para obtener mensa$es del origen de datos que no est#n asociados a un error. En el caso de -icrosoft *+L *erver" cualquier error que tenga la gravedad B/" como m4&imo" se considera de tipo informativo y se captura mediante el evento Info(essage. ,ara obtener m4s informacin" vea el tema GNiveles de gravedad de mensa$es de errorG en los Libros en pantalla de *+L *erver.

El evento Info(essage recibe un ob$eto ;nfo-essageEventArgs que contiene en su propiedad &rrors una coleccin de los mensa$es del origen de datos. ,uede consultar los ob$etos &rror de esa coleccin para conocer el n8mero de error y el te&to del mensa$e" as5 como el origen del error. El proveedor de datos de .NET Framework para *+L *erver incluye asimismo datos acerca de la base de datos" el procedimiento almacenado y el n8mero de l5nea donde se origin el mensa$e. Ejemplo En el e$emplo de cdigo siguiente se muestra cmo se puede agregar un controlador de eventos para el evento Info(essage. '3 'opiar cdigo

// Assumes t!at connection represents a SqlConnection o#Ject. connection.'n+o%essage K= new Sql'n+o%essageAventEandler(On'n+o%essage); protected static void On'n+o%essage( o#Ject sender9 Sql'n+o%essageAventArgs args) { +oreac! (SqlArror err in args.Arrors) { Console.5rite6ine( $,!e {=" !as received a severit& {("9 state {)" error num#er {?"<n$ K $on line {>" o+ procedure {L" on server {M"8<n{N"$9 err.Source9 err.Class9 err.State9 err.1um#er9 err.6ine1um#er9 err.*rocedure9 err.Server9 err.%essage); " "
Controlar errores como &n'o,essages
Normalmente" el evento Info(essage slo se activa para mensa$es informativos y de advertencia enviados desde el servidor. *in embargo" cuando se produce un error real" se detiene la e$ecucin de los m#todos &'ecute)on*uery o &'ecuteReader que iniciaron la operacin del servidor y se inicia una e&cepcin. *i desea seguir procesando el resto de las instrucciones de un comando" independientemente de los errores producidos en el servidor" estable%ca la propiedad Fire;nfo-essageEvent!n=serErrors de S%lConnection como true. e esta forma" la cone&in activa el evento Info(essage para errores" en lugar de iniciar una e&cepcin e interrumpir el procesamiento. La aplicacin cliente puede controlar el evento y reaccionar ante las situaciones de error.

Nota $os errores con un nivel de !ravedad de -4, como m"nimo, que 1acen que el servidor interrumpa el procesamiento de comandos, deben controlarse como e'cepciones. En este caso, se inicia una e'cepcin, independientemente del modo en que se controle el error en el evento InfoMessage.

Traba$ar con el e!ento StateC#ange


El evento StateC+ange se produce cuando cambia el estado de un ob$eto Connection. El evento StateC+ange recibe *tate'6angeEventArgs que permiten determinar el cambio de estado de Connection por medio de las propiedades OriginalState y CurrentState. La propiedad OriginalState es una enumeracin 'onnection*tate que indica el estado del ob$eto Connection antes del cambio. CurrentState es una enumeracin ConnectionState que indica el estado del ob$eto Connection despu#s del cambio. En el e$emplo de cdigo siguiente se utili%a el evento StateC+ange para escribir un mensa$e en la consola cuando cambia el estado del ob$eto Connection.

'3 'opiar cdigo

// Assumes connection represents a SqlConnection o#Ject. connection.StateC!ange K= new StateC!angeAventEandler(OnStateC!ange); protected static void OnStateC!ange(o#Ject sender9 StateC!angeAventArgs args) { Console.5rite6ine( $,!e current Connection state !as c!anged +rom {=" to {(".$9 args.OriginalState9 args.CurrentState); "

Descripcin de agrupacin de conexiones


La agrupacin de cone&iones puede me$orar de forma significativa el rendimiento y la escalabilidad de una aplicacin. Los proveedores de datos de .NET Framework tratan de forma diferente la agrupacin de cone&iones. ,ara obtener informacin acerca de la agrupacin de cone&iones en *+L *erver" vea =so de agrupacin de cone&iones.

Agrupacin de conexiones para el pro!eedor de datos de .NET -rame*or. para O E D"


El proveedor de datos de .NET Framework para !LE mediante la agrupacin de sesiones !LE cone&in para 6abilitar o des6abilitar servicios !LE autom4tica de transacciones. 'opiar cdigo ) agrupa autom4ticamente las cone&iones )" incluida la agrupacin. ,or e$emplo" la ) y la inscripcin

). *e pueden utili%ar argumentos de cadena de

siguiente cadena de cone&in des6abilita la agrupacin de sesiones !LE

*rovider=S76O6ADB;O6A DB Services=O>;Data Source=local!ost;'ntegrated Securit&=SS*';


*e recomienda cerrar siempre o eliminar una cone&in cuando termine de utili%arla" para que la cone&in pueda regresar al grupo. Es posible que las cone&iones que no se cierran e&pl5citamente no puedan regresar al grupo. ,or e$emplo" una cone&in que se 6a salido del 4mbito pero que no se 6a cerrado e&pl5citamente slo se devolver4 al grupo de cone&in si se 6a alcan%ado el tama7o m4&imo del grupo y la cone&in a8n es v4lida. ,ara obtener m4s informacin acerca de la agrupacin de sesiones o de recursos !LE proveedor !LE )" vea !LE ) ,rogrammerKs 1eference en la biblioteca -* N. )" y de

cmo des6abilitar la agrupacin reempla%ando los valores predeterminados del servicio del

Agrupacin de conexiones para el pro!eedor de datos de .NET -rame*or. para ODC"


La agrupacin de cone&iones para el proveedor de datos de .NET Framework para ! )' se administra a trav#s del Administrador de controladores ! )' que se utili%a en la cone&in" y que no est4 influido por dic6o proveedor. ,ara 6abilitar o des6abilitar la agrupacin de cone&iones" abra Administrador de or,genes de datos OD-C en la carpeta Herramientas administrativas del ,anel de control. La fic6a Agrupaci.n de cone'iones permite especificar los par4metros de agrupacin de cone&iones de cada controlador ! )' instalado. Tenga en cuenta que los cambios en la agrupacin de cone&iones de un controlador ! )' espec5fico afectar4n a todas las aplicaciones que utilicen dic6o controlador.

,ara obtener m4s informacin acerca de la agrupacin de cone&iones ! )'" vea !LE ,rogrammerKs 1eference en la biblioteca -* N.

Agrupacin de conexiones para el pro!eedor de datos de .NET -rame*or. para Oracle


El proveedor de datos de .NET Framework para !racle ofrece agrupacin autom4tica de cone&iones para la aplicacin cliente de A !.NET. Tambi#n puede suministrar varios modificadores de cadena de cone&in para controlar el comportamiento de agrupacin de cone&iones 9vea G'ontrol de la agrupacin de cone&iones con palabras clave de cadena de cone&inG" m4s adelante en este tema:. !reacin y asignacin del grupo 'uando se abre una cone&in" se crea un grupo de cone&in basado en un algoritmo de coincidencia e&acta que asocia el grupo con la cadena de cone&in de la cone&in. 'ada grupo de cone&in se asocia con una cadena de cone&in distinta. *i se abre una nueva cone&in y la cadena de cone&in no coincide e&actamente con un grupo e&istente" se crea un nuevo grupo. =na ve% creados" los grupos de cone&in no se destruyen 6asta que finali%a el proceso activo. -antener grupos inactivos o vac5os consume muy pocos recursos del sistema. Adicin de conexiones ,ara cada cadena de cone&in 8nica se crea un grupo de cone&in. 'uando se crea un grupo" se crean y agregan al grupo varios ob$etos de cone&in> as5 se satisface el requisito de tama7o m5nimo del grupo. Las cone&iones se agregan al grupo cuando es necesario" 6asta el tama7o m4&imo del grupo. 'uando se solicita un ob$eto !racle'onnection" se obtiene del grupo si se encuentra disponible una cone&in que se pueda utili%ar. =na cone&in de este tipo debe estar sin utili%ar en ese momento" tener un conte&to de transaccin coincidente o no estar asociada con ning8n conte&to de transaccin" y tener un v5nculo v4lido al servidor. *i se 6a alcan%ado el tama7o m4&imo del grupo y no 6ay disponible ninguna cone&in que se pueda utili%ar" la solicitud se pone en la cola. El concentrador de cone&in satisface estas solicitudes al reasignar las cone&iones conforme se liberan de nuevo en el grupo" lo cual ocurre cuando #stas se cierran o eliminan. Eliminacin de conexiones El concentrador de cone&in elimina una cone&in del grupo despu#s de que 6a estado inactiva durante un per5odo de tiempo prolongado o si detecta que se 6a roto la cone&in al servidor. Tenga en cuenta que esto slo puede detectarse despu#s de intentar comunicarse con el servidor. *i se encuentra que una cone&in ya no est4 conectada al servidor" se marca como no v4lida. El concentrador de cone&in anali%a peridicamente los grupos en busca de ob$etos que se 6an liberado en el grupo y marcado como no v4lidos. Luego" estas cone&iones se eliminan de forma permanente. *i e&iste una cone&in a un servidor que 6a desaparecido" se puede e&traer del grupo si el concentrador de cone&in no 6a detectado la cone&in rota y la 6a marcado como no v4lida. 'uando esto se produce" se genera una e&cepcin. No obstante" aun as5 deber4 cerrar la cone&in para liberarla de nuevo en el grupo. No llame a Close o a Dispose en un ob$eto Connection" un ob$eto DataReader o cualquier otro ob$eto administrado en el m#todo /inali0e de la clase. En un finali%ador" libere slo los recursos

no administrados que pertene%can directamente a su clase. *i la clase no dispone de recursos no administrados" no incluya un m#todo /inali0e en la definicin de clase. ,ara obtener m4s informacin" vea 1ecoleccin de elementos no utili%ados. !ompati"ilidad con transacciones Las cone&iones se e&traen del grupo y se asignan en funcin del conte&to de transaccin. Es necesario que el subproceso solicitante y la cone&in asignada coincidan. ,or lo tanto" cada grupo de cone&in se divide realmente en cone&iones que no tienen asociado ning8n conte&to de transaccin y en N subdivisiones" cada una de las cuales contiene cone&iones con un conte&to de transaccin particular. 'uando se cierra una cone&in" se libera de nuevo en el grupo y en la subdivisin adecuada en funcin de su conte&to de transaccin. ,or lo tanto" puede cerrar la cone&in sin generar un error" incluso aunque a8n 6aya pendiente una transaccin distribuida. Esto le permite confirmar o anular la transaccin distribuida m4s adelante. !ontrol de la agrupacin de conexiones con pala"ras clave de cadena de conexin La propiedad 'onnection*tring del ob$eto OracleConnection admite pares de clave y valor de cadena de cone&in que se pueden utili%ar para a$ustar el comportamiento de la lgica de agrupacin de cone&iones. En la siguiente tabla se describen los valores ConnectionString que puede utili%ar para a$ustar el comportamiento de agrupacin de cone&iones.

No!bre Connection "ifeti!e

Default Descripcin . ,uando una cone'in se devuelve al !rupo, su 1ora de creacin se compara con la 1ora actual y, si ese marco temporal 5en se!undos6 e'cede el valor especificado por Connection "ifeti!e, la cone'in se destruye. Esto resulta de utilidad en confi!uraciones a!rupadas para forzar el equilibrio de car!a entre un servidor en ejecucin y uno que acaba de conectarse. 7n valor de cero 5.6 1ar que las cone'iones a!rupadas ten!an el tiempo de espera m'imo. ,uando es true, el concentrador inscribe automticamente la cone'in en el conte'to de transaccin actual del subproceso de creacin, si e'iste un conte'to de transaccin. El n9mero m'imo de cone'iones permitido en el !rupo. El n9mero m"nimo de cone'iones mantenido en el !rupo. ,uando es true, la cone'in se e'trae del !rupo adecuado o, si es necesario, se crea y a!re!a al !rupo correcto.

Enlist Ma# $ool Size Min $ool Size $ooling

8true8 -.. . 8true8

Conceptos =so de agrupacin de cone&iones Otros recursos 'onectar a or5genes de datos

/e'erencia
b'onnection escribe la clase DbConnection" as5 como todos sus miembros. OdbcConnection escribe la clase OdbcConnection" as5 como todos sus miembros.

OleDbConnection escribe la clase OleDbConnection" as5 como todos sus miembros. OracleConnection escribe la clase OracleConnection" as5 como todos sus miembros. S%lConnection escribe la clase S%lConnection" as5 como todos sus miembros.

Traba$o con comandos


=na ve% establecida una cone&in a un origen de datos" puede e$ecutar comandos y devolver resultados desde el mismo mediante un ob$eto Command. ,ara crear un comando" puede utili%ar el constructor Command" que toma argumentos opcionales de una instruccin *+L para e$ecutar en el origen de datos" un ob$eto Connection y un ob$eto 1ransaction. Tambi#n puede crear un comando para una determinada cone&in mediante el m#todo CreateCommand del ob$eto Connection. La instruccin *+L del ob$eto Command se puede consultar y modificar mediante el uso de la propiedad Command1e't. 'ada proveedor de datos de .NET Framework incluido en .NET Framework cuenta con un ob$eto Command( el proveedor de datos de .NET Framework para !LE ) incluye un ob$eto !le b'ommand" el proveedor de datos de .NET Framework para *+L *erver incluye un ob$eto *ql'ommand" el proveedor de datos de .NET Framework para ! )' incluye un ob$eto !dbc'ommand y el proveedor de datos de .NET Framework para !racle incluye un ob$eto !racle'ommand.

En esta seccin

E$ecutar un comando
El ob$eto Command e&pone varios m#todos &'ecute que puede utili%ar para llevar a cabo la accin deseada. 'uando los resultados se devuelven en forma de secuencia de datos" puede utili%ar &'ecuteReader para devolver un ob$eto DataReader. &'ecuteScalar sirve para devolver un valor *ingleton. &'ecute)on*uery se utili%a para e$ecutar comandos que no devuelven filas. Al utili%ar el ob$eto Command con un procedimiento almacenado" puede establecer la propiedad Command1ype del ob$eto Command para que tenga el valor StoredProcedure. 'uando Command1ype tiene el valor StoredProcedure" puede utili%ar la propiedad Parameters del ob$eto Command para tener acceso a los par4metros de entrada y de salida y a los valores devueltos. ,uede acceder a la propiedad Parameters independientemente del m#todo &'ecute llamado. *in embargo" al llamar a &'ecuteReader" no es posible el acceso a los valores devueltos y los par4metros de salida 6asta que se cierra DataReader. En el siguiente e$emplo de cdigo se muestra cmo crear un ob$eto *ql'ommand para devolver una lista de categor5as de la base de datos de e$emplo )ort+wind de *+L *erver.

E$emplo

'3 'opiar cdigo

// nwindConn is assumed to #e a valid SqlConnection o#Ject. SqlCommand command = new SqlCommand( $SA6AC, Categor&'D9 Categor&1ame 0FO% d#o.Categories$9 nwindConn);
!ontadores de rendimiento para comandos El proveedor de datos de .NET Framework para *+L *erver agrega un contador de rendimiento que permite detectar problemas intermitentes relacionados con errores en la e$ecucin de comandos. Al tener acceso al contador S%lClient2 1otal 3 failed commands4 del -onitor de sistema" incluido en el ob$eto de rendimiento )&1 CLR Data" puede conocer el n8mero total de errores producidos por cualquier causa al e$ecutar comandos.

Nota :l utilizar el proveedor de datos de .NE( )rame*or+ para los contadores de rendimiento de S;$ Server conjuntamente con aplicaciones :SP.NE(, se recomienda utilizar slo la instancia %&lobal. ,omo resultado, el valor devuelto por el contador de rendimiento es la suma de los valores de los contadores de todas las aplicaciones :SP.NE(.

(tili0ar procedimientos almacenados con un comando


Los procedimientos almacenados ofrecen numerosas venta$as en el caso de aplicaciones que procesan datos. -ediante los procedimientos almacenados" las operaciones de bases de datos se pueden encapsular en un solo comando" se optimi%an para lograr el me$or rendimiento y disfrutan de una seguridad adicional. Aunque es cierto que para llamar a un procedimiento almacenado basta con pasar en forma de instruccin *+L su nombre seguido de los argumentos de par4metros" el uso de la coleccin ,arameters del ob$eto par4metros de salida y a los valores devueltos. ,ara llamar a un procedimiento almacenado" estable%ca el 'ommandType del ob$eto Command como StoredProcedure. Al asignar el valor StoredProcedure a Command1ype" puede utili%ar la coleccin Parameters para definir par4metros" como se muestra en el e$emplo siguiente. b'ommand de A !.NET permite definir m4s e&pl5citamente los par4metros del procedimiento almacenado" as5 como tener acceso a los

Nota El #dbc,ommand requiere que el usuario proporcione la sinta'is de llamada ,:$$ de #%&, completa al llamar a un procedimiento almacenado.

E$emplo
'3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand salesCommand = new SqlCommand($SalesB&Categor&$9 connection); salesCommand.Command,&pe = Command,&pe.Stored*rocedure; Sql*arameter parameter = salesCommand.*arameters.Add( $PCategor&1ame$9 SqlD#,&pe.1QarC!ar9 (L); parameter.Qalue = $Beverages$; connection.Open(); SqlDataFeader reader = salesCommand.ACecuteFeader(); Console.5rite6ine(

${="9 {("$9 reader.Ret1ame(=)9 reader.Ret1ame(()); w!ile (reader.Fead()) { Console.5rite6ine(${="9 S{("$9 reader.RetString(=)9 reader.RetDecimal(()); " reader.Close(); connection.Close();

=n ob$eto Parameter se puede crear mediante el constructor Parameter o al llamar al m#todo Add de la coleccin Parameters de Command. Parameters Add acepta como entrada argumentos del constructor o cualquier ob$eto Parameter ya e&istente. Al establecer como una referencia nula el valor de 5alue de un ob$eto Parameter" debe utili%ar D-)ull 5alue. En el caso de par4metros que no sean de entrada 9Input:" debe asignar a la propiedad ParameterDirection un valor que especifique cu4l es el tipo de par4metro( InputOutput" Output o Return5alue. En el e$emplo siguiente se muestra la diferencia en la creacin de par4metros Input" Output y Return5alue para los distintos proveedores.

E$emplo de S+lClient
'3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand command = new SqlCommand($Sample*roc$9 connection); command.Command,&pe = Command,&pe.Stored*rocedure; Sql*arameter parameter = command.*arameters.Add( $FA,:F1-QA6:A$9 SqlD#,&pe.'nt); parameter.Direction = *arameterDirection.FeturnQalue; parameter = command.*arameters.Add( $P'nput*arm$9 SqlD#,&pe.1QarC!ar9 ()); parameter.Qalue = $Sample Qalue$; parameter = command.*arameters.Add( $POutput*arm$9 SqlD#,&pe.1QarC!ar9 )D); parameter.Direction = *arameterDirection.Output; connection.Open(); SqlDataFeader reader = command.ACecuteFeader(); Console.5rite6ine( ${="9 {("$9 reader.Ret1ame(=)9 reader.Ret1ame(()); w!ile (reader.Fead()) { Console.5rite6ine( ${="9 {("$9 reader.Ret'nt?)(=)9 reader.RetString(()); " reader.Close(); connection.Close(); Console.5rite6ine($ POutput*arm8 {="$9 command.*arameters2$POutput*arm$3.Qalue); Console.5rite6ine($FA,:F1-QA6:A8 {="$9 command.*arameters2$FA,:F1-QA6:A$3.Qalue);
E$emplo de OleDb
'3 'opiar cdigo

OleD#Command command = new OleD#Command($Sample*roc$9 connection); command.Command,&pe = Command,&pe.Stored*rocedure; OleD#*arameter parameter = command.*arameters.Add( $FA,:F1-QA6:A$9 OleD#,&pe.'nteger); parameter.Direction = *arameterDirection.FeturnQalue; parameter = command.*arameters.Add( $P'nput*arm$9 OleD#,&pe.QarC!ar9 ()); parameter.Qalue = $Sample Qalue$; parameter = command.*arameters.Add(

$POutput*arm$9 OleD#,&pe.QarC!ar9 )D); parameter.Direction = *arameterDirection.Output; connection.Open(); OleD#DataFeader reader = command.ACecuteFeader(); Console.5rite6ine(${="9 {("$9 reader.Ret1ame(=)9 reader.Ret1ame(()); w!ile (reader.Fead()) { Console.5rite6ine(${="9 {("$9 reader.Ret'nt?)(=)9 reader.RetString(()); " reader.Close(); connection.Close(); Console.5rite6ine($ POutput*arm8 {="$9 command.*arameters2$POutput*arm$3.Qalue); Console.5rite6ine($FA,:F1-QA6:A8 {="$9 command.*arameters2$FA,:F1-QA6:A$3.Qalue);
E$emplo de Odbc
'3 'opiar cdigo

Od#cCommand command = new Od#cCommand( - ${ T = CA66 Sample*roc(T9 T) "$9 connection); command.Command,&pe = Command,&pe.Stored*rocedure; Od#c*arameter parameter = command.*arameters.Add( - $FA,:F1-QA6:A$9 Od#c,&pe.'nt); parameter.Direction = *arameterDirection.FeturnQalue; parameter = command.*arameters.Add( - $P'nput*arm$9 Od#c,&pe.QarC!ar9 ()); parameter.Qalue = $Sample Qalue$; parameter = command.*arameters.Add( $POutput*arm$9 Od#c,&pe.QarC!ar9 )D); parameter.Direction = *arameterDirection.Output; connection.Open(); Od#cDataFeader reader = command.ACecuteFeader(); Console.5rite6ine(${="9 {("$9 reader.Ret1ame(=)9 reader.Ret1ame(()); w!ile (reader.Fead()) { Console.5rite6ine( - ${="9 {("$9 reader.Ret'nt?)(=)9 reader.RetString(()); " reader.Close(); connection.Close(); Console.5rite6ine($ POutput*arm8 {="$9 command.*arameters2$POutput*arm$3.Qalue); Console.5rite6ine($FA,:F1-QA6:A8 {="$9 command.*arameters2$FA,:F1-QA6:A$3.Qalue);
%tili&ar par'metros con S l!ommand Al utili%ar par4metros con *ql'ommand" los nombres de los par4metros agregados a la coleccin ,arameters deben coincidir con los de los marcadores de par4metro del procedimiento almacenado. El proveedor de datos de .NET Framework para *+L *erver trata los par4metros del procedimiento almacenado como par4metros con nombre y busca los marcadores de par4metros que coinciden con sus nombres. El proveedor de datos de .NET Framework para *+L *erver no permite usar el marcador de posicin de signo de interrogacin de cierre 9L: para pasar par4metros a una instruccin *+L o a un procedimiento almacenado. En este caso" debe utili%ar par4metros con nombre" como se muestra en el e$emplo siguiente donde PCustomer'D es el par4metro con nombre. 'opiar cdigo

SA6AC, ; 0FO% Customers 5EAFA Customer'D = PCustomer'D

%tili&ar par'metros con #le$"!ommand o con #d"c!ommand Al utili%ar par4metros con !le b'ommand o con OdbcCommand" el orden de los par4metros agregados a la coleccin Parameters debe coincidir con el de los par4metros definidos en el procedimiento almacenado. El proveedor de datos de .NET Framework para !LE ) y el proveedor de datos de .NET Framework para ! )' consideran a los par4metros de un procedimiento almacenado como marcadores de posicin y aplican los valores de los par4metros en orden. Adem4s" los par4metros de valores devueltos deben ser los primeros que se agreguen a la coleccin Parameters. El proveedor de datos de .NET Framework para !LE ) y el proveedor de datos de .NET

Framework para ! )' no permiten usar par4metros con nombre para pasar par4metros a una instruccin *+L o a un procedimiento almacenado. En este caso" se debe utili%ar el marcador de posicin de signo interrogacin de cierre 9L:" como se muestra en el e$emplo siguiente. 'opiar cdigo

SA6AC, ; 0FO% Customers 5EAFA Customer'D = T


,or eso" el orden en que se agregan los ob$etos Parameter a la coleccin Parameters debe coincidir e&actamente con la posicin del marcador de posicin de interrogacin de cierre correspondiente al par4metro. $erivar la informacin de par'metros Los par4metros tambi#n se pueden derivar de un procedimiento almacenado mediante la clase Command-uilder. Las clases *ql'ommand)uilder y !le b'ommand)uilder proporcionan el m#todo est4tico DeriveParameters" que rellena autom4ticamente la coleccin Parameters de un ob$eto Command con informacin de par4metros de un procedimiento almacenado. Tenga en cuenta que DeriveParameters sobrescribir4 toda la informacin de los par4metros que pueda tener anteriormente el ob$eto Command. ,ara derivar la informacin de los par4metros" es necesario visitar nuevamente el origen de datos y recuperar la informacin. *i la informacin de los par4metros se conoce en tiempo de dise7o" puede me$orar el rendimiento de la aplicacin si establece los par4metros con los valores correspondientes de forma e&pl5cita. En el e$emplo de cdigo siguiente se muestra cmo llenar la coleccin Parameters de un ob$eto Command con Command-uilder DeriveParameters. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand salesCommand = new SqlCommand($Sales B& Gear$9 connection); salesCommand.Command,&pe = Command,&pe.Stored*rocedure; connection.Open(); SqlCommandBuilder.Derive*arameters(salesCommand); connection.Close();

1enerar comandos autom2ticamente


'uando la propiedad SelectCommand se especifica de forma din4mica en tiempo de e$ecucin" por e$emplo a trav#s de una 6erramienta de consulta que acepta un comando de te&to del usuario" e&iste la posibilidad de que no se pueda especificar adecuadamente en tiempo de dise7o si se trata de un comando InsertCommand" UpdateCommand o DeleteCommand. *i la ataTable est4 asignada a una 8nica base de datos o se 6a generado a partir de ella" puede

utili%ar el ob$eto

b'ommand)uilder para generar autom4ticamente el comando b ataAdapter.

DeleteCommand" InsertCommand y UpdateCommand del

El requisito m5nimo para que la generacin autom4tica de comandos funcione correctamente consiste en establecer la propiedad SelectCommand. El esquema de tabla que recupera la propiedad SelectCommand determina la sinta&is de las instrucciones ;N*E1T" =, ATE y generadas autom4ticamente. DbCommand-uilder debe e$ecutar SelectCommand con el ob$eto de poder devolver los metadatos necesarios para construir los comandos *+L ;N*E1T" =, ATE y ELETE. ,or eso es necesario reali%ar un via$e adicional al origen de datos" con el consiguiente efecto adverso en el rendimiento. ,ara me$orar el rendimiento" debe especificar los comandos de forma e&pl5cita" en lugar de utili%ar DbCommand-uilder. SelectCommand debe adem4s devolver al menos una clave principal o columna 8nica. *i no 6ay ninguna" se inicia una e&cepcin InvalidOperation y no se genera ning8n comando. 'uando se asocia con un DataAdapter" DbCommand-uilder genera autom4ticamente las propiedades InsertCommand" UpdateCommand y DeleteCommand del DataAdapter si son referencias nulas. *i ya e&iste alg8n comando Command para una propiedad" se utili%ar4 ese. Las vistas de bases de datos creadas al unir una o varias tablas no se consideran una tabla 8nica de base de datos. En este caso no puede utili%ar DbCommand-uilder para generar comandos autom4ticamente y debe especificarlos de manera e&pl5cita. ,ara obtener informacin sobre cmo especificar comandos de forma e&pl5cita para refle$ar el origen de datos de las actuali%aciones efectuadas en el DataSet" vea Actuali%ar or5genes de datos con ataAdapters. ELETE

,uede ser aconse$able asignar los par4metros de salida a la fila actuali%ada de un DataSet. =na tarea 6abitual consiste en recuperar" a partir del origen de datos" el valor de un campo de identidad de generacin autom4tica o una marca de 6ora. DbCommand-uilder no asigna de forma predeterminada los par4metros de salida a las columnas de una fila actuali%ada. En este caso" debe especificar el comando de forma e&pl5cita. ,ara consultar un e$emplo de asignacin de un campo de identidad de generacin autom4tica a una columna de una fila insertada" vea 1ecuperar valores de identidad o de autonumeracin.

/eglas para comandos generados autom2ticamente


En la tabla siguiente se muestran las reglas de la generacin autom4tica de comandos.

Co!ando InsertCo!!and

Regla <nserta una fila en el ori!en de datos para todas las filas de la tabla con una o*State con el valor :dded. <nserta valores para todas las columnas actualizables, pero no para determinadas columnas como identidades, e'presiones o marcas de 1ora.

'pdateCo!!and :ctualiza filas en el ori!en de datos para todas las filas de la tabla con un valor Ro(State 0odified. :ctualiza los valores de todas las columnas, con e'cepcin de las que no son actualizables, como identidades o e'presiones. :ctualiza todas las filas en las que los valores de columna en el ori!en de datos coinciden con los valores de la columna de clave principal de la fila, siempre que las restantes columnas del ori!en de datos coincidan con los valores ori!inales de la fila. Para obtener ms informacin, vea la seccin =0odelo de simultaneidad optimista para actualizaciones y eliminaciones= de este mismo tema. DeleteCo!!and Elimina filas en el ori!en de datos para todas las filas de la tabla con un valor Ro(State %eleted. Elimina todas las filas en las que los valores de columna coinciden con los valores de la columna de clave principal de la fila, siempre que las restantes columnas del ori!en de datos coincidan con los

valores ori!inales de la fila. Para obtener ms informacin, vea la seccin =0odelo de simultaneidad optimista para actualizaciones y eliminaciones= de este mismo tema.

,odelo de simultaneidad optimista para actuali0aciones y eliminaciones


La lgica empleada en la generacin autom4tica de comandos para las instrucciones =, ATE y ELETE se basa en la simultaneidad optimista" es decir" los registros no se bloquean para ser editados y pueden ser modificados en cualquier momento por otros usuarios o procesos. por la instruccin *ELE'T y antes de que se emita la instruccin =, ATE o =, ATE o ado que e&iste la posibilidad de que un registro 6aya sido modificado despu#s de que 6aya sido devuelto ELETE" la instruccin ELETE generada autom4ticamente incluye una cl4usula <HE1E que especifica que la

fila slo se actuali%a cuando contiene todos los valores originales y no 6a sido eliminada del origen de datos. Esto evita que se sobrescriban los datos nuevos. En los casos en que una actuali%acin generada autom4ticamente intenta actuali%ar una fila que 6a sido eliminada o que no contiene los valores originales que se encuentran en el registros y se inicia una e&cepcin ata*et" el comando no tienen ning8n efecto en los )'oncurrencyE&ception. ELETE se e$ecute sin tener en cuenta los valores originales"

*i desea que la instruccin =, ATE o generacin autom4tica de comandos.

debe establecer de forma e&pl5cita el UpdateCommand del DataAdapter sin utili%ar la

imitaciones de la lgica de generacin autom2tica de comandos


La generacin autom4tica de comandos tiene las siguientes limitaciones. Slo ta"las no relacionadas La lgica de generacin autom4tica de comandos crea instrucciones ;N*E1T" =, ATE o ELETE para tablas independientes sin tener en cuenta las relaciones que #stas puedan tener con otras tablas en el origen de datos. ,or eso" se puede producir un error al llamar a Update para reali%ar cambios en una columna que participa en una restriccin de clave e&terna en la base de datos. ,ara evitar esa e&cepcin" no utilice DbCommand-uilder al actuali%ar las columnas que participan en una restriccin de clave e&terna. En este caso debe especificar de forma e&pl5cita las instrucciones que se van a utili%ar para llevar a cabo la operacin. Nom"res de ta"la y columna La lgica de generacin autom4tica de comandos ocasiona un error cuando los nombres de las tablas o columnas incluyen alg8n car4cter especial" como espacios" puntos" signos de e&clamacin y otros caracteres no alfanum#ricos" aun en el caso de que se incluyan entre corc6etes. *e pueden utili%ar nombres completos de tabla" como catalog.schema.table.

(tili0ar Command"uilder para generar autom2ticamente una instruccin SQ


,ara generar instrucciones *+L autom4ticamente para un DataAdapter" defina en primer lugar la propiedad SelectCommand del DataAdapter y" a continuacin" cree un ob$eto Command-uilder y especifique como argumento DataAdapter para el que Command-uilder generar4 autom4ticamente las instrucciones *+L. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlDataAdapter adapter = new SqlDataAdapter( $SA6AC, ; 0FO% d#o.Customers$9 connection); SqlCommandBuilder #uilder = new SqlCommandBuilder(adapter); #uilder.7uote*re+iC = $2$; #uilder.7uoteSu++iC = $3$; DataSet custDS = new DataSet(); connection.Open(); adapter.0ill(custDS9 $Customers$); // Code to modi+& data in t!e DataSet !ere. // 5it!out t!e SqlCommandBuilder9 t!is line would +ail. adapter.:pdate(custDS9 $Customers$); connection.Close();
,odi'icar SelectCommand
Es posible que se inicie una e&cepcin si modifica el te&to Command1e't de SelectCommand despu#s de generar autom4ticamente los comandos ;N*E1T" =, ATE o ELETE. *i el te&to de SelectCommand Command1e't modificado contiene informacin del esquema que sea inco6erente con la del te&to de SelectCommand Command1e't utili%ado en el momento de la generacin autom4tica de los comandos de insercin" actuali%acin o eliminacin" las futuras llamadas al m#todo DataAdapter Update pueden tratar de tener acceso a columnas que ya no e&istan en la tabla actual a la que 6ace referencia SelectCommand" con lo que se inicia una e&cepcin. ,uede actuali%ar la informacin del esquema que utili%a Command-uilder para generar autom4ticamente los comandos> para ello" basta con llamar al m#todo Refres+Sc+ema de Command-uilder. *i desea conocer el comando generado autom4ticamente" puede obtener una referencia a los comandos generados autom4ticamente mediante los m#todos 6etInsertCommand" 6etUpdateCommand y 6etDeleteCommand del ob$eto Command-uilder. A continuacin" e&amine la propiedad Command1e't del Command asociado. En el e$emplo de cdigo siguiente se escribe en la consola el comando de actuali%acin generado autom4ticamente. 'opiar cdigo

Console.5rite6ine(#uilder.Ret:pdateCommand().Command,eCt)
En el siguiente e$emplo se contin8a con el cdigo del e$emplo anterior y se recrea la tabla Customers" en la que se sustituye la columna Company)ame por la columna Contact)ame. *e llama al m#todo Refres+Sc+ema para actuali%ar los comandos generados autom4ticamente con la informacin de esta nueva columna. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. connection.Open(); adapter.SelectCommand.Command,eCt = $SA6AC, Customer'D9 Contact1ame 0FO% d#o.Customers$; #uilder.Fe+res!Sc!ema(); custDS.,a#les.Femove(custDS.,a#les2$Customers$3); adapter.0ill(custDS9 $Customers$); // Code to modi+& t!e new ta#le in t!e DataSet !ere. // 5it!out t!e call to Fe+res!Sc!ema9 t!is line would +ail. adapter.:pdate(custDS9 $Customers$); connection.Close();
3ea tambi4n

Obtener un 5nico !alor de una base de datos


En ocasiones se debe devolver informacin de bases de datos consistente en un 8nico valor" en lugar de una tabla o una secuencia de datos. ,or e$emplo" puede que desee devolver el resultado de una funcin de agregada como '!=NT9M:" *=-9,rice: o A2N9+uantity:. El ob$eto Command permite devolver valores 8nicos mediante el m#todo &'ecuteScalar. El m#todo &'ecuteScalar devuelve como valor escalar el correspondiente a la primera columna de la primera fila del con$unto de resultados. En el e$emplo de cdigo siguiente se devuelve el n8mero de registros de una tabla mediante el m#todo E&ecute*calar de un ob$eto *ql'ommand. La instruccin *ELE'T utili%a la funcin agregada TransactO*+L '!=NT para devolver un 8nico valor que representa el n8mero de filas de la tabla especificada. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand ordersC%D = new SqlCommand( $SA6AC, Count(;) 0FO% Orders$9 connection); 'nt?) count = ('nt?))ordersC%D.ACecuteScalar();
3ea tambi4n /e'erencia
b'ommand escribe la clase DbCommand" as5 como todos sus miembros. OdbcCommand escribe la clase OdbcCommand" as5 como todos sus miembros. OleDbCommand escribe la clase OleDbCommand" as5 como todos sus miembros. OracleCommand escribe la clase OracleCommand" as5 como todos sus miembros. S%lCommand escribe la clase S%lCommand" as5 como todos sus miembros.

Traba$o con DataAdapters


=n ataAdapter se utili%a para recuperar datos de un origen de datos y llenar tablas con un ata*et. El DataAdapter tambi#n resuelve los cambios reali%ados en el DataSet de vuelta al origen de datos. -ediante el ob$eto Connection del proveedor de datos de .NET Framework" DataAdapter se conecta a un origen de datos y utili%a ob$etos Command para recuperar datos del origen de datos y resolver los cambios a dic6o origen. 'ada proveedor de datos de .NET Framework incluido en .NET Framework cuenta con un ob$eto DataAdapter( el proveedor de datos de .NET Framework para !LE ) incluye un ob$eto !le b ataAdapter" el proveedor de datos de .NET Framework para *+L *erver incluye un ob$eto *ql ataAdapter" el proveedor de datos de .NET Framework para ! )' incluye un ob$eto

!dbc ataAdapter y el proveedor de datos de .NET Framework para !racle incluye un ob$eto !racle ataAdapter.

En esta seccin

lenar un DataSet desde un DataAdapter


ata*et de A !.NET es una representacin residente en memoria de datos que proporciona un modelo de programacin relacional co6erente e independiente del origen de los datos. DataSet representa un con$unto completo de datos que incluye restricciones y tablas" as5 como relaciones entre estas 8ltimas. ado que DataSet es independiente del origen de datos" puede incluir datos locales de la aplicacin" as5 como datos de otros muc6os or5genes. La interaccin con los or5genes de datos e&istentes se controla mediante el DataAdapter. La propiedad SelectCommand del DataAdapter es un ob$eto Command que recupera datos del origen de datos. Las propiedades InsertCommand" UpdateCommand y DeleteCommand de DataAdapter son ob$etos Command que permiten administrar las actuali%aciones de los datos en el origen de datos para refle$ar las modificaciones efectuadas en el DataSet. Estas propiedades se describen m4s detalladamente en Actuali%ar or5genes de datos con ataAdapters.

El m#todo /ill del DataAdapter se usa para llenar un DataSet con los resultados de la propiedad SelectCommand del DataAdapter. El m#todo /ill acepta como argumentos un DataSet que se debe llenar y un ob$eto Data1able" o su nombre" que se debe llenar con las filas que devuelve SelectCommand. El m#todo /ill utili%a el ob$eto DataReader de forma impl5cita para devolver los nombres y tipos de columna utili%ados para crear las tablas de DataSet" as5 como los datos para llenar las filas de las tablas de DataSet. Las tablas y columnas slo se crean cuando no e&isten> en caso contrario" /ill utili%a el esquema e&istente de DataSet. Los tipos de columna se crean como tipos de .NET Framework conforme se indica en las tablas que aparecen en Asignar los tipos de datos del proveedor de datos de .NET para los tipos de datos de .NET Framework. No se crean claves principales a menos que e&istan en el origen de datos y se 6aya dado el valor (issingSc+emaAction Add#it+7ey a DataAdapter (issingSc+emaAction. *i el m#todo /ill determina que una tabla tiene clave principal" sobrescribe los datos del DataSet con los del origen de datos en aquellas filas en las que los valores de la columna de clave principal coincidan con los de la fila que devuelve el origen de datos. *i no se detecta ninguna clave principal" los datos se ane&an a las tablas del DataSet. /ill utili%a cualquier asignacin que pueda e&istir al llenar el DataSet 9vea 'onfigurar las asignaciones de ataTable y ata'olumn:.

Nota Si SelectCo!!and devuelve los resultados de una combinacin e'terna 5#7(E >#<N6, Data)dapter no establecer un valor $ri!ar*+e* para la tabla Data,able resultante. Es necesario definir $ri!ar*+e* para ase!urarse de que las filas duplicadas se resuelven correctamente. Para obtener ms informacin, vea %efinir una clave principal para una tabla. En el e$emplo de cdigo siguiente se crea una instancia de un *ql ataAdapter que utili%a un ob$eto *ql'onnection a la base de datos )ort+wind de -icrosoft *+L *erver y llena una ataTable en un DataSet con la lista de clientes. La instruccin *+L y los argumentos S%lConnection pasados al constructor S%lDataAdapter se utili%an para crear la propiedad *elect'ommand del S%lDataAdapter.

E$emplo
'3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. string quer&String = $SA6AC, Customer'D9 Compan&1ame 0FO% d#o.Customers$; SqlDataAdapter adapter = new SqlDataAdapter(quer&String9 connection); DataSet customers = new DataSet(); adapter.0ill(customers9 $Customers$);
Nota El cdi!o que aparece en este ejemplo no abre o cierra el objeto Connection de manera e'pl"cita. El mtodo Fill abre de forma impl"cita el objeto Connection que utiliza el Data)dapter cuando determina que esta cone'in no est abierta. ,uando el mtodo Fill 1a abierto la cone'in, el mismo mtodo la cierra cuando termina de utilizarla. Este 1ec1o simplifica el cdi!o cuando se trata de una operacin 9nica, como un mtodo Fill o 'pdate. Sin embar!o, en el caso de que se estn realizando varias operaciones que necesiten tener abierta una cone'in, puede mejorar el rendimiento de la aplicacin si llama e'pl"citamente al mtodo Open de Connection, realiza a continuacin las operaciones en el ori!en de datos y, finalmente, llama al mtodo Close de Connection. Es conveniente mantener abiertas las cone'iones del ori!en de datos lo ms brevemente posible con el fin de liberar recursos, de manera que estn disponibles para otras aplicaciones cliente.

3arios con$untos de resultados


*i el DataAdapter encuentra varios con$untos de resultados" crea varias tablas en el DataSet. Las tablas reciben de forma predeterminada el nombre secuencial Table N" comen%ando por GTableG que representa Table/. *i se pasa un nombre de tabla como argumento al m#todo /ill" las tablas reciben de forma predeterminada el nombre secuencial TableName N" comen%ando por GTableNameG que representa TableName/.

lenar un DataSet desde m5ltiples DataAdapter


*e puede utili%ar cualquier cantidad de ob$etos DataAdapter con un DataSet. 'ada DataAdapter se puede utili%ar para llenar uno o varios ob$etos Data1able y para refle$ar en el origen de datos correspondiente las actuali%aciones que sean necesarias. *e pueden agregar de forma local ob$etos DataRelation y Constraint al DataSet" de manera que se pueden relacionar datos procedentes de varios or5genes distintos. ,or e$emplo" un DataSet puede contener datos de una base de datos de -icrosoft *+L *erver" una base de datos de ;)!LE )? e&puesta mediante ) y un origen de datos que genera secuencias D-L. ,ara ocuparse de la comunicacin con

cada origen de datos se pueden usar uno o varios ob$etos DataAdapter. Ejemplo En el e$emplo de cdigo siguiente se llena una lista de clientes a partir de la base de datos )ort+wind de -icrosoft *+L *erver ?/// y una lista de pedidos a partir de la base de datos )ort+wind almacenada en -icrosoft Access ?///. Las tablas de datos llenas se relacionan entre s5 mediante DataRelation" con lo que se puede mostrar una lista de clientes con los pedidos que 6a reali%ado cada uno. ,ara obtener m4s informacin sobre ob$etos DataRelation" vea Agregar una relacin entre tablas y E&plorar una relacin entre tablas. '3 'opiar cdigo

// Assumes t!at customerConnection is a valid SqlConnection o#Ject. // Assumes t!at orderConnection is a valid OleD#Connection o#Ject. SqlDataAdapter

custAdapter = new SqlDataAdapter( $SA6AC, ; 0FO% d#o.Customers$9 customerConnection); OleD#DataAdapter ordAdapter = new OleD#DataAdapter( $SA6AC, ; 0FO% Orders$9 orderConnection); DataSet customerOrders = new DataSet(); custAdapter.0ill(customerOrders9 $Customers$); ordAdapter.0ill(customerOrders9 $Orders$); DataFelation relation = customerOrders.Felations.Add($CustOrders$9 customerOrders.,a#les2$Customers$3.Columns2$Customer'D$39 customerOrders.,a#les2$Orders$3.Columns2$Customer'D$3); +oreac! (DataFow pFow in customerOrders.,a#les2$Customers$3.Fows) { Console.5rite6ine(pFow2$Customer'D$3); +oreac! (DataFow cFow in pFow.RetC!ildFows(relation)) Console.5rite6ine($<t$ K cFow2$Order'D$3); "
Tipo decimal de SQ Ser!er
DataSet almacena de forma predeterminada los datos utili%ando tipos de datos de .NET Framework. En la mayor parte de las aplicaciones" estos tipos proporcionan una representacin adecuada de la informacin del origen de datos. *in embargo" esa representacin puede ocasionar problemas cuando el tipo de datos del origen de datos es decimal o num#rico de *+L *erver. El tipo de datos decimal de .NET Framework permite un m4&imo de ?P d5gitos significativos" mientras que el tipo decimal de *+L *erver permite @P d5gitos. 'uando S%lDataAdapter determina" durante una operacin /ill" que la precisin de un campo decimal de *+L *erver es mayor de ?P caracteres" la fila actual no se agrega a Data1able. En cambio" se produce un evento /ill&rror que le permite decidir si se debe producir o no una p#rdida de precisin y tomar las medidas adecuadas. ,ara obtener m4s informacin sobre el evento /ill&rror" vea Traba$ar con eventos ataAdapter. ,ara obtener el valor decimal de *+L *erver tambi#n" puede utili%ar un ob$eto *ql ata1eader y llamar al m#todo Net*ql ecimal. A !.NET ?./ incorpora compatibilidad me$orada para *ystem. ata.*qlTypes en el DataSet. ,ara obtener m4s informacin" vea *qlTypes y el ata*et.

Captulos de O E D"
*e pueden usar con$untos $er4rquicos de filas" o cap5tulos 9tipo D-18P&9:C:AP1&R de !LE tipo adC+apter de A !:" para llenar un DataSet. 'uando !le b ataAdapter encuentra una columna que tiene un cap5tulo" durante una operacin /ill" se crea una Data1able para ella y la tabla se llena con las columnas y filas del cap5tulo. La tabla creada para la columna con cap5tulo recibe como nombre el de la tabla primaria y el de la columna con cap5tulo. El nombre tiene el formato GnombreDeTablaPrimariaNombreDeColumnaConCaptuloG. *i ya e&iste en el DataSet una tabla que tenga el nombre de la columna con cap5tulo" esa tabla se llena con los datos del cap5tulo. *i ninguna de las columnas de la tabla e&istente coincide con una de las columnas del cap5tulo" se agrega una nueva columna a la tabla. Antes de que se llenen las tablas del DataSet con los datos de las columnas con cap5tulos" se crea una relacin entre las tablas primaria y secundaria del con$unto $er4rquico de filas> para ello" se agrega una columna de tipo entero a las tablas primaria y secundaria" se establece la propiedad de incremento autom4tico en la columna de la tabla primaria y se crea una DataRelation entre las columnas agregadas de ambas tablas. ,ara dar nombre a la relacin" se utili%an los nombres )y

de la tabla primaria y de la columna con cap5tulo con el formato GnombreDeTablaPrimariaNombreDeColumnaConCaptuloG. Tenga en cuenta que la columna relacionada slo e&iste en el DataSet. !tras operaciones de llenado que se realicen a continuacin desde el origen de datos ir4n agregando nuevas filas a las tablas en lugar de introducir cambios en las filas ya e&istentes. Tenga en cuenta adem4s que" si se utili%a una sobrecarga de DataAdapter /ill que acepte una tabla Data1able" #sa ser4 la 8nica tabla que se llene. En este caso tambi#n se agrega a la tabla una columna de tipo entero y con incremento autom4tico" aunque no se crea ni rellena ninguna tabla secundaria" ni se crea ninguna relacin. En el e$emplo siguiente se utili%a el proveedor -* ata*6ape para generar un cap5tulo con la columna de pedidos reali%ados por cada uno de los clientes de una lista. A continuacin se llena con esos datos un DataSet. '3 'opiar cdigo

using (OleD#Connection connection = new OleD#Connection($*rovider=%SDataS!ape;Data *rovider=S76O6ADB;$ K $Data Source=(local);'ntegrated Securit&=SS*';'nitial Catalog=nort!wind$)) { OleD#DataAdapter adapter = new OleD#DataAdapter($SEA*A {SA6AC, Customer'D9 Compan&1ame 0FO% Customers" $ K $A**A1D ({SA6AC, Customer'D9 Order'D 0FO% Orders" AS Orders $ K $FA6A,A Customer'D ,O Customer'D)$9 connection); DataSet customers = new DataSet(); adapter.0ill(customers9 $Customers$); "
=na ve% finali%ada la operacin /ill" el DataSet contiene dos tablas( Customers y CustomersOrders" donde CustomersOrders representa la columna con cap5tulo. *e agrega una columna adicional con el nombre Orders a la tabla Customers y una columna adicional con el nombre CustomersOrders a la tabla CustomersOrders. La columna Orders en la tabla Customers se establece con incremento autom4tico. *e crea tambi#n una relacin DataRelation" CustomersOrders" utili%ando las columnas que se 6an agregado a las tablas donde la tabla Customers es la primaria. Las siguientes tablas muestran algunos e$emplos de los resultados. 1able)ame2 Customers Custo!erID :$)?< :N:( Co!pan*Na!e :lfreds )utter+iste :na (rujillo Emparedados y 1elados Orders . -

1able)ame2 CustomersOrders Custo!erID :$)?< :$)?< :N:( :N:( OrderID -.@AB -.@CD -.B.E -.@DF Custo!ersOrders . . -

3ea tambi4n

(tili0ar par2metros con DataAdapter


DataAdapter dispone de cuatro propiedades que se utili%an para recuperar y actuali%ar datos en un origen de datos( la propiedad SelectCommand devuelve datos a partir del origen de datos> las propiedadesInsertCommand" UpdateCommand y DeleteCommand se utili%an para administrar cambios en el origen de datos. La propiedad SelectCommand se debe establecer antes de llamar al m#todo /ill de DataAdapter. Es necesario establecer las propiedades InsertCommand" UpdateCommand o DeleteCommand antes de llamar al m#todo Update del DataAdapter" en funcin de las modificaciones reali%adas en los datos en el ata*et. ,or e$emplo" si se 6an agregado filas" se debe establecer la propiedad InsertCommand antes de llamar a Update. 'uando Update procesa una fila insertada" actuali%ada o eliminada" DataAdapter utili%a la propiedad Command adecuada para la accin en cuestin. La informacin actual relacionada con la fila modificada se pasa al ob$eto Command a trav#s de la coleccin Parameters. Al actuali%ar una fila en el origen de datos" se llama a la instruccin =, ATE que utili%a un identificador 8nico para identificar la fila de la tabla que debe actuali%arse. El identificador 8nico suele ser el valor del campo de clave principal. La instruccin =, ATE utili%a par4metros que contienen el identificador 8nico y las columnas y valores que se van a actuali%ar" como muestra la siguiente instruccin TransactO*+L. 'opiar cdigo

:*DA,A Customers SA, Compan&1ame = PCompan&1ame 5EAFA Customer'D = PCustomer'D


Nota $a sinta'is de los marcadores de posicin de parmetros var"a en funcin del ori!en de datos. En este ejemplo se muestran marcadores de posicin para un ori!en de datos de S;$ Server. 7tilice si!nos de interro!acin de cierre 5G6 como marcadores de posicin de para los parmetros System.%ata.#le%b y System.%ata.#dbc. En este e$emplo de 2isual )asic" el campo Company)ame se actuali%a con el valor del par4metro @CompanyName de la fila cuyo CustomerID coincida con el valor del par4metro @CustomerID. Los par4metros recuperan informacin de la fila modificada mediante la propiedad *ource'olumn del ob$eto *ql,arameter. A continuacin se muestran los par4metros del e$emplo anterior de la instruccin =, ATE. En el cdigo se supone que el adapter de la variable representa a un ob$eto *ql ataAdapter v4lido. 'opiar cdigo

adapter.*arameters.Add( - $PCompan&1ame$9 SqlD#,&pe.1C!ar9 (L9 $Compan&1ame$) Dim parameter As Sql*arameter = adapter.:pdateCommand.*arameters.Add($PCustomer'D$9 - SqlD#,&pe.1C!ar9 L9 $Customer'D$) parameter.SourceQersion = DataFowQersion.Original
El m#todo Add de la coleccin Parameters toma de Data1able el nombre del par4metro" el tipo espec5fico de DataAdapter" el tama7o 9si corresponde al tipo: y el nombre de SourceColumn. !bserve que el valor de Source5ersion del par4metro @CustomerID est4 establecido a Original. e esta forma se garanti%a que la fila e&istente en el origen de datos se actuali%a cuando el valor de la columna o columnas identificadas 6a cambiado en la fila DataRow modificada. En ese caso" el valor de la fila Original coincidir5a con el valor actual en el origen de datos y el valor de la fila

Current contendr5a el valor actuali%ado. No se asigna ning8n valor a Source5ersion para el par4metro @CompanyName por lo que se usa el predeterminado" el de la fila Current.

E$emplo de S+lClient
En el e$emplo siguiente se muestran instrucciones *+L de e$emplo que se pueden utili%ar como Command1e't para las propiedades SelectCommand" InsertCommand" UpdateCommand y DeleteCommand del S%lDataAdapter. En el ob$eto S%lDataAdapter debe usar par4metros con nombre. '3 'opiar cdigo

string selectS76 = $SA6AC, Customer'D9 Compan&1ame 0FO% Customers 5EAFA Countr&Fegion = $ K $PCountr&Fegion A1D Cit& = PCit&$; string insertS76 = $'1SAF, '1,O Customers (Customer'D9 Compan&1ame) $ K $QA6:AS (PCustomer'D9 PCompan&1ame)$; string updateS76 = $:*DA,A Customers SA, Customer'D = PCustomer'D9 $ K $Compan&1ame = PCompan&1ame 5EAFA Customer'D = POldCustomer'D$; string deleteS76 = $DA6A,A 0FO% Customers 5EAFA Customer'D = PCustomer'D$;
E$emplo de OleDb u Odbc
En el caso de los ob$etos !le b ataAdapter y !dbc ataAdapter" debe utili%ar signos de interrogacin de cierre 9L: como marcadores de posicin para identificar los par4metros. '3 'opiar cdigo

string selectS76 = $SA6AC, Customer'D9 Compan&1ame 0FO% Customers $ K $5EAFA Countr&Fegion = T A1D Cit& = T$; string insertS76 = $'1SAF, '1,O Customers (Customer'D9 Compan&1ame) $ K $QA6:AS (T9 T)$; string updateS76 = $:*DA,A Customers SA, Customer'D = T9 Compan&1ame = T $ K $5EAFA Customer'D = T $; string deleteS76 = $DA6A,A 0FO% Customers 5EAFA Customer'D = T$;
Las instrucciones de consulta con par4metros definen qu# par4metros de entrada y de salida se deben crear. ,ara crear un par4metro" se utili%a el m#todo Parameters Add o el constructor Parameter con el fin de especificar el nombre de columna" tipo de datos y tama7o. En el caso de tipos de datos intr5nsecos" como Integer" no es necesario incluir el tama7o" aunque puede especificar el tama7o predeterminado. En el e$emplo de cdigo siguiente se crean los par4metros para la instruccin *+L del e$emplo anterior y se llena un DataSet.

S+lClient
'3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlDataAdapter adapter = new SqlDataAdapter(); SqlCommand selectC%D = new

SqlCommand(selectS769 connection); adapter.SelectCommand = selectC%D; // Add parameters and set values. selectC%D.*arameters.Add( $PCountr&Fegion$9 SqlD#,&pe.1QarC!ar9 (L).Qalue = $:U$; selectC%D.*arameters.Add( $PCit&$9 SqlD#,&pe.1QarC!ar9 (L).Qalue = $6ondon$; DataSet customers = new DataSet(); adapter.0ill(customers9 $Customers$);
OleDb

'3 'opiar cdigo

// Assumes t!at connection is a valid OleD#Connection o#Ject. OleD#DataAdapter adapter = new OleD#DataAdapter(); OleD#Command selectC%D = new OleD#Command(selectS769 connection); adapter.SelectCommand = selectC%D; // Add parameters and set values. selectC%D.*arameters.Add( $PCountr&Fegion$9 OleD#,&pe.QarC!ar9 (L).Qalue = $:U$; selectC%D.*arameters.Add( $PCit&$9 OleD#,&pe.QarC!ar9 (L).Qalue = $6ondon$; DataSet customers = new DataSet(); adapter.0ill(customers9 $Customers$);
Odbc
'3 'opiar cdigo

// Assumes t!at connection is a valid Od#cConnection o#Ject. Od#cDataAdapter adapter = new Od#cDataAdapter(); Od#cCommand selectC%D = new Od#cCommand(selectS769 connection); adapter.SelectCommand = selectC%D; //Add *arameters and set values. selectC%D.*arameters.Add($PCountr&Fegion$9 Od#c,&pe.QarC!ar9 (L).Qalue = $:U$; selectC%D.*arameters.Add($PCit&$9 Od#c,&pe.QarC!ar9 (L).Qalue = $6ondon$; DataSet customers = new DataSet(); adapter.0ill(customers9 $Customers$);
Nota Si no se proporciona un nombre para un parmetro, ste toma un nombre predeterminado y secuencial del tipo Parameter N, que comienza por =Parameter-=. Se recomienda evitar la convencin de nomenclatura del tipo =Parameter N= al asi!nar un nombre de parmetro, ya que dic1o nombre podr"a entrar en conflicto con un nombre de parmetro predeterminado e'istente en la $ara!eterCollection. Si el nombre proporcionado ya e'iste, se inicia una e'cepcin.

%arameter.DbType
El tipo de un Parameter es espec5fico del proveedor de datos de .NET Framework. Al especificar el tipo" el valor de Parameter se convierte al tipo del proveedor de datos de .NET Framework antes de pasarlo al origen de datos. Tambi#n se puede especificar el tipo de un Parameter de forma gen#rica si se establece la propiedad Db1ype del ob$eto Parameter en un determinado bType.

El tipo del proveedor de datos de .NET Framework del ob$eto Parameter se deduce del tipo de .NET Framework del valor 5alue del ob$eto Parameter o a partir del Db1ype del ob$eto Parameter. En la siguiente tabla se muestra el tipo Parameter inferido en funcin del ob$eto que se 6a pasado como valor Parameter o del Db1ype especificado. ,ipo de .NE, Fra!e(or- S*ste!.Data.Db,*pe bool b*te b*te01 /oolean /*te /inar*

S.lDb,*pe /it ,in*Int

OleDb,*pe /oolean 'nsigned,in*Int

Odbc,*pe /it ,in*Int /inar*

Oracle,*pe /*te /*te Ra(

2ar/inar*. Esta 2ar/inar* conversin impl"cita producir un error si la matriz de bytes es mayor que el tamaHo m'imo de una 2ar/inar*, que es E... bytes. En los casos de las matrices de bytes mayores de E... bytes, establezca S.lDb,*pe e'pl"citamente. No es posible deducir el valor de S.lDb,*pe a partir de c ar. C ar

c ar

C ar

/*te

Date,i!e Deci!al double float &uid Int34 Int56 Int47 ob8ect

Date,i!e Deci!al Double Single &uid Int34 Int56 Int47 Ob8ect

Date,i!e Deci!al Float Real 'ni.ueIdentifier S!allInt Int /itInt 2ariant

D/,i!eSta!p Deci!al Double Single &uid S!allInt Int /igInt 2ariant

Date,i!e Nu!eric Double Real 'ni.ueIdentifier S!allInt Int /igInt

Date,i!e Nu!ber Double Float Ra( Int34 Int56 Nu!ber

No es posible /lob deducir el valor de Odbc,*pe a partir de Ob8ect. N2arC ar N2arC ar

string

String

N2arC ar. Esta 2ar9C ar conversin impl"cita !enera un error en el caso de que la cadena sea mayor que el tamaHo m'imo de una N2arC ar, que es A.... caracteres.. En el caso de cadenas mayores de A.... caracteres, el valor de S.lDb,*pe se debe establecer de forma e'pl"cita. No es posible deducir el valor de S.lDb,*pe a partir de ,i!eSpan. No es posible deducir el valor de S.lDb,*pe a D/,i!e

,i!eSpan

,i!e

,i!e

Date,i!e

'Int34

'Int34

'nsignedS!allInt Int

'Int34

partir de 'Int34. 'Int56 'Int56 No es posible deducir el valor de S.lDb,*pe a partir de 'Int56. No es posible deducir el valor de S.lDb,*pe a partir de 'Int47. 2arC ar C ar Mone* 'nsignedInt /igInt 'Int56

'Int47

'Int47

'nsigned/igInt

Nu!eric

Nu!ber

)nsiString )nsiStringFi#ed"engt Currenc*

2arC ar C ar Currenc*

2arC ar C ar

2arC ar C ar

No es posible Nu!ber deducir el valor de Odbc,*pe a partir de Currenc*. Date Date,i!e

Date

No es posible deducir el D/Date valor de S.l,*pe a partir de Date. No es posible deducir el ,in*Int valor de S.l,*pe a partir de S/*te. NC ar 9C ar

S/*te

No es posible S/*te deducir el valor de Odbc,*pe a partir de S/*te. NC ar ,i!e NC ar Date,i!e

StringFi#ed"engt ,i!e

No es posible deducir el D/,i!e valor de S.l,*pe a partir de ,i!e. No es posible deducir el valor de S.lDb,*pe a partir de 2arNu!eric. 2arNu!eric

2arNu!eric

No es posible Nu!ber deducir el valor de Odbc,*pe a partir de 2arNu!eric.

Nota $os proveedores de datos de .NE( )rame*or+ que se incluyen con la versin -.. de .NE( )rame*or+ no verifican la precisin ni la escala de los valores de parmetros de tipo Deci!al, lo que puede provocar que se inserten datos truncados en el ori!en de datos. Si est utilizando la versin -.. de .NE( )rame*or+, valide los valores de precisin y escala de los valores de tipo Deci!al antes de establecer el valor de parmetro. En el caso de la versin -.- y versiones posteriores de .NE( )rame*or+, al confi!urar un valor de parmetro Deci!al con un valor de precisin que no es vlido, se inicia una e'cepcin. $os valores de escala que sobrepasan la escala del parmetro Deci!al si!uen truncados. Nota En el caso de la versin -.. de .NE( )rame*or+, puede utilizar System.%ata.Sql(ypes cuando trabaje con System.%ata.Sql,lient. Para obtener ms informacin, vea (rabajo con Sql(ypes.

%arameter.Direction
En la tabla siguiente se muestran los valores que se pueden utili%ar con la enumeracin ,arameter irection para establecer la Direction del Parameter. No!bre del !ie!bro Entrada InputOutput Resultados Descripcin Se trata de un parmetro de entrada. Iste es el valor predeterminado. El parmetro puede ser de entrada o de salida. Se trata de un parmetro de salida.

2alor devuelto

El parmetro representa un valor devuelto.

En el e$emplo de cdigo siguiente se muestra cmo establecer el valor Direction de Parameter. 'opiar cdigo

parameter.Direction = *arameterDirection.Output
%arameter.SourceColumn6 %arameter.Source3ersion
SourceColumn y Source5ersion se pueden pasar como argumentos al constructor Parameter o tambi#n se pueden establecer como propiedades de un Parameter e&istente. SourceColumn es el nombre de la valor. En la tabla siguiente se muestran los valores de la enumeracin su uso con Source5ersion. No!bre del !ie!bro Current Default Original $roposed Descripcin El parmetro utiliza el valor actual de la columna. Iste es el valor predeterminado. El parmetro utiliza el valor Default2alue de la columna. El parmetro utiliza el valor ori!inal de la columna. El parmetro utiliza un valor propuesto. ata1ow2ersion disponibles para ata'olumn de la ata1ow en la que se recupera el valor del Parameter. Source5ersion especifica qu# versin de DataRow debe usar DataAdapter para recuperar el

En el siguiente e$emplo de cdigo se define una instruccin =, ATE en la que la columna CustomerID se utili%a como SourceColumn para dos par4metros( @CustomerID 9SA,

Customer'D = PCustomer'D: y @ ldCustomerID 95EAFA Customer'D = POldCustomer'D:. El par4metro @CustomerID se utili%a para actuali%ar la columna
CustomerID de forma que tenga el valor actual de DataRow. En consecuencia" se usa CustomerID SourceColumn con el valor Current para Source5ersion. El par4metro @ ldCustomerID se utili%a para identificar la fila actual en el origen de datos. ado que el valor de la columna que coincide con @ ldCustomerID se encuentra en la versin Original de la fila" tambi#n se usa el mismo SourceColumn 9CustomerID: con el valor Original para Source5ersion.

S+lClient
'3 'opiar cdigo

adapter.:pdateCommand.*arameters.Add( $PCustomer'D$9 SqlD#,&pe.1C!ar9 L9 $Customer'D$); adapter.:pdateCommand.*arameters.Add( $PCompan&1ame$9 SqlD#,&pe.1QarC!ar9 >=9 $Compan&1ame$); Sql*arameter parameter = adapter.:pdateCommand.*arameters.Add( $POldCustomer'D$9 SqlD#,&pe.1C!ar9 L9 $Customer'D$); parameter.SourceQersion = DataFowQersion.Original;
(pdated/o*Source

,uede controlar la forma en que los valores devueltos desde el origen de datos se asignan al DataSet. ,ara ello" puede usar la propiedad UpdatedRowSource del ob$eto Command. Al asignar la propiedad UpdatedRowSource a uno de los valores de enumeracin =pdate1ow*ource" puede determinar si los par4metros que devuelve el comando DataAdapter se deben omitir o aplicar a la fila cambiada en el DataSet. Tambi#n puede especificar si la primera fila devuelta 9si e&iste: se aplica a la fila modificada en el DataSet. En la tabla siguiente se describen los distintos valores de la enumeracin UpdateRowSource y la forma en que afectan al comportamiento del comando usado con DataAdapter. 'pdateRo(Source /ot Descripcin (anto los parmetros de salida como la primera fila del conjunto de resultados devuelto se pueden asi!nar a la fila modificada en DataSet.

FirstReturnedRecord Slo los datos de la primera fila del conjunto de resultados devuelto se pueden asi!nar a la fila modificada en el DataSet. Ninguna Output$ara!eters Se pasan por alto todos los parmetros de salida y las filas del conjunto de resultados devuelto. Slo los parmetros de salida se pueden asi!nar a la fila modificada del DataSet.

3ea tambi4n

Traba$ar con e!entos DataAdapter


DataAdapter de A !.NET e&pone tres eventos que se pueden utili%ar para responder a los cambios efectuados en los datos en el origen. En la tabla siguiente se muestran los eventos de DataAdapter.

Evento

Descripcin

Ro('pdating Est a punto de comenzar una operacin 7P%:(E, <NSE ( o %E$E(E en una fila 5mediante una llamada a uno de los mtodos 'pdate6. Ro('pdated Se 1a completado una operacin 7P%:(E, <NSE ( o %E$E(E en una fila 5mediante una llamada a uno de los mtodos 'pdate6. FillError Se 1a producido un error durante una operacin Fill.

/o*(pdating y /o*(pdated
El evento RowUpdating se activa antes de que se produ%ca la actuali%acin de una fila del ata*et en el origen de datos. El evento RowUpdated se activa despu#s de producirse la actuali%acin de una fila del DataSet en el origen de datos. ,or lo tanto" puede utili%ar RowUpdating para modificar el comportamiento de la actuali%acin antes de que tenga lugar" proporcionar un control adicional del proceso durante la actuali%acin" conservar una referencia a la fila actuali%ada" cancelar la actuali%acin actual y programarla como parte de un proceso por lotes que se e$ecutar4 despu#s" entre otras acciones. RowUpdated es 8til para reaccionar cuando se producen errores y e&cepciones durante la actuali%acin. ,uede agregar informacin de errores al DataSet" as5 como procedimientos de reintento" etc#tera. Los argumentos 1ow=pdatingEventArgs y 1ow=pdatedEventArgs pasados a los eventos RowUpdating y RowUpdated incluyen( una propiedad Command que 6ace referencia al ob$eto Command que se est4 utili%ando para reali%ar la actuali%acin> una propiedad Row que 6ace

referencia al ob$eto DataRow que contiene la informacin actuali%ada> una propiedad Statement1ype para el tipo de actuali%acin que se est4 llevando a cabo> la 1able(apping" si es pertinente y el Status de la operacin. ,uede utili%ar la propiedad Status para determinar si se 6a producido o no un error durante la operacin y" si as5 se desea" controlar las acciones que se emprenden con las filas actuales y las resultantes de la operacin. 'uando se produce el evento" la propiedad Status toma el valor Continue o &rrorsOccurred. En la tabla siguiente se muestran los valores que se pueden asignar a la propiedad Status para controlar las acciones siguientes en el proceso de actuali%acin.

Estado Continue ErrorsOccurred S-ipCurrentRo( S-ip)llRe!ainingRo(s

Descripcin ,ontinuar la operacin de actualizacin. :nular la operacin de actualizacin e iniciar una e'cepcin. #mitir la fila actual y continuar la operacin de actualizacin. :nular la operacin de actualizacin sin iniciar una e'cepcin.

Al asignar a la propiedad Status al valor &rrorsOccurred se inicia una e&cepcin. ,uede controlar qu# e&cepciones se inician si establece el valor correspondiente a la e&cepcin deseada en la propiedad &rrors. El uso de un valor distinto para la propiedad Status evita que se inicie una e&cepcin. Tambi#n puede utili%ar la propiedad ContinueUpdateOn&rror para controlar los errores producidos por las filas actuali%adas. 'uando DataAdapter ContinueUpdateOn&rror tiene el valor true y la actuali%acin de una fila inicia una e&cepcin" el te&to de la e&cepcin se coloca en la informacinRow&rror de la fila en cuestin y el proceso contin8a sin que se inicie una e&cepcin. detectan. En el e$emplo de cdigo siguiente se muestra cmo se pueden agregar y quitar controladores de eventos. El controlador de eventos RowUpdating mantiene un registro con todos los registros eliminados y una marca de tiempo asociada a cada uno de ellos. El controlador de eventos RowUpdated agrega informacin de error a la propiedad Row&rror de la fila correspondiente en el DataSet" evita que se inicie la e&cepcin y de$a continuar el proceso 9al igual que ContinueUpdateOn&rror I true:. '3 'opiar cdigo e esta forma" puede reaccionar frente a errores cuando finalice el proceso Update" a diferencia del evento RowUpdated" que permite reaccionar frente a los errores cuando se

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlDataAdapter custAdapter = new SqlDataAdapter( $SA6AC, Customer'D9 Compan&1ame 0FO% Customers$9 connection); // Add !andlers. custAdapter.Fow:pdating K= new SqlFow:pdatingAventEandler(OnFow:pdating); custAdapter.Fow:pdated K= new SqlFow:pdatedAventEandler(OnFow:pdated); // Set DataAdapter command properties9 +ill DataSet9 modi+& DataSet. custAdapter.:pdate(custDS9 $Customers$); // Femove !andlers. custAdapter.Fow:pdating O= new SqlFow:pdatingAventEandler(OnFow:pdating); custAdapter.Fow:pdated O= new SqlFow:pdatedAventEandler(OnFow:pdated); protected static void OnFow:pdating( o#Ject sender9 SqlFow:pdatingAventArgs args) { i+

(args.Statement,&pe == Statement,&pe.Delete) { S&stem.'O.,eCt5riter tw = S&stem.'O.0ile.Append,eCt($Deletes.log$); tw.5rite6ine( ${="8 Customer {(" Deleted.$9 Date,ime.1ow9 args.Fow2$Customer'D$9 DataFowQersion.Original3); tw.Close(); " " protected static void OnFow:pdated( o#Ject sender9 SqlFow:pdatedAventArgs args) { i+ (args.Status == :pdateStatus.ArrorsOccurred) { args.Fow.FowArror = args.Arrors.%essage; args.Status = :pdateStatus.S ipCurrentFow; " "
-illError
DataAdapter emite el evento /ill&rror cuando se produce un error durante una operacin de llenado 9/ill:. Este tipo de error suele producirse si al convertir los datos de la fila que se agrega a un tipo de .NET Framework se produce una p#rdida de precisin. *i el error se produce durante una operacin /ill" la fila actual no se agrega a Data1able. El evento /ill&rror permite resolver el error y agregar la fila" o pasar por alto la fila e&cluida y continuar con la operacin /ill. El ob$eto /ill&rror&ventArgs que se pasa al evento /ill&rror puede contener varias propiedades que permiten reaccionar en caso de errores y resolverlos. En la tabla siguiente se muestran las propiedades del ob$eto /ill&rror&ventArgs.

$ropiedad Descripcin Errors E#cepcin producida.

Data,abl #bjeto Data,able que se estaba llenando cuando ocurri el error. e 2alores 0atriz de objetos que contiene los valores de la fila que se est a!re!ando cuando se produce el error. $as referencias de orden de la matriz 2alues coinciden con las de las columnas de la fila que se estaba a!re!ando. Por ejemplo, 2alues0:1 es el valor que se a!re!a en la primera columna de la fila. Permite ele!ir si desea iniciar una e'cepcin o no. Si asi!na a la propiedad Continue el valor false, la operacin Fill en curso se detiene y se inicia una e'cepcin. Si a la propiedad Continue se le asi!na el valor true, la operacin Fill contin9a pese al error.

Continue

En el e$emplo de cdigo siguiente se agrega un controlador de eventos para el evento /ill&rror de DataAdapter. En el cdigo del evento /ill&rror" el e$emplo determina si 6ay una posible p#rdida de precisin y ofrece la posibilidad de reaccionar en ese caso. '3 'opiar cdigo

adapter.0illArror K= new 0illArrorAventEandler(0illArror); DataSet dataSet = new DataSet(); adapter.0ill(dataSet9 $,!is,a#le$); protected static void 0illArror(o#Ject sender9 0illArrorAventArgs args) { i+ (args.Arrors.Ret,&pe() == t&peo+(S&stem.Over+lowACception)) { // Code to !andle precision loss. //Add a row to ta#le using t!e values +rom t!e +irst two columns. DataFow m&Fow = args.Data,a#le.Fows.Add(new o#Ject23 {args.Qalues2=39 args.Qalues2(39 DB1ull.Qalue"); //Set t!e FowArror containing t!e value +or t!e t!ird column. args.FowArror = $Over+lowACception Ancountered. Qalue +rom data source8 $ K args.Qalues2)3; args.Continue = true; " "

3ea tambi4n

Agregar restricciones existentes a DataSet


El m#todo /ill de DataAdapter llena un ata*et slo con las columnas y filas de un origen de datos. Aunque las restricciones se suelen establecer en el origen de datos" el m#todo /ill no agrega de forma predeterminada esta informacin del esquema al DataSet. ,ara llenar un DataSet con la informacin de restricciones de clave principal e&istentes en el origen de datos" puede llamar al m#todo /illSc+ema de DataAdapter o establecer la propiedad (issingSc+emaAction de DataAdapter con el valor Add#it+7ey antes de llamar a /ill. de datos. La informacin de restricciones de clave e&terna no se incluye y se debe crear e&pl5citamente" como se muestra en Agregar restricciones a una tabla. Al agregar la informacin del esquema a un DataSet antes de llenarlo con datos" se garanti%a que se incluyen las restricciones de clave principal con los ob$etos ataTable en el DataSet. 'omo resultado" al reali%ar llamadas adicionales para llenar el DataSet" la informacin de la columna de clave principal se puede utili%ar para 6acer coincidir las nuevas filas del origen de datos con las filas actuales de cada una de las tablas Data1able" con lo que los datos actuales de las tablas se sobrescriben con los del origen de datos. *in la informacin del esquema" las filas nuevas del origen de datos se agregan al DataSet" con lo que se obtienen filas duplicadas. e esta forma se garanti%a que las restricciones de clave principal del DataSet refle$an las del origen

Nota Si una columna del ori!en de datos es de incremento automtico, el mtodo FillSc e!a o el mtodo Fill con el valor )dd9it +e* en la propiedad MissingSc e!a)ction crean una DataColu!n con el valor de la propiedad )utoIncre!ent establecido como true. Sin embar!o, en este caso debe definir manualmente los valores de )utoIncre!entStep y )utoIncre!entSeed. Para obtener ms informacin sobre las columnas de incremento automtico, vea ,rear columnas :uto<ncrement. Al utili%ar /illSc+ema o establecer el valor Add#it+7ey en (issingSc+emaAction" se precisa un proceso adicional en el origen de datos para determinar la informacin de la columna de clave principal. Este proceso adicional puede reducir el rendimiento. *i conoce en la fase de dise7o la informacin de la clave principal" es aconse$able especificar de modo e&pl5cito la columna o columnas que la forman para me$orar el rendimiento. ,ara obtener m4s informacin sobre la especificacin de modo e&pl5cito de informacin de la clave principal de una tabla" vea clave principal para una tabla. En el e$emplo de cdigo siguiente se muestra cmo agregar la informacin del esquema a un DataSet mediante /illSc+ema. '3 'opiar cdigo efinir una

DataSet custDataSet = new DataSet(); custAdapter.0illSc!ema(custDataSet9 Sc!ema,&pe.Source9 $Customers$); custAdapter.0ill(custDataSet9 $Customers$);


En el e$emplo de cdigo siguiente se muestra la forma de agregar la informacin del esquema a un DataSet mediante la propiedad (issingSc+emaAction Add#it+7ey del m#todo /ill. '3 'opiar cdigo

DataSet custDataSet = new DataSet(); custAdapter.%issingSc!emaAction = %issingSc!emaAction.Add5it!Ue&; custAdapter.0ill(custDataSet9 $Customers$);

3arios con$untos de resultados


'uando DataAdapter encuentra varios con$untos de resultados devueltos por SelectCommand" crea varias tablas en el DataSet. Las tablas reciben de forma predeterminada el nombre secuencial que comien%a en cero 1ableN" comen%ando por 1able en lugar de GTable/G. *i se pasa un nombre de tabla como argumento al m#todo /illSc+ema" las tablas reciben el nombre secuencial que comien%a por cero 1able)ameN" comen%ando por 1able)ame en lugar de GTableName/G.

Nota Si se llama al mtodo FillSc e!a del objeto OleDbData)dapter para un comando que devuelve m9ltiples conjuntos de resultados, slo se devuelve la informacin del esquema del primer conjunto de resultados. :l devolver la informacin del esquema de m9ltiples conjuntos de resultados mediante OleDbData)dapter, es aconsejable asi!nar a la propiedad MissingSc e!a)ction el valor )dd9it +e* y obtener la informacin del esquema al llamar al mtodo Fill.

3ea tambi4n

Especi'icar par2metros y !alores de!ueltos


Los procedimientos almacenados pueden tener valores devueltos" adem4s de tener par4metros de entrada y de salida. En el e$emplo siguiente se muestra la forma en que A !.NET env5a y recibe par4metros de entrada" par4metros de salida y valores devueltos mediante la insercin de un registro nuevo en una tabla que tiene como columna de clave principal una columna de identidad en una base de datos de *+L *erver.

E$emplo
En el e$emplo se utili%a el siguiente procedimiento almacenado para insertar una categor5a nueva en la tabla )ort+wind Categories" que toma el valor de la columna Category)ame como par4metro de entrada y utili%a la funcin *'!,EQ; ENT;TJ9: para recuperar el nuevo valor del campo de identidad CategoryID y devolverlo en un par4metro de salida. La instruccin 1ET=1N utili%a la funcin RR1!<'!=NT para devolver el n8mero de filas insertadas. 'opiar cdigo

CFAA,A *FOCAD:FA 'nsertCategor& PCategor&1ame nc!ar((L)9 P'dentit& int O:,*:, AS SA, 1OCO:1, O1 '1SAF, '1,O Categories (Categor&1ame) QA6:AS(PCategor&1ame) SA, P'dentit& = SCO*A-'DA1,',G() FA,:F1 PPFO5CO:1,
En el siguiente e$emplo se utili%a el anterior procedimiento almacenado InsertCategory como origen de la propiedad ;nsert'ommand de *ql ataAdapter. El par4metro de salida ;Identity y el valor devuelto se refle$ar4n en ata*et una ve% que se 6aya insertado el registro en la base de dados al llamar al m#todo Update del S%lDataAdapter.

Nota En el caso de #le%b%ata:dapter, los parmetros con un Parameter%irection con el valor Return2alue se deben especificar antes que los restantes parmetros. '3 'opiar cdigo

// Assumes t!at connection represents a SqlConnection o#Ject. SqlDataAdapter adapter = new SqlDataAdapter( $SA6AC, Categor&'D9 Categor&1ame 0FO% d#o.Categories$9 connection); adapter.'nsertCommand = new SqlCommand($'nsertCategor&$9 connection); adapter.'nsertCommand.Command,&pe = Command,&pe.Stored*rocedure; Sql*arameter parameter = adapter.'nsertCommand.*arameters.Add( $PFowCount$9 SqlD#,&pe.'nt); parameter.Direction = *arameterDirection.FeturnQalue; adapter.'nsertCommand.*arameters.Add( $PCategor&1ame$9 SqlD#,&pe.1C!ar9 (L9 $Categor&1ame$); parameter = adapter.'nsertCommand.*arameters.Add( $P'dentit&$9 SqlD#,&pe.'nt9 =9 $Categor&'D$); parameter.Direction = *arameterDirection.Output; DataSet categoriesDS = new DataSet(); adapter.0ill(categoriesDS9 $Categories$); DataFow newFow = categoriesDS.,a#les2$Categories$3.1ewFow(); newFow2$Categor&1ame$3 = $1ew Categor&$; categoriesDS.,a#les2$Categories$3.Fows.Add(newFow); adapter.:pdate(categoriesDS9 $Categories$); 'nt?) rowCount = ('nt?))adapter.'nsertCommand.*arameters2$PFowCount$3.Qalue;

/ecuperar !alores de identidad o de autonumeracin


Es posible establecer una columna de un ataTable para que sea una clave principal de incremento autom4tico con el fin de garanti%ar que 6aya un valor 8nico para cada fila de la tabla. *in embargo" es posible tener varios clientes de la aplicacin y cada uno de esos clientes puede traba$ar con una instancia diferente de la tabla Data1able. En este caso" podr5a 6aber valores duplicados en las distintas instancias de la Data1able. 'omo todos los clientes traba$an con un 8nico origen de datos" es posible resolver este conflicto si se permite que el origen de datos defina el valor de incremento autom4tico. ,ara ello se utili%an columnas de identidad en -icrosoft *+L *erver o campos Autonum#rico en -icrosoft Access. El uso del origen de datos con el fin de llenar una columna ;dentidad o Autonum#rica para una nueva fila agregada a un ata*et crea una situacin 8nica" ya que el DataSet no tiene cone&in directa con el origen de datos. ,or tanto" el DataSet no conoce los valores generados autom4ticamente por el origen de datos. *in embargo" con un origen de datos que pueda crear procedimientos almacenados con par4metros de salida" como -icrosoft *+L *erver" se pueden especificar los valores generados autom4ticamente" como un nuevo valor de identidad" como par4metro de salida" y utili%ar DataAdapter para volver a asignar el valor a la columna del DataSet. ,uede que el origen de datos pertinente no admita procedimientos almacenados con par4metros de salida. En este caso es posible utili%ar el evento RowUpdated para recuperar un valor generado autom4ticamente y ponerlo en la fila insertada o actuali%ada del DataSet. Esta seccin incluye un e$emplo que muestra cmo se puede utili%ar -icrosoft Access ?/// o posterior y el proveedor de datos !LE ) de Fet C./ con el fin de agregar cdigo al evento RowUpdated para determinar si se 6a producido una insercin y recuperar el valor de incremento autom4tico y almacenarlo en la fila actuali%ada en estos momentos.

/ecuperar !alores de columnas de identidad de SQ Ser!er


El procedimiento almacenado y el e$emplo de cdigo siguientes muestran cmo asignar el valor de identidad de incremento autom4tico desde una tabla de -icrosoft *+L *erver a su columna correspondiente en una fila agregada a una tabla de un DataSet. El procedimiento almacenado se utili%a para insertar una nueva fila en la tabla Categories de la base de datos )ort+wind y para devolver el valor de identidad devuelto por la funcin TransactO*+L *'!,EQ; ENT;TJ9: como par4metro de salida. 'opiar cdigo

CFAA,A *FOCAD:FA 'nsertCategor& PCategor&1ame nc!ar((L)9 P'dentit& int O:, AS '1SAF, '1,O Categories (Categor&1ame) QA6:AS(PCategor&1ame) SA, P'dentit& = SCO*A-'DA1,',G()
A continuacin" el procedimiento almacenado InsertCategory se puede especificar como el origen del ;nsert'ommand. *e crea un par4metro para recibir el par4metro de salida de identidad. Ese par4metro tiene un valor ,arameter irection de !utput y tiene una SourceColumn especificada como columna CategoryID de la tabla local Categories en el DataSet. 'uando se procesa InsertCommand para una fila agregada" se devuelve el valor de identidad de incremento autom4tico como este par4metro de salida y se coloca en la columna CategoryID de la fila actual. En el siguiente e$emplo de cdigo se muestra cmo devolver el valor de incremento autom4tico como el par4metro de salida y cmo especificarlo como el valor de origen para la columna CategoryID del DataSet. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlDataAdapter adapter = new SqlDataAdapter( $SA6AC, Categor&'D9 Categor&1ame 0FO% d#o.Categories$9 connection); adapter.'nsertCommand = new SqlCommand($'nsertCategor&$9 connection); adapter.'nsertCommand.Command,&pe = Command,&pe.Stored*rocedure; adapter.'nsertCommand.*arameters.Add( $PCategor&1ame$9 SqlD#,&pe.1C!ar9 (L9 $Categor&1ame$); Sql*arameter parameter = adapter.'nsertCommand.*arameters.Add( $P'dentit&$9 SqlD#,&pe.'nt9 =9 $Categor&'D$); parameter.Direction = *arameterDirection.Output; connection.Open(); DataSet categories = new DataSet(); adapter.0ill(categories9 $Categories$); DataFow newFow = categories.,a#les2$Categories$3.1ewFow(); newFow2$Categor&1ame$3 = $1ew Categor&$; categories.,a#les2$Categories$3.Fows.Add(newFow); adapter.:pdate(categories9 $Categories$); connection.Close();
/ecuperar !alores de autonumeracin de ,icroso't Access
-icrosoft Access no admite procedimientos almacenados ni el procesamiento de comandos por lotes" por lo que no es posible asignar un par4metro de salida a la columna de origen de la tabla en el e$emplo anterior. *in embargo" -icrosoft Access ?/// o posterior admite la propiedad ;;ID&)1I18 para recuperar el valor de un campo Autonum#rico despu#s de una insercin 9con

;N*E1T:. -ediante el evento RowUpdated es posible determinar si se 6a producido una insercin" recuperar el 8ltimo valor de autonumeracin y ponerlo en la columna de identidad de la tabla local del DataSet. En el siguiente e$emplo de cdigo se muestra cmo insertar un nuevo valor en la tabla 'ategories de la base de datos )ort+wind de -icrosoft Access ?/// mediante un !le b ataAdapter. En el e$emplo se utili%a el evento RowUpdated para rellenar los valores de autonumeracin generados por el motor Fet y la base de datos de Access al insertar un registro en la tabla Categories. Hay que tener en cuenta que esto slo funcionar4 con el proveedor !LE Access ?/// o posterior. ) de Fet C./ y -icrosoft

'3 'opiar cdigo

// Assumes t!at connection is a valid OleD#Connection o#Ject. OleD#DataAdapter adapter = new OleD#DataAdapter( $SA6AC, Categor&'D9 Categor&1ame 0FO% Categories OFDAF BG Categor&'D$9 connection); adapter.'nsertCommand = new OleD#Command( $'1SAF, '1,O Categories (Categor&1ame) Qalues(T)$9 connection); adapter.'nsertCommand.Command,&pe = Command,&pe.,eCt; adapter.'nsertCommand.*arameters.Add( - $PCategor&1ame$9 OleD#,&pe.C!ar9 (L9 $Categor&1ame$); connection.Open(); // 0ill t!e DataSet. DataSet categories = new DataSet(); adapter.0ill(categories9 $Categories$); // Add a new row. DataFow newFow = categories.,a#les2$Categories$3.1ewFow(); newFow2$Categor&1ame$3 = $1ew Categor&$; categories.,a#les2$Categories$3.Fows.Add(newFow); // 'nclude an event to +ill in t!e Autonum#er value. adapter.Fow:pdated K= new OleD#Fow:pdatedAventEandler(OnFow:pdated); // :pdate t!e DataSet. adapter.:pdate(categories9 $Categories$); connection.Close(); // Avent procedure +or OnFow:pdated protected static void OnFow:pdated( o#Ject sender9 OleD#Fow:pdatedAventArgs args) { // 'nclude a varia#le and a command to retrieve t!e identit& value +rom t!e Access data#ase. int new'D = =; OleD#Command idC%D = new OleD#Command( $SA6AC, PP'DA1,',G$9 connection); i+ (args.Statement,&pe == Statement,&pe.'nsert) { // Fetrieve t!e identit& value and store it in t!e Categor&'D column. new'D = (int)idC%D.ACecuteScalar(); args.Fow2$Categor&'D$3 = new'D; " "
3ea tambi4n

Con'igurar las asignaciones de DataTable y DataColumn


DataAdapter contiene una coleccin con cero o m4s ob$etos ataTable-apping en su propiedad ataTable. El nombre del 1able(appings. =n ob$eto Data1able(apping proporciona una asignacin principal entre los datos devueltos por una consulta reali%ada en un origen de datos y una ob$eto Data1able(apping se puede pasar en lugar del nombre de Data1able al m#todo /ill de DataAdapter. En el e$emplo siguiente se crea un ob$eto Data1able(apping denominado Aut+ors(apping en la tabla Aut+ors.

'3 'opiar cdigo

wor Adapter.,a#le%appings.Add($Aut!ors%apping$9 $Aut!ors$);


=n ob$eto Data1able(apping permite usar en un Data1able nombres de columna distintos a los de la base de datos. DataAdapter usa la asignacin para 6acer coincidir las columnas al actuali%ar la tabla. *i no se especifica un nombre de 1able)ame o de Data1able(apping al llamar a los m#todos /ill o Update de DataAdapter" DataAdapter busca un ob$eto Data1able(apping denominado GTableG. *i no e&iste ese ob$eto Data1able(apping" el nombre de 1able)ame de Data1able es GTableG. ,uede especificar un ob$eto Data1able(apping predeterminado si crea un Data1able(apping denominado GTableG. En el e$emplo de cdigo siguiente se crea un Data1able(apping 9a partir del espacio de nombres *ystem. ata.'ommon: y" al darle el nombre GTableG" lo convierte en la asignacin predeterminada del DataAdapter especificado. A continuacin" en el e$emplo se asignan las columnas de la primera tabla de resultados de la consulta 9la tabla Customers de la base de datos )ort+wind: a un con$unto de nombres m4s descriptivos e&istentes en la tabla )ort+wind Customers del origen de datos. '3 'opiar cdigo ata*et. En las columnas que no se asignan se usa el nombre de la columna en el

Data,a#le%apping mapping = adapter.,a#le%appings.Add($,a#le$9 $1ort!windCustomers$); mapping.Column%appings.Add($Compan&1ame$9 $Compan&$); mapping.Column%appings.Add($Contact1ame$9 $Contact$); mapping.Column%appings.Add($*ostalCode$9 $V'*Code$); adapter.0ill(custDS);
En situaciones m4s avan%adas" puede que desee que el mismo DataAdapter permita la carga de distintas tablas" cada una con sus propias asignaciones. ,ara ello" basta con agregar m4s ob$etos Data1able(apping. 'uando al m#todo /ill se le pasa una instancia de un DataSet y un nombre de Data1able(apping" se utili%a la asignacin de ese nombre" si e&iste" o un Data1able con ese nombre" en caso contrario. En los e$emplos siguientes se crea un Data1able(apping denominado Customers y una Data1able denominada -i01al$Sc+ema. En el e$emplo se asignan a continuacin las filas devueltas por la instruccin *ELE'T a la Data1able -i01al$Sc+ema. '3 'opiar cdigo

',a#le%apping mapping = adapter.,a#le%appings.Add($Customers$9 $BiW,al Sc!ema$); mapping.Column%appings.Add($Customer'D$9 $Client'D$); mapping.Column%appings.Add($Compan&1ame$9 $Client1ame$); mapping.Column%appings.Add($Contact1ame$9 $Contact$); mapping.Column%appings.Add($*ostalCode$9 $V'*$); adapter.0ill(custDS9 $Customers$);
Nota

Si no se proporciona un nombre de columna de ori!en en una asi!nacin de columnas o no se identifica un nombre de tabla de ori!en en una asi!nacin de tablas, se !eneran nombres predeterminados de forma automtica. Si no se proporciona una columna de ori!en en una asi!nacin de columnas, la asi!nacin de columna recibe el nombre predeterminado secuencial SourceColu!nN, comenzando por SourceColu!n3. Si no se proporciona un nombre de tabla de ori!en en una asi!nacin de tablas, la asi!nacin de tabla recibe el nombre predeterminado secuencial Source,ableN, comenzando por Source,able3. Nota Es recomendable evitar la convencin de nombres de SourceColu!nN para una asi!nacin de columnas o Source,ableN para una asi!nacin de tablas, ya que es posible que el nombre proporcionado entre en conflicto con el nombre predeterminado de asi!nacin de columnas de la Colu!nMappingCollection o con el nombre de asi!nacin de tablas de la Data,ableMappingCollection. Si el nombre proporcionado ya e'iste, se iniciar una e'cepcin.

3arios con$untos de resultados


'uando SelectCommand devuelve varias tablas" /ill genera autom4ticamente nombres de tabla con valores secuenciales para todas las tablas del DataSet" comen%ando por el nombre de tabla especificado y continuando con el nombre 1able)ameN" el cual comien%a por 1able)ame<. ,uede usar asignaciones de tabla para asignar el nombre generado autom4ticamente a un nombre que desee especificar para la tabla en el DataSet. ,or e$emplo" en el caso de un SelectCommand que devuelve dos tablas" Customers y Orders" puede emitir la llamada siguiente a /ill. 'opiar cdigo

adapter.0ill(customersDataSet9 $Customers$)
*e crean dos tablas en el DataSet( Customers y Customers<. ,uede usar asignaciones de tabla para asegurarse de que la segunda se denomina Orders en lugar de Customers<. ,ara ello" asigne la tabla de origen de Customers< a la tabla Orders del DataSet" como se muestra en el e$emplo siguiente. 'opiar cdigo

adapter.,a#le%appings.Add($Customers($9 $Orders$) adapter.0ill(customersDataSet9 $Customers$)


3ea tambi4n /e'erencia
DataAdapter escribe la clase DataAdapter" as5 como todos sus miembros. OdbcDataAdapter escribe la clase OdbcDataAdapter" as5 como todos sus miembros. OleDbDataAdapter escribe la clase OleDbDataAdapter" as5 como todos sus miembros. OracleDataAdapter escribe la clase OracleDataAdapter" as5 como todos sus miembros. S%lDataAdapter escribe la clase S%lDataAdapter" as5 como todos sus miembros.

3ea tambi4n

Traba$o con Data/eaders


,uede utili%ar el DataReader de A !.NET para recuperar secuencias de datos de slo lectura y slo avance de una base de datos. Los resultados se devuelven a medida que se e$ecuta la consulta y se almacenan en el b8fer de red del cliente 6asta que se solicitan con el m#todo Read del DataReader. 'on el DataReader puede aumentar el rendimiento de la aplicacin tanto al recuperar datos en cuanto est4n disponibles como al almacenar 9de forma predeterminada: una fila cada ve% en memoria" lo que reduce la sobrecarga del sistema. 'ada proveedor de datos de .NET Framework incluido en .NET Framework cuenta con un ob$eto DataReader( el proveedor de datos de .NET Framework para !LE ) incluye un ob$eto !le b ata1eader" el proveedor de datos de .NET Framework para *+L *erver incluye un ob$eto *ql ata1eader" el proveedor de datos de .NET Framework para ! )' incluye un ob$eto !dbc ata1eader y el proveedor de datos de .NET Framework para !racle incluye un ob$eto !racle ata1eader.

En esta seccin

/ecuperar datos mediante Data/eader


La recuperacin de datos mediante DataReader implica crear una instancia del ob$eto Command y de un DataReader a continuacin" para lo cual se llama a Command &'ecuteReader a fin de recuperar filas de un origen de datos. En el e$emplo siguiente se muestra cmo se utili%a un *ql ata1eader" donde command representa un ob$eto *ql'ommand v4lido. S'3T 'opiar cdigo

SqlDataFeader reader = command.ACecuteFeader();


,uede utili%ar el m#todo Read del ob$eto DataReader para obtener una fila a partir de los resultados de una consulta. ,ara tener acceso a cada columna de la fila devuelta" puede pasar a DataReader el nombre o referencia num#rica de la columna en cuestin. *in embargo" el me$or rendimiento se logra con los m#todos que ofrece DataReader y que permiten tener acceso a los valores de las columnas en sus tipos de datos nativos 96etDate1ime" 6etDouble" 6et6uid" 6etInt=>" etc:. ,ara obtener una lista de m#todos de descriptor de acceso con tipo para DataReaders de proveedores de datos espec5ficos" vea las secciones !le b ata1eader y S%lDataReader. *i se utili%an los m#todos de descriptor de acceso con tipo" dando por supuesto que se conoce el tipo de datos subyacentes" se reduce el n8mero de conversiones de tipo necesarias para recuperar el valor de una columna.

Nota En la versin de .NE( )rame*or+ de /indo*s Server D..B se incluye una propiedad adicional para el DataReader, ;asRo(s, que permite determinar si el DataReader 1a devuelto al!9n resultado antes de realizar una lectura del mismo. En el e$emplo de cdigo siguiente se repite por un ob$eto DataReader y se devuelven dos columnas de cada fila. S'3T

'opiar cdigo

i+ (reader.EasFows) w!ile (reader.Fead()) Console.5rite6ine($<t{="<t{("$9 reader.Ret'nt?)(=)9 reader.RetString(()); else Console.5rite6ine($1o rows returned.$); reader.Close();
DataReader proporciona una secuencia de datos sin b8fer que permite a la lgica de los procedimientos procesar efica%mente y de forma secuencial los resultados procedentes de un origen de datos. DataReader es la me$or opcin cuando se trata de recuperar grandes cantidades de datos" ya que #stos no se almacenan en la memoria cac6#.

Cerrar el Data/eader
*iempre debe llamar al m#todo Close cuando 6aya terminado de utili%ar el ob$eto DataReader. *i Command contiene par4metros de salida o valores devueltos" #stos no estar4n disponibles 6asta que se cierre el DataReader. Tenga en cuenta que mientras est4 abierto un DataReader" #ste utili%a de forma e&clusiva el ob$eto Connection. No se podr4 e$ecutar ning8n comando para el ob$eto Connection 6asta que se cierre el DataReader original" incluida la creacin de otro DataReader.

Nota No llame a Close o Dispose para objetos Connection o DataReader ni para nin!9n otro objeto administrado en el mtodo Finalize de su clase. En un finalizador, libere slo los recursos no administrados que pertenezcan directamente a su clase. Si la clase no dispone de recursos no administrados, no incluya un mtodo Finalize en la definicin de clase. Para obtener ms informacin, consulte ecoleccin de elementos no utilizados.

3arios con$untos de resultados


En el caso en que se devuelvan varios resultados" el DataReader proporciona el m#todo )e'tResult para recorrer los con$untos de resultados en orden. En el siguiente e$emplo se muestra el S%lDataReader mientras procesa los resultados de las dos instrucciones *ELE'T mediante el m#todo E&ecute1eader. S'3T 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand command = new SqlCommand( $SA6AC, Categor&'D9 Categor&1ame 0FO% d#o.Categories;$ K $SA6AC, Amplo&ee'D9 6ast1ame 0FO% d#o.Amplo&ees$9 connection); connection.Open(); SqlDataFeader reader = command.ACecuteFeader(); do { Console.5rite6ine($<t{="<t{("$9 reader.Ret1ame(=)9 reader.Ret1ame(()); w!ile (reader.Fead()) Console.5rite6ine($<t{="<t{("$9 reader.Ret'nt?)(=)9 reader.RetString(()); " w!ile (reader.1eCtFesult()); reader.Close(); connection.Close();
Obtener in'ormacin del es+uema a partir del Data/eader

-ientras 6ay abierto un DataReader" puede utili%ar el m#todo 6etSc+ema1able para recuperar informacin del esquema acerca del con$unto actual de resultados. 6etSc+ema1able devuelve un ob$eto ataTable rellenado con filas y columnas que contienen la informacin del esquema del con$unto actual de resultados. Data1able contiene una fila por cada una de las columnas del con$unto de resultados. 'ada columna de una fila de la tabla de esquema est4 asociada a una propiedad de la columna que se devuelve en el con$unto de resultados. Column)ame es el nombre de la propiedad y el valor de la columna es el de la propiedad. En el e$emplo de cdigo siguiente se muestra la informacin del esquema de DataReader. S'3T 'opiar cdigo

Data,a#le sc!ema,a#le = reader.RetSc!ema,a#le(); +oreac! (DataFow row in sc!ema,a#le.Fows) { +oreac! (DataColumn column in sc!ema,a#le.Columns) Console.5rite6ine(column.Column1ame K $ = $ K row2column3); Console.5rite6ine(); "
Captulos de O E D"
-ediante OleDbDataReader puede recuperar con$untos $er4rquicos de filas o cap5tulos 9tipo D-18P&9:C:AP1&R de !LE ) y tipo adC+apter de A !: . 'uando se devuelve en forma de DataReader una consulta que incluye un cap5tulo" #ste se devuelve como una columna del DataReader y se e&pone como un ob$eto DataReader. Tambi#n se puede utili%ar DataSet de A !.NET para representar con$untos $er4rquicos de filas utili%ando relaciones entre tablas primarias y secundarias. ,ara obtener m4s informacin" consulte =tili%ar ata*ets en A !.NET.

En el e$emplo de cdigo siguiente se utili%a el proveedor -* ata*6ape para generar un cap5tulo con la columna de pedidos reali%ados por cada uno de los clientes de una lista. S'3T 'opiar cdigo

OleD#Connection connection = new OleD#Connection( $*rovider=%SDataS!ape;Data *rovider=S76O6ADB;$ K $Data Source=local!ost;'ntegrated Securit&=SS*';'nitial Catalog=nort!wind$); OleD#Command custC%D = new OleD#Command( $SEA*A {SA6AC, Customer'D9 Compan&1ame 0FO% Customers" $ K $A**A1D ({SA6AC, Customer'D9 Order'D 0FO% Orders" AS CustomerOrders $ K $FA6A,A Customer'D ,O Customer'D)$9 connection); connection.Open(); OleD#DataFeader custFeader = custC%D.ACecuteFeader(); OleD#DataFeader orderFeader; w!ile (custFeader.Fead()) { Console.5rite6ine($Orders +or $ K custFeader.RetString(()); // custFeader.RetString(() = Compan&1ame orderFeader = (OleD#DataFeader)custFeader.RetQalue()); // custFeader.RetQalue()) = Orders c!apter as DataFeader w!ile (orderFeader.Fead()) Console.5rite6ine($<t$ K orderFeader.Ret'nt?)(()); // orderFeader.Ret'nt?)(() = Order'D orderFeader.Close(); " custFeader.Close(); connection.Close();

Cursores /E- C(/SO/ de Oracle


El proveedor de datos de .NET Framework para !racle admite el uso de cursores 1EF '=1*!1 de !racle para devolver los resultados de una consulta. =n 1EF '=1*!1 de !racle se devuelve en forma de ob$eto !racle ata1eader. ,uede recuperar un ob$eto OracleDataReader" que representa un 1EF '=1*!1 de !racle" mediante el m#todo E&ecute1eader. Tambi#n puede especificar un !racle'ommand que devuelva uno o varios cursores 1EF '=1*!1 de !racle como SelectCommand de un !racle ataAdapter utili%ado para rellenar un ata*et.

,ara obtener acceso a un 1EF '=1*!1 devuelto desde un origen de datos de !racle" cree un OracleCommand para la consulta y agregue un par4metro de salida que estable%ca una referencia entre el 1EF '=1*!1 y la coleccin Parameters de OracleCommand. El nombre del par4metro debe coincidir con el nombre del par4metro 1EF '=1*!1 de la consulta. Estable%ca el tipo del par4metro en Oracle1ype Cursor. El m#todo &'ecuteReader del OracleCommand devolver4 un OracleDataReader para el 1EF '=1*!1. *i OracleCommand devuelve varios cursores 1EF '=1*!1" agregue varios par4metros de salida. ,uede tener acceso a los distintos cursores 1EF '=1*!1 llamando al m#todo OracleCommand &'ecuteReader . La llamada a &'ecuteReader devuelve un ob$eto OracleDataReader que 6aga referencia al primer 1EF '=1*!1. A continuacin" puede llamar al m#todo OracleDataReader )e'tResult para obtener acceso a los cursores 1EF '=1*!1 posteriores. Aunque los par4metros de la coleccin OracleCommand Parameters tengan el mismo nombre que los par4metros de salida de 1EF '=1*!1" OracleDataReader obtendr4 acceso a #stos en el mismo orden en el que se agregaron a la coleccin Parameters. ,or e$emplo" considere el siguiente paquete de !racle y" concretamente" el cuerpo del paquete. 'opiar cdigo

CFAA,A OF FA*6ACA *ACUARA C:FS*UR AS ,G*A ,-C:FSOF 'S FA0 C:FSOF; *FOCAD:FA O*A1-,5O-C:FSOFS (A%*C:FSOF O:, ,-C:FSOF9 DA*,C:FSOF O:, ,-C:FSOF); A1D C:FS*UR; CFAA,A OF FA*6ACA *ACUARA BODG C:FS*UR AS *FOCAD:FA O*A1-,5O-C:FSOFS (A%*C:FSOF O:, ,-C:FSOF9 DA*,C:FSOF O:, ,-C:FSOF) 'S BAR'1 O*A1 A%*C:FSOF 0OF SA6AC, ; 0FO% DA%O.A%*6OGAA; O*A1 DA*,C:FSOF 0OF SA6AC, ; 0FO% DA%O.DA*AF,%A1,; A1D O*A1-,5O-C:FSOFS; A1D C:FS*UR;
En el cdigo siguiente se crea un OracleCommand que devuelve los cursores 1EF '=1*!1 del paquete anterior de !racle mediante la adicin de dos par4metros de tipo Oracle1ype Cursor a la coleccin Parameters. S'3T 'opiar cdigo

OracleCommand cursCmd = new OracleCommand($C:FS*UR.O*A1-,5O-C:FSOFS$9 oraConn); cursCmd.*arameters.Add($A%*C:FSOF$9 Oracle,&pe.Cursor).Direction = *arameterDirection.Output; cursCmd.*arameters.Add($DA*,C:FSOF$9 Oracle,&pe.Cursor).Direction = *arameterDirection.Output;

El cdigo siguiente devuelve los resultados del comando anterior utili%ando los m#todos Read y )e'tResult del OracleDataReader. Los par4metros 1EF '=1*!1 se devuelven en orden. S'3T 'opiar cdigo

oraConn.Open(); OracleCommand cursCmd = new OracleCommand($C:FS*UR.O*A1-,5O-C:FSOFS$9 oraConn); cursCmd.Command,&pe = Command,&pe.Stored*rocedure; cursCmd.*arameters.Add($A%*C:FSOF$9 Oracle,&pe.Cursor).Direction = *arameterDirection.Output; cursCmd.*arameters.Add($DA*,C:FSOF$9 Oracle,&pe.Cursor).Direction = *arameterDirection.Output; OracleDataFeader reader = cursCmd.ACecuteFeader(); Console.5rite6ine($<nAmp 'D<t1ame$); w!ile (reader.Fead()) Console.5rite6ine(${="<t{("9 {)"$9 reader.RetOracle1um#er(=)9 reader.RetString(()9 reader.RetString())); reader.1eCtFesult(); Console.5rite6ine($<nDept 'D<t1ame$); w!ile (reader.Fead()) Console.5rite6ine(${="<t{("$9 reader.RetOracle1um#er(=)9 reader.RetString(()); reader.Close(); oraConn.Close();
En el siguiente e$emplo se utili%a el comando anterior para rellenar un DataSet con los resultados del paquete de !racle.

Nota Se recomienda que el usuario controle tambin cualquier conversin del tipo N70&E de #racle a un tipo vlido de .NE( )rame*or+ antes de almacenar el valor en DataRo( para evitar que se produzca una e'cepcin Overflo(E#ception. Puede utilizar el evento FillError para determinar si se 1a producido una e'cepcin Overflo(E#ception. Para obtener ms informacin sobre el evento FillError, vea (rabajar con eventos %ata:dapter. S'3T 'opiar cdigo

DataSet ds = new DataSet(); OracleDataAdapter adapter = new OracleDataAdapter(cursCmd); adapter.,a#le%appings.Add($,a#le$9 $Amplo&ees$); adapter.,a#le%appings.Add($,a#le($9 $Departments$); adapter.0ill(ds);

Obtener !alores " O" a partir de una base de datos


El ob$eto DataReader carga de forma predeterminada los datos que recibe en una fila" siempre que 6ay disponibles suficientes datos para llenarla. *in embargo" los ob$etos binarios grandes 9)L!): se deben tratar de otra forma" ya que pueden llegar a contener grandes cantidades de datos 9del orden de gigabytes: que no pueden almacenarse en una sola fila. El m#todo Command &'ecuteReader se puede sobrecargar para aceptar un argumento 'ommand)e6avior y modificar el comportamiento predeterminado de DataReader. ,uede pasar *equentialAccess al m#todo &'ecuteReader para modificar el comportamiento predeterminado de DataReader de forma que en lugar de cargar los datos por filas" los vaya cargando secuencialmente a medida que los vaya recibiendo. Este sistema es idneo para cargar )L!) y otras estructuras de datos grandes. Tenga en cuenta que este comportamiento puede depender del origen de datos. ,or e$emplo" si se devuelve un )L!) desde -icrosoft Access" el )L!) completo se cargar4 en memoria" en lugar de 6acerlo secuencialmente a medida que se recibe.

Al configurar DataReader para que utilice Se%uentialAccess debe tener en cuenta la secuencia en que va a tener acceso a los datos devueltos. El comportamiento predeterminado de DataReader" consistente en cargar una fila completa de datos en cuanto 6ay datos suficientes" permite tener acceso a ellos en cualquier orden 6asta que se lee la fila siguiente. *in embargo" al utili%ar Se%uentialAccess es necesario tener acceso a los campos que devuelve DataReader en el orden adecuado. ,or e$emplo" si la consulta devuelve tres columnas y la tercera es un )L!)" debe devolver los valores de los campos primero y segundo antes de tener acceso a los datos )L!) del tercer campo. *i trata de tener acceso al tercer campo antes que al primero o el segundo" puede que #stos de$en de estar disponibles. Esto se debe a que Se%uentialAccess cambia la forma en que el DataReader devuelve los datos" 6aciendo que lo 6aga de forma secuencial con lo que los datos de$an de estar disponibles en el momento en que el DataReader lee datos posteriores. 'uando intente obtener acceso a los datos del campo )L!)" utilice los descriptores de acceso con informacin de tipos 6et-ytes o 6etC+ars del DataReader" que llenan una matri% con los datos. En el caso de los datos de caracteres" puede utili%ar tambi#n 6etString> no obstante" si desea conservar los recursos del sistema" es me$or que no cargue un valor )L!) completo en una sola variable de cadena. En lugar de ello" puede especificar un tama7o determinado de b8fer para los datos que se van a devolver" as5 como la ubicacin de comien%o para leer el primer byte o car4cter de los datos devueltos. 6et-ytes y 6etC+ars devuelven un valor de tipo long que representa el n8mero de bytes o caracteres devueltos. *i pasa una matri% con valores null a 6et-ytes o 6etC+ars" el valor de tipo long devuelto contiene el n8mero total de bytes o caracteres del )L!). Tambi#n puede especificar un 5ndice de la matri% como posicin de comien%o para la lectura de datos.

E$emplo
En el siguiente e$emplo se devuelve el identificador y el logotipo del editor desde la base de datos de e$emplo pubs de -icrosoft *+L *erver ?///. El identificador de editor 9 pu#-id: es un campo de caracteres" mientras que el logotipo es una imagen de )L!). 'omo el campo logo es un mapa de bits" el e$emplo devuelve datos binarios mediante 6et-ytes. Tenga en cuenta que la necesidad de tener acceso a los datos de forma secuencial 6ace que en la fila actual de datos se tenga acceso al identificador de editor antes que al logotipo. '3 'opiar cdigo

// Assumes t!at connection is a valid SqlConnection o#Ject. SqlCommand command = new SqlCommand( $SA6AC, pu#-id9 logo 0FO% pu#-in+o$9 connection); // 5rites t!e B6OB to a +ile (;.#mp). 0ileStream stream; // Streams t!e B6OB to t!e 0ileStream o#Ject. Binar&5riter writer; // SiWe o+ t!e B6OB #u++er. int #u++erSiWe = (==; // ,!e B6OB #&te23 #u++er to #e +illed #& RetB&tes. #&te23 outB&te = new #&te2#u++erSiWe3; // ,!e #&tes returned +rom RetB&tes. long retval; // ,!e starting position in t!e B6OB output. long start'ndeC = =; // ,!e pu#lis!er id to use in t!e +ile name. string pu#'D = $$; // Open t!e connection and read data into t!e DataFeader. connection.Open(); SqlDataFeader reader = command.ACecuteFeader(CommandBe!avior.SequentialAccess); w!ile (reader.Fead()) { // Ret t!e pu#lis!er id9 w!ic! must occur #e+ore getting t!e logo. pu#'D = reader.RetString(=); // Create a +ile to !old t!e output. stream = new

0ileStream( $logo$ K pu#'D K $.#mp$9 0ile%ode.OpenOrCreate9 0ileAccess.5rite); writer = new Binar&5riter(stream); // Feset t!e starting #&te +or t!e new B6OB. start'ndeC = =; // Fead #&tes into outB&te23 and retain t!e num#er o+ #&tes returned. retval = reader.RetB&tes((9 start'ndeC9 outB&te9 =9 #u++erSiWe); // Continue w!ile t!ere are #&tes #e&ond t!e siWe o+ t!e #u++er. w!ile (retval == #u++erSiWe) { writer.5rite(outB&te); writer.0lus!(); // Feposition start indeC to end o+ last #u++er and +ill #u++er. start'ndeC K= #u++erSiWe; retval = reader.RetB&tes((9 start'ndeC9 outB&te9 =9 #u++erSiWe); " // 5rite t!e remaining #u++er. writer.5rite(outB&te9 =9 (int)retval O (); writer.0lus!(); // Close t!e output +ile. writer.Close(); stream.Close(); " // Close t!e reader and t!e connection. reader.Close(); connection.Close();
3ea tambi4n /e'erencia
b ata1eader escribe la clase DbDataReader" as5 como todos sus miembros. S%lDataReader escribe la clase S%lDataReader" as5 como todos sus miembros. OleDbDataReader escribe la clase OleDbDataReader" as5 como todos sus miembros. OdbcDataReader escribe la clase OdbcDataReader" as5 como todos sus miembros. OracleDataReader escribe la clase OracleDataReader" as5 como todos sus miembros.

Obtencin de in'ormacin de es+uema de una base de datos


La obtencin de informacin de esquema de una base de datos se efect8a con el proceso de descubrimiento de esquemas. El descubrimiento de esquemas permite que las aplicaciones soliciten a los proveedores administrados que busquen y devuelvan informacin acerca del esquema de base de datos" tambi#n conocido como metadatos" de una base de datos dada. Los diferentes elementos del esquema de base de datos" como tablas" columnas y procedimientos almacenados" se e&ponen a trav#s de colecciones de esquemas. 'ada coleccin de esquemas contiene diversa informacin de esquema relativa al proveedor que se est4 utili%ando. 'ada uno de los proveedores administrados de .NET Framework implementa el m#todo 6etSc+ema en la clase Connection" y la informacin de esquema que devuelve el m#todo 6etSc+ema viene en forma de una ataTable. El m#todo 6etSc+ema es un m#todo sobrecargado que proporciona par4metros opcionales para especificar la coleccin de esquemas que se devolver4 y restringir la cantidad de informacin devuelta.

Adem4s del m#todo Net*c6ema" el proveedor de datos de .NET Framework para !LE e&pone informacin de esquema mediante el m#todo Net!le b*c6emaTable del ob$eto

) tambi#n

!le b'onnection. 6etOleDbSc+ema1able toma como argumentos un !le b*c6emaNuid que identifica la informacin de esquema que se devuelve y una matri% de restricciones en esas columnas devueltas. 6etOleDbSc+ema1able devuelve una Data1able rellena con la informacin de esquema solicitada.

En esta seccin

Traba$o con los m4todos 1etSc#ema


Las clases Connection de cada uno de los proveedores administrados de .NET Framework implementan un m#todo 6etSc+ema que se utili%a para recuperar informacin de esquema de la base de datos que est4 actualmente conectada. La informacin de esquema que devuelve el m#todo 6etSc+ema viene en forma de una ataTable. El m#todo 6etSc+ema es un m#todo sobrecargado que proporciona par4metros opcionales para especificar la coleccin de esquemas que se devolver4 y para limitar la cantidad de informacin devuelta.

Especi'icacin de las colecciones de es+uemas


El primer par4metro opcional del m#todo 6etSc+ema es el nombre de la coleccin que se especifica como una cadena. E&isten dos tipos de colecciones de esquemas( comunes" que son comunes a todos los proveedores" y espec5ficas" que son espec5ficas de cada proveedor. ,uede consultar un proveedor administrado de .NET Framework para determinar la lista de colecciones de esquemas admitidas mediante la llamada al m#todo 6etSc+ema sin argumentos" o con el nombre de coleccin de esquemas G-eta ata'ollectionsG. Esto devolver4 una Data1able con una lista de colecciones de esquemas admitidas" el n8mero de restricciones que admite cada una y el n8mero de partes de identificador que emplean. ,ara obtener m4s informacin acerca de las colecciones de esquemas comunes" consulte escripcin de las colecciones de esquemas comunes> para obtener m4s informacin acerca de las colecciones espec5ficas del proveedor" vea del proveedor. Ejemplo de recuperacin de colecciones de es uemas En los siguientes e$emplos se muestra cmo utili%ar el m#todo Net*c6ema del proveedor de datos de .NET Framework para la clase *ql'onnection de *+L *erver para recuperar informacin de esquema de todas las tablas contenidas en la base de datos de e$emplo Adventure#or$s( S'3T 'opiar cdigo escripcin de las colecciones de esquemas espec5ficas

using S&stem; using S&stem.Data; using S&stem.Data.SqlClient; class *rogram { static void %ain(string23 args) { string connectionString = RetConnectionString(); sing (SqlConnection connection = new SqlConnection(connectionString)) { // Connect to t!e data#ase t!en retrieve t!e sc!ema in+ormation. connection.Open(); Data,a#le ta#le = connection.RetSc!ema($,a#les$); // Displa& t!e contents o+ t!e ta#le. Displa&Data(ta#le); Console.5rite6ine($*ress an& e& to continue.$); Console.FeadUe&(); " " private static string RetConnectionString() { // ,o avoid

storing t!e connection string in &our code9 // &ou can retrieve it +rom a con+iguration +ile. return $Data Source=(local);Data#ase=Adventure5or s;$ K $'ntegrated Securit&=SS*';$; " private static void Displa&Data(S&stem.Data.Data,a#le ta#le) { +oreac! (S&stem.Data.DataFow row in ta#le.Fows) { +oreac! (S&stem.Data.DataColumn col in ta#le.Columns) { Console.5rite6ine(${=" = {("$9 col.Column1ame9 row2col3); " Console.5rite6ine($============================$); " " "
Especi'icacin de los !alores de restriccin
El segundo par4metro opcional del m#todo 6etSc+ema son las restricciones que se utili%an para limitar la cantidad de informacin de esquema devuelta. Este par4metro se pasa al m#todo 6etSc+ema como una matri% de cadenas. La posicin en la matri% determina los valores que puede pasar" y es equivalente al n8mero de restricciones. ,or e$emplo" en la siguiente tabla se describen las restricciones que admite la coleccin de esquemas GTablesG utili%ando el proveedor de datos de .NET Framework para *+L *erver(

No!bre de la restriccin ,atlo!o Propietario (abla (able(ype

2alor predeter!inado de la restriccin (:&$EJ,:(:$#K (:&$EJS,LE0: (:&$EJN:0E (:&$EJ(MPE

N<!ero de restricciones D B A

,ara utili%ar una de las restricciones de la coleccin de esquemas GTablesG" basta con que cree una matri% de cadenas con cuatro elementos y coloque un valor en el elemento que coincida con el n8mero de restricciones. ,or e$emplo" para restringir las tablas devueltas por el m#todo 6etSc+ema a slo las que son propiedad de la funcin GdboG" estable%ca el segundo elemento de la matri% en GdboG antes de pasarlo al m#todo 6etSc+ema.

Nota $as colecciones con restricciones para S.lClient y OracleClient tienen una columna $ara!eterNa!e adicional. $a columna de valor predeterminado de restriccin si!ue a1" para la compatibilidad con versiones anteriores, pero actualmente se omite. Para reducir el ries!o de un ataque de inyeccin de S;$ al especificar valores de restriccin, es necesario utilizar consultas parametrizadas en lu!ar de sustitucin de cadenas. Nota El n9mero de elementos de la matriz debe ser menor o i!ual que el n9mero de restricciones admitidas en la coleccin de esquemas especificada o se iniciar una :r!umentE'ception. Puede 1aber un n9mero de restricciones inferior al m'imo. Se supone que las restricciones que faltan sern nulas 5sin restricciones6. ,uede consultar un proveedor administrado de .NET Framework para determinar la lista de restricciones admitidas mediante la llamada al m#todo 6etSc+ema con el nombre de la coleccin de esquemas con restricciones" que es G1estrictionsG. Esto devolver4 una Data1able con una lista de los nombres de colecciones" los nombres de restricciones" los valores predeterminados de restriccin y los n8meros de restricciones.

Ejemplo de restriccin de colecciones de es uemas En los siguientes e$emplos se muestra cmo utili%ar el m#todo 6etSc+ema del proveedor de datos de .NET Framework para la clase S%lConnection de *+L *erver para recuperar informacin de esquema de todas las tablas contenidas en la base de datos de e$emplo Adventure#or$s" y para restringir la informacin devuelta a slo las tablas propiedad de la funcin GdboG. S'3T 'opiar cdigo

using S&stem; using S&stem.Data; using S&stem.Data.SqlClient; class *rogram { static void %ain() { string connectionString = RetConnectionString(); using (SqlConnection connection = new SqlConnection(connectionString)) { //Connect to t!e data#ase9 and t!en retrieve t!e //sc!ema in+ormation. connection.Open(); string23 restrictions = new string2>3; restrictions2(3 = $d#o$; Data,a#le ta#le = connection.RetSc!ema($,a#les$9 restrictions); // Displa& t!e contents o+ t!e ta#le. Displa&Data(ta#le); Console.5rite6ine($*ress an& e& to continue.$); Console.FeadUe&(); " " private static string RetConnectionString() { // ,o avoid storing t!e connection string in &our code9 // &ou can retrieve it +rom a con+iguration +ile. return $Data Source=(local);Data#ase=Adventure5or s;$ K $'ntegrated Securit&=SS*';$; " private static void Displa&Data(S&stem.Data.Data,a#le ta#le) { +oreac! (S&stem.Data.DataFow row in ta#le.Fows) { +oreac! (S&stem.Data.DataColumn col in ta#le.Columns) { Console.5rite6ine(${=" = {("$9 col.Column1ame9 row2col3); " Console.5rite6ine($============================$); " " "

Anda mungkin juga menyukai