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


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

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

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

Este conjunto de clases específicas heredan de unas clases más genéricas. Así, SqlConnection heredará de DbConnection, que no es más que una clase genérica con la funcionalidad de conectarse a una base de datos genérica. En la introducción a Abstract Factory, SqlConnection equivaldría a un «Motor Gasolina», mientras que DbConnection sería un «Motor». Nuestra
aplicación (el vehículo) necesita precisamente eso: un motor. Y se lo daremos a través de una factoría abstracta proporcionada por ADO.NET: DbProviderFactory.

Nuestro objeto DbProviderFactory será creado para una base de datos en concreto (es decir, crearemos una factoría concreta para SQL Server, Oracle, MySQL…). No obstante, el tipo instanciado dependerá ni más ni menos que de un parámetro de configuración en vez de estar codificado directamente. De forma dinámica, y sin modificar ni una sola línea de código, seremos capaces de conectarnos a diferentes bases de datos. Será ADO.NET el encargado de averiguar con qué base de datos estamos tratando y, a partir de ahí, instanciar la factoría adecuada para crear los componentes que necesitamos (en el artículo anterior, la factoría de motores gasolina, factoría de motores diesel…).

Entendiendo el funcionamiento de ADO.NET

Antes de comenzar, debemos comprender la filosofía del siguiente ejemplo. Debemos tener claro lo siguiente: nuestro objetivo (actual) es realizar una consulta a una fuente de datos. Esa consulta será almacenada en una estructura de datos, por ejemplo, en un DataSet.

estructura

Para rellenar el DataSet, será necesario un objeto que adapte los datos desde la fuente hasta la aplicación. Éste objeto será un DbDataAdapter.

El DbDataAdapter, sin embargo, necesita saber qué ejecutar. Para ello hará uso de una orden de base de datos o DbCommand.

El DbCommand necesita también cierta información: una sentencia SQL o nombre de procedimiento almacenado para pasarle a la fuente de datos… y una conexión por la cual establecer el intercambio de datos. Esta conexión será un objeto de tipo DbConnection.

Por último, el DbConnection únicamente hará uso de una cadena de conexión para establecer el enlace entre aplicación y fuente de datos.

Efectuando una consulta

Para comenzar, deberemos añadir la cadena de conexión a nuestro web.config o app.config.

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

<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=SQLSRV2005;Initial Catalog=TestDB;Persist Security Info=True;User ID=dani;Password=prueba" providerName="System.Data.SqlClient" />
</connectionStrings>

</configuration>

A continuación declararemos e instanciaremos todos los objetos que hemos indicado previamente.

// Declaración de referencias
DbConnection conexion;          // Clase encargada de realizar una conexión a una fuente de datos
DbCommand cmd;                  // Clase encargada de ejecutar sentencias SQL y procedimientos almacenados
DbDataAdapter da;               // Clase encargada de adaptar los datos obtenidos a una estructura local
DataSet ds = new DataSet();     // Clase encargada de almacenar los datos

En este momento, necesitaremos acceder a nuestro web.config o app.config. Para ello, añadiremos una referencia al ensamblado System.Configuration (Click derecho sobre el proyecto > Agregar
referencia > .NET > System.Configuration) y añadiremos manualmente la inclusión del espacio de nombres en la cabecera de nuestro fichero fuente, así como otros espacios de nombres necesarios.

using System;
using System.Configuration;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;

A continuación necesitaremos acceder a la configuración. Para ello obtendremos una referencia a la misma pasando como parámetro el nombre de la cadena de conexión. A partir de ella, ADO.NET obtendrá un proveedor, el cual pasaremos como parámetro al método GetFactory para instanciar una factoría del tipo concreto al que hace referencia nuestra cadena de conexión.

// Obtenemos la configuración a través del nombre de la cadena de conexión
ConnectionStringSettings settings = ConfigurationManager.ConnectionStrings["DefaultConnection"];

// Utilizamos la configuración para instanciar una factoría abstracta de la cual instanciar objetos.
DbProviderFactory factoriaProveedor = DbProviderFactories.GetFactory(settings.ProviderName);

Una vez que tenemos la factoría, es hora de instanciar, a través de ella, los objetos que necesitamos: una conexión,una orden y un adaptador de datos.

// Instanciamos una conexión, una orden y un adaptador
conexion = factoriaProveedor.CreateConnection();
cmd = factoriaProveedor.CreateCommand();
da = factoriaProveedor.CreateDataAdapter();

Finalmente, configuramos los objetos de forma que las piezas encajen: proporcionaremos una cadena de conexión a la conexión, una conexión y una sentencia SQL a la orden, y una orden al adaptador.

// Configuramos la orden SQL indicando la sentencia a ejecutar y la conexión con la que trabajar.
cmd.CommandText = "SELECT * FROM Usuario";
cmd.Connection = conexion;

// Configuramos el adaptador indicándole la orden a ejecutar.
da.SelectCommand = cmd;

Por último, rellenamos el DataSet con el adaptador, con lo que obtendríamos los resultados buscados.

// Rellenamos el DataSet mediante el adaptador.
da.Fill(ds);

Cambiando de proveedor

Si ahora quisiéramos utilizar una base de datos MySQL en lugar de SQL Server, lo único que deberíamos hacer sería modificar la cadena de conexión, de la siguiente manera:

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

<connectionStrings>
<add name="DefaultConnection" connectionString="Dsn=127.0.0.1;database=prueba;option=0;port=0;uid=root" providerName="System.Data.Odbc" />
</connectionStrings>

</configuration>

Mediante esta operación, la aplicación seguiría sin preocuparse de la base de datos subyacente, pero manteniendo operativa la portabilidad.

5 comentarios

  1. Todo lo que hiciste se pudo haber realizado con Dependency Injection, ¿Podrías indicarme si es correcto? o ¿si se aplica con Abstract Factory, Cual es la diferencia?

    SAludos

Deja una respuesta

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s