DataSet
Definición de tipos

Fecha: 04/Ago/2004 (04/Ago/2004)
Autor: Ariel N. Menendez

 

.

En el artículo anterior que publiqué, expliqué sobre cómo realizar una definición inflexible de tipos, la cual nos brindaba la posibilidad de realizar Datasets tipados. Estos datasets eran representativos de la estructura de datos que estábamos manejando. Para quien no haya leído el articulo anterior y desea hacerlo, hagan clic aquí.

 

En este artículo voy a tratar el mismo tema pero con una vuelta más de rosca, para poder darle a la tipificación la flexibilidad que el otro mecanismo no tiene y de este modo poder realizar datasets que sean 100% intuitivos de manejar al momento de tener que trabajar con ellos.

 

 

Índice.

 

 

 

Como obtener el esquema de una estructura de datos.

 

Nota: Esta primera parte no difiere prácticamente en nada del artículo anterior así que los que ya leyeron el otro artículo, pueden saltear esta primera parte.

 

En primer lugar, para poder tipificar un dataset, es necesario saber qué estructura de datos será la que el dataset se encargará de albergar. De esta forma sabremos como tipificarlo para que su uso resulte intuitivo.

 

Supongamos que tenemos la siguiente estructura de datos:

 

 

Para poder realizar un dataset con definición de tipos, lo primero que deberíamos hacer es obtener un esquema de esta estructura puesto que lo que queremos hacer es que nuestro dataset trabaje con estas dos entidades y su respectiva restricción.

Para lograr esto, recurriremos a unas pocas líneas de código las cuales nos permitirán obtener el esquema que necesitamos y de este modo comenzar a trabajar con él para definir sus tipos.

El código para obtener el esquema de una estructura ya lo había publicado en el articulo anterior, pero como en el otro ejemplo no he mostrado como cargar dos tablas en un dataset y realizar una relación, voy a volver a mostrarlo en este articulo. Así que presten atención.

 

1.      En primer lugar es necesario crear una conexión:

 

Cuadro de texto: string str = "integrated security=sspi;database=suscriptors;server=localhost";
 
SqlConnection sConn = new SqlConnection(str);
 
 

 

 

 

 

 

 

 

 

 


2.      Una vez que tengo la conexión es necesario crear dos objetos de tipo SqlDataAdapter para poder ejecutar los SelectCommand de cada  uno con la consulta correspondiente a cada una de las entidades que van a ser consultadas.

 

Cuadro de texto: SqlDataAdapter da = new SqlDataAdapter("select * from Category",sConn);
SqlDataAdapter da2 = new SqlDataAdapter("select * from Product",sConn);
 

 

 

 

 

 

 

 

 


3.      Luego de instanciar los DataAdapter es necesario instanciar un Dataset (Sin definición de tipos) del cual podré obtener el esquema que necesito para poder realizar mi dataset con definición de tipos.

 

Cuadro de texto: DataSet ds = new DataSet("CategoryProduct");
sConn.Open();
da.Fill(ds,"Categories");
da2.Fill(ds,"Products");
sConn.Close();
 

 

 

 

 

 

 

 

 

 

 


Nota: En este punto hay un par de cosas a tener en cuenta. Primero es importante que el dataset tenga un nombre definido en forma explícita porque de lo contrario al momento de obtener el esquema van a notar que como uno mismo no le dio un nombre al dataset en el esquema va a figurar con el nombre de “NewDataSet” y en realidad como estamos haciendo que el dataset quede tipado para poder ser mas intuitivo trabajar  con el, esto hace que no sea así, porque no es cómodo trabajar con un dataset que se llama NewDataSet… ¿o si?

En segundo termino el mismo tema que les acabo de comentar para los DataSet ocurre con los DataTable, es por eso que también se los nombra en forma explicita, para que no aparezcan con un nombre auto asignado como por ejemplo TableN donde N es un número correlativo que se le asigna para diferenciar un DataTable de otro dentro de la colección de DataTables.

 

4.      Una vez realizado esto, es tiempo de crear la relación entre nuestros DataTables (Categories y Products), para ello es necesario utilizar la propiedad Relations que se encuentra en el dataset la cual me permite agregar dicha relación a los datatables.

 

 

 

 

Cuadro de texto: ds.Relations.Add("Products",ds.Tables["Categories"].Columns["CategoryID”],ds.Tables["Products"].Columns["CategoryID"]);
 
 
 

 

 

 


De esta simple forma realizamos la relación entre las dos DataTables que luego nos servirá para poder recorrer los DataRow hijos que se encuentren anidados en nuestra consulta como resultado de dicha relación.

 

 

5.      Bueno, ahora que ya tenemos nuestras tablas y relaciones cargadas es momento de obtener el esquema que estamos buscando para poder realizar nuestro dataset con definición de tipos.

 

Cuadro de texto:  
StreamWriter sw = new StreamWriter("CategoryProduct.xsd");
ds.WriteXmlSchema(sw);
sw.Close();
 
 

 

 

 

 

 

 

 

 

 


Como podrán notar el esquema va a ser guardado en un archivo con extensión xsd. XDS es la extensión que define a los esquemas xml como tal, por lo tanto cada vez que vean un archivo con esta extensión sabrán que se trata de un esquema. Una vez obtenido el esquema de la estructura con la que quiero trabajar y luego de haberlo grabado en un archivo, ya nos encontramos en condiciones de comenzar con nuestra bendita definición de tipos.

 

Ah, me olvidaba… para poder trabajar con los DataAdapter, DataSet y la clase StreamWriter no olviden de agregar los siguientes espacios de nombre al proyecto de consola.

 

Cuadro de texto:  
using System.Data;
using System.Data.SqlClient;
using System.IO;
 

 

 

 

 

 

 

 

 

 


Como tipificar el esquema

 

Bueno, como ya he mencionado, de todo lo antes realizado, lo que vamos a obtener es un bonito esquema que representará la estructura de datos con la cual queremos trabajar.

 

Lo bueno de poder obtener la estructura de nuestro esquema con estas pocas líneas de código es que no necesariamente tenemos la obligación de ser grandes conocedores de cómo esta conformado un esquema, aunque el conocimiento nunca esta de más, pero como todos sabemos a veces por una cuestión de tiempos, sí tenemos la posibilidad de que la máquina lo haga por nosotros, bienvenido sea, en fin, como les decía, una vez obtenido el esquema es necesario abrir el archivo donde fue guardado para comenzar a trabajar con él y de ese modo antes de convertir nuestro esquema a una clase, poder tipificarlo y así obtener un dataset con definición de tipos.

El aspecto de nuestro esquema será como éste que se ve a continuación:

 

<?xml version="1.0" encoding="utf-8"?>

<xs:schema id="CategoryProduct" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">

  <xs:element name="CategoryProduct" msdata:IsDataSet="true">

    <xs:complexType>

      <xs:choice maxOccurs="unbounded">

        <xs:element name="Categories">

          <xs:complexType>

            <xs:sequence>

              <xs:element name="CategoryID" type="xs:int" minOccurs="0" />

              <xs:element name="CategoryName" type="xs:string" minOccurs="0" />

            </xs:sequence>

          </xs:complexType>

        </xs:element>

        <xs:element name="Products">

          <xs:complexType>

            <xs:sequence>

              <xs:element name="ProducID" type="xs:int" minOccurs="0" />

              <xs:element name="Description" type="xs:string" minOccurs="0" />

              <xs:element name="CategoryID" type="xs:int" minOccurs="0" />

            </xs:sequence>

          </xs:complexType>

        </xs:element>

      </xs:choice>

    </xs:complexType>

    <xs:unique name="Constraint1">

      <xs:selector xpath=".//Categories" />

      <xs:field xpath="CategoryID" />

    </xs:unique>

    <xs:keyref name="Products" refer="Constraint1">

      <xs:selector xpath=".//Products" />

      <xs:field xpath="CategoryID" />

    </xs:keyref>

  </xs:element>

</xs:schema>

 

¿Lindo no?, así a simple vista parece chino básico (indescriptible), pero si se acercan un poquitito mas al monitor van a poder apreciar que todo comienza a tomar un poco de sentido y que esa cosa que están viendo ya deja de ser tan cosa, puesto que lo que esta representando en definitiva es la estructura de datos de nuestro modelo de datos, o al menos de las entidades y relaciones que cargamos en nuestro dataset.

 

Nota: Para los que leyeron mi artículo anterior sobre la Definición Inflexible de tipos fíjense que de esta forma que obtuve el esquema ya no hace falta cambiar el encoding de utf-16 a utf-8 porque ya viene por default en utf-8 (un paso menos.).

 

Comencemos por parte… dijo Jack. En primer lugar deberíamos conocer unas anotaciones que nos van a permitir cambiar los nombres de los elementos (digo elementos porque no se olviden que un esquema no es mas que un xml que representa una estructura) sin necesidad de cambiar el esquema subyacente. De este modo lo que vamos a lograr es que la representación del esquema siga coincidiendo con los elementos correspondientes al origen de datos y por lo tanto sigan manteniendo su relación. Las anotaciones nos dan la posibilidad de personalizar los nombres de objetos del dataset con información de tipos para utilizar nombre con más significado para nosotros.

 

Por ejemplo e una definición inflexible de tipos este esquema daría como resultado que para la tabla Categories un DataRow denominado CategoriesRow y una DataRowCollection denominada Categories 

 

 

 

<xs:element name="Categories">

    <xs:complexType>

        <xs:sequence>

           <xs:element name="CategoryID" type="xs:int" minOccurs="0" />

           <xs:element name="CategoryName" type="xs:string" minOccurs="0" />

         </xs:sequence>

     </xs:complexType>

 </xs:element>

 

 

En este caso una colección de DataRow llamado Categories puede resultar significativo y por lo tanto bastante intuitivo, pero qué pasa con un único Row el cual es denominado CategoriesRow, esto puede prestarse a confusión.

 

La solución a esto seria anotar el esquema y de esta forma darle una forma mas intuitiva de poder manipularlo, para ello se debe definir la estructura anterior de la siguiente manera.

 

<xs:element name="Categories"  codegen:typedName=”Category” codegen:pluralName=”Categories”>

    <xs:complexType>

        <xs:sequence>

           <xs:element name="CategoryID" type="xs:int" minOccurs="0" />

           <xs:element name="CategoryName" type="xs:string" minOccurs="0" />

         </xs:sequence>

     </xs:complexType>

 </xs:element>

 

Como podrán ver, de esta forma va a ser mucho mas claro poder trabajar con nuestra colección de Categories, puesto que cuando necesitemos trabajar con un único elemento de la colección lo invocaremos con el nombre de Category.

 

En este caso como podrán observar el nombre de un objeto DataRow en este caso será Category y su DataRowCollections Categories.

 

A continuación les listo las anotaciones disponibles con la descripción de cada una de ellas:

 

Anotación

Descripción

typedName

Nombre del objeto

typedPlural

Nombre de la colección de objetos

typedParent

Nombre del objeto cuando se hace referencia al mismo en una relación primaria

typedChildren

Nombre del objeto para devolver objetos de una relación secundaria.

nullValue

Valor, si el valor subyacente es DBNull

 

 

En la siguiente tabla se muestran los valores que se pueden especificar para nullValue:

 

nullValue

Descripción

Valor de reemplazo

Especifica un valor que se va a devolver. El valor devuelto debe coincidir con el tipo del elemento. Por ejemplo, utilice nullValue="0" para devolver 0 en el caso de campos null integer

_throw

Inicia una excepción. Valor predeterminado.

_null

Devuelve una referencia nula o inicia un excepción

_empty

En el caso de cadenas, devuelve String.Empty; de lo contrario, devuelve un objeto creado desde un constructor vacío. Si se encuentra un tipo primitivo, inicia una excepción.

 

 

 

Para poder utilizar anotaciones en un Dataset con información de tipos es necesario agregar al esquema la siguiente referencia xmlns.

 

xmlns:codegen=”urn:schemas-microsoft-com:xml-msprop”

 

Una vez explicado esto, vamos a ver como debería quedar nuestro esquema para que funcione con las respectivas anotaciones para realizar las definiciones de tipo del dataset.

 

 

<?xml version="1.0" encoding="utf-8"?>

<xs:schema id="CategoryProduct" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:codegen=”urn:schemas-microsoft-com:xml-msprop”>

  <xs:element name="CategoryProduct" msdata:IsDataSet="true">

    <xs:complexType>

      <xs:choice maxOccurs="unbounded">

        <xs:element name="Categories" codegen:typedName=”Category” codegen:typedPlural=”Categories”>

          <xs:complexType>

            <xs:sequence>

              <xs:element name="CategoryID" type="xs:int" minOccurs="0" codegen:typedName=”CategoryID”/>

              <xs:element name="CategoryName" type="xs:string" minOccurs="0" codegen:typedName=”CategoryName/>

            </xs:sequence>

          </xs:complexType>

        </xs:element>

        <xs:element name="Products" codegen:typedName=”Product” codegen:typedPlural=”Products”>

          <xs:complexType>

            <xs:sequence>

              <xs:element name="ProducID" type="xs:int" minOccurs="0" codegen:typedName=”ProductID />

              <xs:element name="Description" type="xs:string" minOccurs="0" codegen:typedName=”Descripcion/>

              <xs:element name="CategoryID" type="xs:int" minOccurs="0" codegen:typedName=”CatID/>

            </xs:sequence>

          </xs:complexType>

        </xs:element>

      </xs:choice>

    </xs:complexType>

    <xs:unique name="Constraint1">

      <xs:selector xpath=".//Categories" />

      <xs:field xpath="CategoryID" />

    </xs:unique>

    <xs:keyref name="Products" refer="Constraint1" codegen:typedParent=”Category” codegen:typedChildren=”GetProducts>

      <xs:selector xpath=".//Products" />

      <xs:field xpath="CategoryID" />

    </xs:keyref>

  </xs:element>

</xs:schema>

 

 

Una vez realizado ésto y grabado nuestro esquema con anotaciones, ya estamos en condiciones se seguir con el siguiente paso.

 

 

Como realizar una clase basándose en esquema.

 

Como esto no difiere de lo explicado en el articulo anterior, debería pedirles que se remitan a él para realizar esta tarea, pero para que no se molesten en tener que hacer eso voy a ser mas benevolente y me voy a tomar la molestia en realizar un copy paste para que lo puedan leer directamente de este artículo.

 

Para poder realizar una clase basándome en el esquema anterior, es necesario utilizar una herramienta que ya viene con el framework denominada XSD.exe

 

Nota: para poder acceder a ella desde cualquier path les recomiendo que utilicen la consola que viene seteada en el accedo de .net (Start > Programs > Microsoft Visual Studio .Net 2003 >  Visual Studio Tools > Visual Studio 2003 Command Prompt)

 

Bueno, una vez dicho esto, manos a la obra.

 

El primer paso que se debe realizar es ubicar el archivo .XSD que he realizado en alguna carpeta que sea fácilmente identificable.

 

Una vez hecho esto, tengo que iniciar la consola (Command Prompt) para poder ejecutar el comando XSD.exe.

 

Dentro de la carpeta donde se encuentra situado el esquema, invoco el comando de la siguiente manera:

 

 

 

 

Una vez invocado, ya poseemos nuestra clase.

 

Dentro de los parámetros que se le pasan a la herramienta, se encuentran:

 

/d à define que la clase generada será de tipo Dataset

/l  à es para determinar bajo qué código será generada la clase. En nuestro clase le definimos /l:CS para que el comando la genere en C#.

 

/n à sirve para que podamos definirle un espacio de nombre a nuestra clase. En este ejemplo, le puse MyDataSet.DSTyped.

 

 

Si miramos en la carpeta donde se encontraba el esquema ahora veremos que, además del esquema, se encuentra la clase que se generó a partir de su estructura.

 

Una vez obtenida la clase, lo que podemos hacer es compilarla para obtener un assembly el cual referenciar en el proyecto donde necesitemos hacer uso de la entidad en cuestión.

 

 

Como compilar.

 

Para compilar la clase los pasos son muy simples. Al igual que el comando para crearla, también tenemos el comando para compilarla (esto ya lo tenemos bien claro, supongo). El comando es csc.exe, el cual nos permite compilar clases programadas en C#.

 

 

 

 

Dentro de los parámetros que podemos pasar a la compilación se encuentran:

 

/t:library à El cual me permite construir una librería.

/r à Permite definir de qué otros assembly depende éste que estoy generando.

Una vez realizado estos pasos, ya estamos en condiciones de utilizar nuestra librería en algún proyecto que lo requiera. Por si no lo notaron, en la misma carpeta donde está el esquema y la clase, ahora poseemos un assembly recién horneado y listo para usar.

 

 

Ejemplo de cómo usar el dataset tipificado

 

Ya llegamos al momento de la verdad, ahora vamos a ver como hacer funcionar nuestro Dataset.

 

En primer lugar vamos a crear un proyecto y realizar una referencia al assembly que acabamos de crear. Una vez hecho ésto es necesario:

 

 

a.      Crear una conexión a la base de datos.

b.      Crear un objeto DataAdapter para la entidad Category.

c.      Crear otro objeto DataAdapter para la entidad Product.

d.      Y en este punto en lugar de crear un DataSet no tipificado creamos un objeto que corresponda al tipo de nuestro DataSet.

 

Cuadro de texto:  
CategoryProduct categoryProduct = new CategoryProduct();
 

 

 

 

 

 

 

 


Una vez hecho esto podemos utilizar el método Fill de los DataAdapters para llenar nuestro DataSet con los datos provenientes del repositorio de datos.

 

 

Cuadro de texto:  
sConn.Open();
da.Fill(categoryProduct,"Categories");
da2.Fill(categoryProduct,"Products");
sConn.Close();

 

 

 

 

 

 

 

 

 


Una vez que tengo cargado el DataSet con los datos lo puedo recorrer de esta forma. Por ejemplo:

 

 

 

Cuadro de texto:  
foreach(CategoryProduct.Category category in categoryProduct.Categories)
{
Console.WriteLine(category.CategoryID + " --> " + category.CategoryName);
 
foreach(CategoryProduct.Product product in category.GetProducts())
{
Console.WriteLine("------->" + product.Descripcion);
}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Pueden observar en la instrucción foreach lo intuitivo que resulta iterar la colección de Category, y en la instrucción foreach anidada en que forma obtengo la colección de productos. Creo que esta es la mejor práctica recomendada para trabajar los Dataset, puesto que en forma muy sencilla y clara se puede crear una clase que represente una estructura de datos la cual podré usar para instanciar objetos del tipo. Además observen que, en forma implícita queda armada la relación entre la entidad Category y Product de nuestro repositorio de datos.

 

Bueno, espero que apliquen esta forma efectiva de trabajar con los dataset. Saludos.

 

 

 

 

 

                                   


 

Ariel Norberto Menéndez
menendea@hotmail.com
www.cardinal-cg.com

ir al índice

Fichero con el código de ejemplo: menendea_DataSetDefinicionDeTipos.zip- 17KB