Está en la página 1de 33

Este es un tema polémico del que se habla mucho y nada, digo que se habla mucho

porque al buscar algo de información en Internet, uno se da cuenta, que esta plagado de
sitios donde preguntan como aplicar programación en 3 capas, o N-Capas, pero en muy
pocos lugares se responde con algo cierto y concreto, la mayoría hacen referencia a
libros gordos que tardarías en leer semanas (no estoy en contra de la lectura, es un
proceso largo nada más y casi todos buscamos aprenderlo un poco más rápido). Este
artículo también será bastante largo y me aventuro a decir que me tomará varias noches
escribirlo completamente, pero no será nada comparado con un libro con un lomo de
15 centímetros

La primer gran confusión que noto, es que la mayoría no sabe diferenciar entre los
conceptos

1. Arquitectura de 3 capas: se basa más bien en como será construido el entorno, una
manera de decirlo en romper el clásico concepto Cliente-Servidor para introducir
conceptos como Back End (Base de Datos), Middleware (Servidor de
Aplicaciones), Front End (Interfaz de Usuario). Este es un concepto grande que no
veremos ahora, pero lo remarco para hacer entender que no tiene nada que ver con la
programación en capas. Se acerca más a un concepto físico.

2. Programación en 3 (n) capas: este es el tema en cuestión y estira más hacia un


concepto lógico. En cómo partimos, agrupamos, clasificamos, optimizamos nuestro
código. El mismo introduce conceptos como Capa de Acceso a Datos (Esta nos
permite conectarnos a la fuente de datos y operar contra ella), Capa de Negocios (es la
que se encarga de procesar todo, validaciones, etc. la misma suele distribuirse en la
aplicación en sí y en la BBDD), y Capa de Presentación (es más bien lo que el usuario
percibe, su interfaz gráfica por lo gral).

Creo que con esos conceptos introductorios ya estamos preparados para comprender
mejor ciertos aspectos de este paradigma. Para resaltar por último, gracias a la
separación en capas quiere decir que podemos cambiar de proveedor de base de datos, y
no necesitaremos reescribir toda la aplicación de vuelta, sino solamente esa pequeña
capa y reutilizaríamos la interfaz y las reglas de negocios, o también podemos mantener
las reglas de negocios y el motor de base de datos, y fácilmente cambiarnos de una
interfaz WinForm a WebForm, siempre la más dura de cambiar en la de negocios ya
que afecta en un nivel mínimo a las otras 2 capas.

Creo que ya es suficiente teoría de momento y podemos comenzar con la acción, el


código que voy a ir escribiendo lo haré en Visual Studio 2010 por que es la que tengo
instalada ahora mismo en la maquina, pero funciona desde la versión 2005 con el
framework 2.0 en adelante, ya que ADO.Net no ha sufrido grandes cambios desde esa
versión, así que ustedes lo pueden ir creando en la versión del IDE o framework que
más gusten.

Primeramente vamos a crear una solución con un proyecto de Biblioteca de Clases, el


mismo tendrá 3 clases principales para representar la capa de Acceso a Datos, la
primera llamaremos GDatos.cs, la misma le asignaremos el namespace AccesoDatos, y
una clase abstracta con el mismo nombre. Haremos uso de estos 2 namespace:

1 using System;
2 using System.Data;

Lo siguiente que haremos será estructurar en código con regiones para una mayor
comodidad en la lectura del mismo, la primer región es la de Declaración de Variables
en la misma creamos las variables o atributos para la conexion a la BBDD, más un
objeto de interfaz de conexión para que sea implementada de manera específica por la
clase hija, si se han dado cuenta estamos usando ya conceptos de OOP avanzados, y lo
seguiremos usando fuertemente en el transcurso del artículo. Esta es una clase que
obligatoriamente debe ser hereda por otra. El nivel de acceso por eso están definidas
como protected para que sean modificadas por si misma o por sus clases derivadas.

1 #region "Declaración de Variables"


2
3 protected string MServidor = "";
4 protected string MBase = "";
5 protected string MUsuario = "";
6 protected string MPassword = "";
7 protected string MCadenaConexion = "";
8 protected IDbConnection MConexion;
9
10 #endregion

Lo siguiente por hacer es muy sencillo, crear los setters y getters de


nuestros atributos anteriormente definidos:

1 #region "Setters y Getters"


2
3 // Nombre del equipo servidor de datos.
4 public string Servidor
5 {
6 get { return MServidor; }
7 set { MServidor = value; }
8 } // end Servidor
9
10 // Nombre de la base de datos a utilizar.
11 public string Base
12 {
13 get { return MBase; }
14 set { MBase = value; }
15 } // end Base
16
17 // Nombre del Usuario de la BD.
18 public string Usuario
19 {
20 get { return MUsuario; }
21 set { MUsuario = value; }
22 } // end Usuario
23
24 // Password del Usuario de la BD.
25 public string Password
26 {
27 get { return MPassword; }
28 set { MPassword = value; }
29 } // end Password
30
31 // Cadena de conexión completa a la base.
32 public abstract string CadenaConexion
33 { get; set; }
#endregion
34
35
#region "Privadas"
36
37
// Crea u obtiene un objeto para conectarse a la base de datos.
38
protected IDbConnection Conexion
39
{
40
get
41
{
42
// si aun no tiene asignada la cadena de conexion lo
43
hace
44
if (MConexion == null)
45
MConexion = CrearConexion(CadenaConexion);
46
47
// si no esta abierta aun la conexion, lo abre
48
if (MConexion.State != ConnectionState.Open)
49
MConexion.Open();
50
51
// retorna la conexion en modo interfaz, para que se
52
adapte a cualquier implementacion de los distintos fabricantes de
53
motores de bases de datos
54
return MConexion;
55
} // end get
56
} // end Conexion
57
#endregion

Creamos ahora los métodos para hacer lecturas a la fuente de datos, lo hacemos ya en
esta clase porque son metodos generales que pueden implementar tal cual las clases
hijas. En el caso de los DataReader que son muy especificos del driver utilizados,
vamos a utilizar el objeto IDataReader que es una interfaz de implementación general.

1 #region "Lecturas"
2
3 // Obtiene un DataSet a partir de un Procedimiento Almacenado.
4 public DataSet TraerDataSet(string procedimientoAlmacenado)
5 {
6 var mDataSet = new DataSet();
7 CrearDataAdapter(procedimientoAlmacenado).Fill(mDataSet);
8 return mDataSet;
9 } // end TraerDataset
10
11 //Obtiene un DataSet a partir de un Procedimiento Almacenado y sus
12 parámetros.
13 public DataSet TraerDataSet(string procedimientoAlmacenado, params
14 Object[] args)
15 {
16 var mDataSet = new DataSet();
17 CrearDataAdapter(procedimientoAlmacenado,
18 args).Fill(mDataSet);
19 return mDataSet;
20 } // end TraerDataset
21
22 // Obtiene un DataSet a partir de un Query Sql.
23 public DataSet TraerDataSetSql(string comandoSql)
24 {
25 var mDataSet = new DataSet();
26 CrearDataAdapterSql(comandoSql).Fill(mDataSet);
27 return mDataSet;
28 } // end TraerDataSetSql
29
30 // Obtiene un DataTable a partir de un Procedimiento Almacenado.
31 public DataTable TraerDataTable(string procedimientoAlmacenado)
32 { return TraerDataSet(procedimientoAlmacenado).Tables[0].Copy(); }
33 // end TraerDataTable
34
35 //Obtiene un DataSet a partir de un Procedimiento Almacenado y sus
36 parámetros.
37 public DataTable TraerDataTable(string procedimientoAlmacenado,
38 params Object[] args)
39 { return TraerDataSet(procedimientoAlmacenado,
40 args).Tables[0].Copy(); } // end TraerDataTable
41
42 //Obtiene un DataTable a partir de un Query SQL
43 public DataTable TraerDataTableSql(string comandoSql)
44 { return TraerDataSetSql(comandoSql).Tables[0].Copy(); } // end
45 TraerDataTableSql
46
47 // Obtiene un DataReader a partir de un Procedimiento Almacenado.
48 public IDataReader TraerDataReader(string procedimientoAlmacenado)
49 {
50 var com = Comando(procedimientoAlmacenado);
51 return com.ExecuteReader();
52 } // end TraerDataReader
53
54 // Obtiene un DataReader a partir de un Procedimiento Almacenado y
55 sus parámetros.
56 public IDataReader TraerDataReader(string procedimientoAlmacenado,
57 params object[] args)
58 {
59 var com = Comando(procedimientoAlmacenado);
60 CargarParametros(com, args);
61 return com.ExecuteReader();
62 } // end TraerDataReader
63
64 // Obtiene un DataReader a partir de un Procedimiento Almacenado.
65 public IDataReader TraerDataReaderSql(string comandoSql)
66 {
67 var com = ComandoSql(comandoSql);
68 return com.ExecuteReader();
69 } // end TraerDataReaderSql
70
71 // Obtiene un Valor Escalar a partir de un Procedimiento
72 Almacenado. Solo funciona con SP's que tengan
73 // definida variables de tipo output, para funciones escalares mas
74 abajo se declara un metodo
75 public object TraerValorOutput(string procedimientoAlmacenado)
76 {
77 // asignar el string sql al command
78 var com = Comando(procedimientoAlmacenado);
79 // ejecutar el command
80 com.ExecuteNonQuery();
81 // declarar variable de retorno
82 Object resp = null;
83
84 // recorrer los parametros del SP
85 foreach (IDbDataParameter par in com.Parameters)
86 // si tiene parametros de tipo IO/Output retornar
87 ese valor
88 if (par.Direction == ParameterDirection.InputOutput
89 || par.Direction == ParameterDirection.Output)
90 resp = par.Value;
91 return resp;
92 } // end TraerValor
93
94 // Obtiene un Valor a partir de un Procedimiento Almacenado, y sus
95 parámetros.
96 public object TraerValorOutput(string procedimientoAlmacenado,
97 params Object[] args)
98 {
99 // asignar el string sql al command
100 var com = Comando(procedimientoAlmacenado);
101 // cargar los parametros del SP
102 CargarParametros(com, args);
103 // ejecutar el command
104 com.ExecuteNonQuery();
105 // declarar variable de retorno
106 Object resp = null;
107
108 // recorrer los parametros del SP
109 foreach (IDbDataParameter par in com.Parameters)
110 // si tiene parametros de tipo IO/Output retornar
111 ese valor
112 if (par.Direction == ParameterDirection.InputOutput
113 || par.Direction == ParameterDirection.Output)
114 resp = par.Value;
115 return resp;
116 } // end TraerValor
117
118 // Obtiene un Valor Escalar a partir de un Procedimiento
119 Almacenado.
120 public object TraerValorOutputSql(string comadoSql)
121 {
122 // asignar el string sql al command
123 var com = ComandoSql(comadoSql);
124 // ejecutar el command
125 com.ExecuteNonQuery();
126 // declarar variable de retorno
127 Object resp = null;
128
129 // recorrer los parametros del Query (uso tipico envio de
130 varias sentencias sql en el mismo command)
131 foreach (IDbDataParameter par in com.Parameters)
132 // si tiene parametros de tipo IO/Output retornar
133 ese valor
134 if (par.Direction == ParameterDirection.InputOutput
135 || par.Direction == ParameterDirection.Output)
136 resp = par.Value;
137 return resp;
138 } // end TraerValor
139
140 // Obtiene un Valor de una funcion Escalar a partir de un
Procedimiento Almacenado.
public object TraerValorEscalar(string procedimientoAlmacenado)
{
var com = Comando(procedimientoAlmacenado);
return com.ExecuteScalar();
} // end TraerValorEscalar

/// Obtiene un Valor de una funcion Escalar a partir de un


Procedimiento Almacenado, con Params de Entrada
public Object TraerValorEscalar(string procedimientoAlmacenado,
params object[] args)
{
var com = Comando(procedimientoAlmacenado);
CargarParametros(com, args);
return com.ExecuteScalar();
} // end TraerValorEscalar

// Obtiene un Valor de una funcion Escalar a partir de un Query SQL


public object TraerValorEscalarSql(string comandoSql)
{
var com = ComandoSql(comandoSql);
return com.ExecuteScalar();
} // end TraerValorEscalarSql

#endregion

El siguiente bloque es para ejecutar procesos que no devuelven valores, al inicio


tendremos varios métodos abstractos, para que las clases derivadas estén obligadas a
implementarlas a su manera, en un modo especifico, ya que los objetos connection,
command, dataadapter, son muy específicos y deben ser implementados por cada una.

1 #region "Acciones"
2
3 protected abstract IDbConnection CrearConexion(string cadena);
4 protected abstract IDbCommand Comando(string
5 procedimientoAlmacenado);
6 protected abstract IDbCommand ComandoSql(string comandoSql);
7 protected abstract IDataAdapter CrearDataAdapter(string
8 procedimientoAlmacenado, params Object[] args);
9 protected abstract IDataAdapter CrearDataAdapterSql(string
10 comandoSql);
11 protected abstract void CargarParametros(IDbCommand comando,
12 Object[] args);
13
14 // metodo sobrecargado para autenticarse contra el motor de BBDD
15 public bool Autenticar()
16 {
17 if (Conexion.State != ConnectionState.Open)
18 Conexion.Open();
19 return true;
20 }// end Autenticar
21
22 // metodo sobrecargado para autenticarse contra el motor de BBDD
23 public bool Autenticar(string vUsuario, string vPassword)
24 {
25 MUsuario = vUsuario;
26 MPassword = vPassword;
27 MConexion = CrearConexion(CadenaConexion);
28
29 MConexion.Open();
30 return true;
31 }// end Autenticar
32
33
34 // cerrar conexion
35 public void CerrarConexion()
36 {
37 if (Conexion.State != ConnectionState.Closed)
38 MConexion.Close();
}

// end CerrarConexion

39
40 // Ejecuta un Procedimiento Almacenado en la base.
41 public int Ejecutar(string procedimientoAlmacenado)
42 { return Comando(procedimientoAlmacenado).ExecuteNonQuery(); } //
43 end Ejecutar
44
45 // Ejecuta un query sql
46 public int EjecutarSql(string comandoSql)
47 { return ComandoSql(comandoSql).ExecuteNonQuery(); } // end Ejecutar
48
49 //Ejecuta un Procedimiento Almacenado en la base, utilizando los
50 parámetros.
51 public int Ejecutar(string procedimientoAlmacenado, params Object[]
52 args)
53 {
54 var com = Comando(procedimientoAlmacenado);
55 CargarParametros(com, args);
56 var resp = com.ExecuteNonQuery();
57 for (var i = 0; i < com.Parameters.Count; i++)
58 {
59 var par = (IDbDataParameter)com.Parameters[i];
60 if (par.Direction == ParameterDirection.InputOutput
61 || par.Direction == ParameterDirection.Output)
62 args.SetValue(par.Value, i - 1);
63 }// end for
return resp;
} // end Ejecutar

#endregion

Ahora bien, no podemos olvidarnos de la sección transaccional, no se utiliza


normalmente en todos lados desde la aplicación, pero en procesos dependientes es
necesario, así que si necesitamos usarlo, podemos crearlo de este modo:

1 #region "Transacciones"
2
3 protected IDbTransaction MTransaccion;
4 protected bool EnTransaccion;
5
6 //Comienza una Transacción en la base en uso.
7 public void IniciarTransaccion()
8 {
9 try
10 {
11 MTransaccion = Conexion.BeginTransaction();
12 EnTransaccion = true;
13 }// end try
14 finally
15 { EnTransaccion = false; }
16 }// end IniciarTransaccion
17
18
19 //Confirma la transacción activa.
20 public void TerminarTransaccion()
21 {
22 try
23 { MTransaccion.Commit(); }
24 finally
25 {
26 MTransaccion = null;
27 EnTransaccion = false;
28 }// end finally
29 }// end TerminarTransaccion
30
31
32 //Cancela la transacción activa.
33 public void AbortarTransaccion()
34 {
35 try
36 { MTransaccion.Rollback(); }
37 finally
38 {
39 MTransaccion = null;
40 EnTransaccion = false;
41 }// end finally
42 }// end AbortarTransaccion
43
44 #endregion

El código completo lo pueden ver aqui:

1 using System;
2 using System.Data;
3
4 namespace AccesoDatos
5 {
6 public abstract class GDatos
7 {
8 #region "Declaración de Variables"
9
10 protected string MServidor = "";
11 protected string MBase = "";
12 protected string MUsuario = "";
13 protected string MPassword = "";
14 protected string MCadenaConexion = "";
15 protected IDbConnection MConexion;
16
17 #endregion
18
19 #region "Setters y Getters"
20
21 // Nombre del equipo servidor de datos.
22 public string Servidor
23 {
24 get { return MServidor; }
25 set { MServidor = value; }
26 } // end Servidor
27
28 // Nombre de la base de datos a utilizar.
29 public string Base
30 {
31 get { return MBase; }
32 set { MBase = value; }
33 } // end Base
34
35 // Nombre del Usuario de la BD.
36 public string Usuario
37 {
38 get { return MUsuario; }
39 set { MUsuario = value; }
40 } // end Usuario
41
42 // Password del Usuario de la BD.
43 public string Password
44 {
45 get { return MPassword; }
46 set { MPassword = value; }
47 } // end Password
48
49 // Cadena de conexión completa a la base.
50 public abstract string CadenaConexion
51 { get; set; }
52
53 #endregion
54
55 #region "Privadas"
56
57 // Crea u obtiene un objeto para conectarse a la base de
58 datos.
59 protected IDbConnection Conexion
60 {
61 get
62 {
63 // si aun no tiene asignada la cadena de conexion
64 lo hace
65 if (MConexion == null)
66 MConexion = CrearConexion(CadenaConexion);
67
68 // si no esta abierta aun la conexion, lo abre
69 if (MConexion.State != ConnectionState.Open)
70 MConexion.Open();
71
72 // retorna la conexion en modo interfaz, para que
73 se adapte a cualquier implementacion de los distintos fabricantes
74 de motores de bases de datos
75 return MConexion;
76 } // end get
77 } // end Conexion
78
79 #endregion
80
81 #region "Lecturas"
82
83 // Obtiene un DataSet a partir de un Procedimiento
84 Almacenado.
85 public DataSet TraerDataSet(string procedimientoAlmacenado)
86 {
87 var mDataSet = new DataSet();
88 CrearDataAdapter(procedimientoAlmacenado).Fill(mDataSet
89 );
90 return mDataSet;
91 } // end TraerDataset
92
93
94 //Obtiene un DataSet a partir de un Procedimiento
95 Almacenado y sus parámetros.
96 public DataSet TraerDataSet(string procedimientoAlmacenado,
97 params Object[] args)
98 {
99 var mDataSet = new DataSet();
100 CrearDataAdapter(procedimientoAlmacenado,
101 args).Fill(mDataSet);
102 return mDataSet;
103 } // end TraerDataset
104
105 // Obtiene un DataSet a partir de un Query Sql.
106 public DataSet TraerDataSetSql(string comandoSql)
107 {
108 var mDataSet = new DataSet();
109 CrearDataAdapterSql(comandoSql).Fill(mDataSet);
110 return mDataSet;
111 } // end TraerDataSetSql
112
113 // Obtiene un DataTable a partir de un Procedimiento
114 Almacenado.
115 public DataTable TraerDataTable(string
116 procedimientoAlmacenado)
117 { return
118 TraerDataSet(procedimientoAlmacenado).Tables[0].Copy(); } // end
119 TraerDataTable
120
121
122 //Obtiene un DataSet a partir de un Procedimiento
123 Almacenado y sus parámetros.
124 public DataTable TraerDataTable(string
125 procedimientoAlmacenado, params Object[] args)
126 { return TraerDataSet(procedimientoAlmacenado,
127 args).Tables[0].Copy(); } // end TraerDataTable
128
129 //Obtiene un DataTable a partir de un Query SQL
130 public DataTable TraerDataTableSql(string comandoSql)
131 { return TraerDataSetSql(comandoSql).Tables[0].Copy(); } //
132 end TraerDataTableSql
133
134 // Obtiene un DataReader a partir de un Procedimiento
135 Almacenado.
136 public IDataReader TraerDataReader(string
137 procedimientoAlmacenado)
138 {
139 var com = Comando(procedimientoAlmacenado);
140 return com.ExecuteReader();
141 } // end TraerDataReader
142
143
144 // Obtiene un DataReader a partir de un Procedimiento
145 Almacenado y sus parámetros.
146 public IDataReader TraerDataReader(string
147 procedimientoAlmacenado, params object[] args)
148 {
149 var com = Comando(procedimientoAlmacenado);
150 CargarParametros(com, args);
151 return com.ExecuteReader();
152 } // end TraerDataReader
153
154 // Obtiene un DataReader a partir de un Procedimiento
155 Almacenado.
156 public IDataReader TraerDataReaderSql(string comandoSql)
157 {
158 var com = ComandoSql(comandoSql);
159 return com.ExecuteReader();
160 } // end TraerDataReaderSql
161
162 // Obtiene un Valor Escalar a partir de un Procedimiento
163 Almacenado. Solo funciona con SP's que tengan
164 // definida variables de tipo output, para funciones
165 escalares mas abajo se declara un metodo
166 public object TraerValorOutput(string
167 procedimientoAlmacenado)
168 {
169 // asignar el string sql al command
170 var com = Comando(procedimientoAlmacenado);
171 // ejecutar el command
172 com.ExecuteNonQuery();
173 // declarar variable de retorno
174 Object resp = null;
175
176 // recorrer los parametros del SP
177 foreach (IDbDataParameter par in com.Parameters)
178 // si tiene parametros de tipo IO/Output retornar
179 ese valor
180 if (par.Direction == ParameterDirection.InputOutput
181 || par.Direction == ParameterDirection.Output)
182 resp = par.Value;
183 return resp;
184 } // end TraerValor
185
186
187 // Obtiene un Valor a partir de un Procedimiento
188 Almacenado, y sus parámetros.
189 public object TraerValorOutput(string
190 procedimientoAlmacenado, params Object[] args)
191 {
192 // asignar el string sql al command
193 var com = Comando(procedimientoAlmacenado);
194 // cargar los parametros del SP
195 CargarParametros(com, args);
196 // ejecutar el command
197 com.ExecuteNonQuery();
198 // declarar variable de retorno
199 Object resp = null;
200
201 // recorrer los parametros del SP
202 foreach (IDbDataParameter par in com.Parameters)
203 // si tiene parametros de tipo IO/Output retornar
204 ese valor
205 if (par.Direction == ParameterDirection.InputOutput
206 || par.Direction == ParameterDirection.Output)
207 resp = par.Value;
208 return resp;
209 } // end TraerValor
210
211 // Obtiene un Valor Escalar a partir de un Procedimiento
212 Almacenado.
213 public object TraerValorOutputSql(string comadoSql)
214 {
215 // asignar el string sql al command
216 var com = ComandoSql(comadoSql);
217 // ejecutar el command
218 com.ExecuteNonQuery();
219 // declarar variable de retorno
220 Object resp = null;
221
222 // recorrer los parametros del Query (uso tipico envio
223 de varias sentencias sql en el mismo command)
224 foreach (IDbDataParameter par in com.Parameters)
225 // si tiene parametros de tipo IO/Output retornar
226 ese valor
227 if (par.Direction == ParameterDirection.InputOutput
228 || par.Direction == ParameterDirection.Output)
229 resp = par.Value;
230 return resp;
231 } // end TraerValor
232
233
234 // Obtiene un Valor de una funcion Escalar a partir de un
235 Procedimiento Almacenado.
236 public object TraerValorEscalar(string
237 procedimientoAlmacenado)
238 {
239 var com = Comando(procedimientoAlmacenado);
240 return com.ExecuteScalar();
241 } // end TraerValorEscalar
242
243 /// Obtiene un Valor de una funcion Escalar a partir de un
244 Procedimiento Almacenado, con Params de Entrada
245 public Object TraerValorEscalar(string
246 procedimientoAlmacenado, params object[] args)
247 {
248 var com = Comando(procedimientoAlmacenado);
249 CargarParametros(com, args);
250 return com.ExecuteScalar();
251 } // end TraerValorEscalar
252
253 // Obtiene un Valor de una funcion Escalar a partir de un
254 Query SQL
255 public object TraerValorEscalarSql(string comandoSql)
256 {
257 var com = ComandoSql(comandoSql);
258 return com.ExecuteScalar();
259 } // end TraerValorEscalarSql
260
261 #endregion
262
263 #region "Acciones"
264
265 protected abstract IDbConnection CrearConexion(string
266 cadena);
267 protected abstract IDbCommand Comando(string
268 procedimientoAlmacenado);
269 protected abstract IDbCommand ComandoSql(string
270 comandoSql);
271 protected abstract IDataAdapter CrearDataAdapter(string
272 procedimientoAlmacenado, params Object[] args);
273 protected abstract IDataAdapter CrearDataAdapterSql(string
274 comandoSql);
275 protected abstract void CargarParametros(IDbCommand
276 comando, Object[] args);
277
278 // metodo sobrecargado para autenticarse contra el motor de
279 BBDD
280 public bool Autenticar()
281 {
282 if (Conexion.State != ConnectionState.Open)
283 Conexion.Open();
284 return true;
285 }// end Autenticar
286
287 // metodo sobrecargado para autenticarse contra el motor de
288 BBDD
289 public bool Autenticar(string vUsuario, string vPassword)
290 {
291 MUsuario = vUsuario;
292 MPassword = vPassword;
293 MConexion = CrearConexion(CadenaConexion);
294
295 MConexion.Open();
296 return true;
297 }// end Autenticar
298
299
300 // cerrar conexion
301 public void CerrarConexion()
302 {
303 if (Conexion.State != ConnectionState.Closed)
304 MConexion.Close();
305 }
306
307 // end CerrarConexion
308
309
310 // Ejecuta un Procedimiento Almacenado en la base.
311 public int Ejecutar(string procedimientoAlmacenado)
312 { return
313 Comando(procedimientoAlmacenado).ExecuteNonQuery(); } // end
314 Ejecutar
315
316 // Ejecuta un query sql
317 public int EjecutarSql(string comandoSql)
318 { return ComandoSql(comandoSql).ExecuteNonQuery(); } // end
319 Ejecutar
320
321 //Ejecuta un Procedimiento Almacenado en la base,
322 utilizando los parámetros.
323 public int Ejecutar(string procedimientoAlmacenado, params
324 Object[] args)
325 {
326 var com = Comando(procedimientoAlmacenado);
327 CargarParametros(com, args);
328 var resp = com.ExecuteNonQuery();
329 for (var i = 0; i < com.Parameters.Count; i++)
330 {
331 var par = (IDbDataParameter)com.Parameters[i];
332 if (par.Direction == ParameterDirection.InputOutput
333 || par.Direction == ParameterDirection.Output)
334 args.SetValue(par.Value, i - 1);
335 }// end for
return resp;
} // end Ejecutar

#endregion
#region "Transacciones"

protected IDbTransaction MTransaccion;


protected bool EnTransaccion;

//Comienza una Transacción en la base en uso.


public void IniciarTransaccion()
{
try
{
MTransaccion = Conexion.BeginTransaction();
EnTransaccion = true;
}// end try
finally
{ EnTransaccion = false; }
}// end IniciarTransaccion

//Confirma la transacción activa.


public void TerminarTransaccion()
{
try
{ MTransaccion.Commit(); }
finally
{
MTransaccion = null;
EnTransaccion = false;
}// end finally
}// end TerminarTransaccion

//Cancela la transacción activa.


public void AbortarTransaccion()
{
try
{ MTransaccion.Rollback(); }
finally
{
MTransaccion = null;
EnTransaccion = false;
}// end finally
}// end AbortarTransaccion

#endregion

}// end class gDatos


}// end namespace
Continuando con la segunda entrega de la programación en n-Capas, (la primera lo
pueden ver aqui). Hasta el momento solo creamos una clase abstracta que servirá de
padre para las demás implementaciones (1 clase por cada fabricante de motor).

Ahora nos enfocaremos en crear una capa para conectarnos a SQL Server, si llegamos
a cambiar de proveedor de base de datos en algún momento, lo único que deberíamos
hacer es agregar una clase semejante a ésta con la implementación especifica para éste
motor, ni siquiera debemos modificar ésta clase que veremos ahora, el unico cambio
que se hará si se desea hacer esto es en una ÚNICA línea de código en una clase
superior que veremos en la tercer entrega.

Para ésta clase el único uso que haremos será del namspace System. También se
encontrará en el mismo namespace que la clase anterior AccesoDatos. La misma será
una clase que hereda de la clase GDatos

1 using System;

Lo siguiente que crearemos en una colección de hashtable para preservar por así
decirlo los comandos ejecutados y accederlos con mayor velocidad si ya fue ejecutado
una vez.

static readonly System.Collections.Hashtable ColComandos = new


1
System.Collections.Hashtable();

El primer método que tendrá esta clase, es un método sellado que sobreescibirá el de su
padre, y creará el ConnectionString y lo retornorá.

1 public override sealed string CadenaConexion


2 {
3 get
4 {
5 if (MCadenaConexion.Length == 0)
6 {
7 if (MBase.Length != 0 && MServidor.Length !=
8 0)
9 {
10 var sCadena = new
11 System.Text.StringBuilder("");
12 sCadena.Append("data
13 source=<SERVIDOR>;");
14 sCadena.Append("initial
15 catalog=<BASE>;");
16 sCadena.Append("user id=<USER>;");
17
18 sCadena.Append("password=<PASSWORD>;");
19 sCadena.Append("persist security
20 info=True;");
21 sCadena.Replace("<SERVIDOR>",
22 Servidor);
23 sCadena.Replace("<BASE>", Base);
24 sCadena.Replace("<USER>", Usuario);
25 sCadena.Replace("<PASSWORD>",
26 Password);
27
28 return sCadena.ToString();
}
throw new Exception("No se puede establecer
la cadena de conexión en la clase SQLServer");
}
return MCadenaConexion;
}// end get
set
{ MCadenaConexion = value; } // end set
}// end CadenaConexion

Ésta es una de las caracteristicas mas llamativas y útiles, que nos permitirá cargar una
cantidad n de parámetros, a los SP que invocaremos

protected override void CargarParametros(System.Data.IDbCommand com,


1 Object[] args)
2{
3 for (int i = 1; i < com.Parameters.Count; i++)
4 {
5 var p =
6 (System.Data.SqlClient.SqlParameter)com.Parameters[i];
7 p.Value = i <= args.Length ? args[i - 1] : null;
8 } // end for
} // end CargarParametros

Luego para crear el Comando a ejecutar crearemos un método que revisará en el


hashTable anteriormente creado, si ya se lo ha hecho o no. En caso que estemos dentro
de una transacción conectado, necesitamos crear una segunda conexión para la lectura
de los parámetros de Procedimiento almacenado.

protected override System.Data.IDbCommand Comando(string


procedimientoAlmacenado)
{
System.Data.SqlClient.SqlCommand com;
if (ColComandos.Contains(procedimientoAlmacenado))
1 com =
2 (System.Data.SqlClient.SqlCommand)ColComandos[procedimientoAlmacenad
3 o];
4 else
5 {
6 var con2 = new
7 System.Data.SqlClient.SqlConnection(CadenaConexion);
8 con2.Open();
9 com = new
10System.Data.SqlClient.SqlCommand(procedimientoAlmacenado, con2)
11 { CommandType =
12System.Data.CommandType.StoredProcedure };
13
14
15 System.Data.SqlClient.SqlCommandBuilder.DeriveParameters(com);
16 con2.Close();
17 con2.Dispose();
18 ColComandos.Add(procedimientoAlmacenado, com);
19 }//end else
20 com.Connection =
(System.Data.SqlClient.SqlConnection)Conexion;
com.Transaction =
(System.Data.SqlClient.SqlTransaction)MTransaccion;
return com;
}// end Comando
Como no puedo sobrecargar un método con la misma cantidad de
parámetros que recibe, y del mismo tipo, me veo obligado a crear un método extra para
crear el comando si queremos ejecutar sentencias SQL directamente desde la App.

protected override System.Data.IDbCommand ComandoSql(string


comandoSql)
1
{
2
var com = new System.Data.SqlClient.SqlCommand(comandoSql,
3
(System.Data.SqlClient.SqlConnection)Conexion,
4
(System.Data.SqlClient.SqlTransaction)MTransaccion);
5
return com;
}// end Comando

Ahora crearemos la conexión a partir de la cadena de conexión

protected override System.Data.IDbConnection CrearConexion(string


1
cadenaConexion)
2
{ return new System.Data.SqlClient.SqlConnection(cadenaConexion); }

Ya en este punto si intentamos programar en capas, supongo que sabemos para que
sirve un DataAdapter, simplemente veremos como crearlo y asociarlo con el commando
a su vez, este método es para las llamadas a Procedimientos

protected override System.Data.IDataAdapter CrearDataAdapter(string


procedimientoAlmacenado, params Object[] args)
1
{
2
var da = new
3
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlComman
4
d) Comando(procedimientoAlmacenado));
5
if (args.Length != 0)
6
CargarParametros(da.SelectCommand, args);
7
return da;
} // end CrearDataAdapter

La siguiente es casi los mismo que la anterior, pero para ejecución de query’s SQL

protected override System.Data.IDataAdapter


CrearDataAdapterSql(string comandoSql)
1
{
2
var da = new
3
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlComman
4
d) ComandoSql(comandoSql));
5
return da;
} // end CrearDataAdapterSql

Nada más queda crear los constructores, creamos 4 sobrecargas del mismo según lo que
necesitemos más adelante.

1 public SqlServer()
2 {
3 Base = "";
4 Servidor = "";
5 Usuario = "";
6 Password = "";
7 }// end DatosSQLServer
8
9
public SqlServer(string cadenaConexion)
10
{ CadenaConexion = cadenaConexion; }// end DatosSQLServer
11
12
13
public SqlServer(string servidor, string @base)
14
{
15
Base = @base;
16
Servidor = servidor;
17
}// end DatosSQLServer
18
19
20
public SqlServer(string servidor, string @base, string usuario,
21
string password)
22
{
23
Base = @base;
24
Servidor = servidor;
25
Usuario = usuario;
26
Password = password;
27
}// end DatosSQLServer

El código completo quedaría así:

1 using System;
2
3 namespace AccesoDatos
4 {
5 public class SqlServer : GDatos
6 {
7 /*
8 * Continuaremos con el método Comando, procediendo de
9 igual forma que en los anteriores.
10 * En este caso, además, implementaremos un mecanismo de
11 “preservación” de los Comandos creados,
12 * para acelerar su utilización. Esto es, cada
13 procedimiento que sea accedido, se guardará
14 * en memoria hasta que la instancia del objeto se
15 destruya. Para ello, declararemos una variable
16 * como HashTable para la clase, con el modificador Shared
17 (compartida) que permite
18 * persistir la misma entre creaciones de objetos
19 */
20 static readonly System.Collections.Hashtable ColComandos =
21 new System.Collections.Hashtable();
22
23
24 public override sealed string CadenaConexion
25 {
26 get
27 {
28 if (MCadenaConexion.Length == 0)
29 {
30 if (MBase.Length != 0 && MServidor.Length != 0)
31 {
32 var sCadena = new
33 System.Text.StringBuilder("");
34 sCadena.Append("data source=<SERVIDOR>;");
35 sCadena.Append("initial catalog=<BASE>;");
36 sCadena.Append("user id=<USER>;");
37 sCadena.Append("password=<PASSWORD>;");
38 sCadena.Append("persist security
39 info=True;");
40 sCadena.Append("user id=sa;packet
41 size=4096");
42 sCadena.Replace("<SERVIDOR>", Servidor);
43 sCadena.Replace("<BASE>", Base);
44 sCadena.Replace("<USER>", Usuario);
45 sCadena.Replace("<PASSWORD>", Password);
46
47 return sCadena.ToString();
48 }
49 throw new Exception("No se puede establecer la
50 cadena de conexión en la clase DatosSQLServer");
51 }
52 return null;
53 }// end get
54 set
55 { MCadenaConexion = value; } // end set
56 }// end CadenaConexion
57
58
59 /*
60 * Agregue ahora la definición del procedimiento
61 CargarParametros, el cual deberá asignar cada valor
62 * al parámetro que corresponda (considerando que, en el
63 caso de SQLServer©, el parameter 0
64 * siempre corresponde al “return Value” del Procedimiento
65 Almacenado). Por otra parte, en algunos casos,
66 * como la ejecución de procedimientos almacenados que
67 devuelven un valor como parámetro de salida,
68 * la cantidad de elementos en el vector de argumentos,
69 puede no corresponder con la cantidad de parámetros.
70 * Por ello, se decide comparar el indicador con la
71 cantidad de argumentos recibidos, antes de asignar el valor.
72 * protected override void
73 CargarParametros(System.Data.IDbCommand Com, System.Object[] Args)
74 */
75 protected override void
76 CargarParametros(System.Data.IDbCommand com, Object[] args)
77 {
78 for (int i = 1; i < com.Parameters.Count; i++)
79 {
80 var p =
81 (System.Data.SqlClient.SqlParameter)com.Parameters[i];
82 p.Value = i <= args.Length ? args[i - 1] : null;
83 } // end for
84 } // end CargarParametros
85
86
87 /*
88 * En el procedimiento Comando, se buscará primero si ya
89 existe el comando en dicha Hashtable para retornarla
90 * (convertida en el tipo correcto). Caso contrario, se
91 procederá a la creación del mismo,
92 * y su agregado en el repositorio. Dado que cabe la
93 posibilidad de que ya estemos dentro de una transacción,
94 * es necesario abrir una segunda conexión a la base de
95 datos, para obtener la definición de los parámetros
96 * del procedimiento Almacenado (caso contrario da error,
97 por intentar leer sin tener asignado el
98 * objeto Transaction correspondiente). Además, el comando,
99 obtenido por cualquiera de los mecanismos
100 * debe recibir la conexión y la transacción
101 correspondientes (si no hay Transacción, la variable es null,
102 * y ese es el valor que se le pasa al objeto Command)
103 */
104 protected override System.Data.IDbCommand Comando(string
105 procedimientoAlmacenado)
106 {
107 System.Data.SqlClient.SqlCommand com;
108 if (ColComandos.Contains(procedimientoAlmacenado))
109 com =
110 (System.Data.SqlClient.SqlCommand)ColComandos[procedimientoAlmacena
111 do];
112 else
113 {
114 var con2 = new
115 System.Data.SqlClient.SqlConnection(CadenaConexion);
116 con2.Open();
117 com = new
118 System.Data.SqlClient.SqlCommand(procedimientoAlmacenado, con2)
119 { CommandType =
120 System.Data.CommandType.StoredProcedure };
121 System.Data.SqlClient.SqlCommandBuilder.DeriveParam
122 eters(com);
123 con2.Close();
124 con2.Dispose();
125 ColComandos.Add(procedimientoAlmacenado, com);
126 }//end else
127 com.Connection =
128 (System.Data.SqlClient.SqlConnection)Conexion;
129 com.Transaction =
130 (System.Data.SqlClient.SqlTransaction)MTransaccion;
131 return com;
132 }// end Comando
133
134 protected override System.Data.IDbCommand ComandoSql(string
135 comandoSql)
136 {
137 var com = new
138 System.Data.SqlClient.SqlCommand(comandoSql,
139 (System.Data.SqlClient.SqlConnection)Conexion,
140 (System.Data.SqlClient.SqlTransaction)MTransaccion);
141 return com;
142 }// end Comando
143
144
145 /*
146 * Luego implementaremos CrearConexion, donde simplemente
147 se devuelve una nueva instancia del
148 * objeto Conexión de SqlClient, utilizando la cadena de
149 conexión del objeto.
150 */
151 protected override System.Data.IDbConnection
152 CrearConexion(string cadenaConexion)
153 { return new
154 System.Data.SqlClient.SqlConnection(cadenaConexion); }
155
156
157 //Finalmente, es el turno de definir CrearDataAdapter, el
158 cual aprovecha el método Comando para crear el comando necesario.
159 protected override System.Data.IDataAdapter
CrearDataAdapter(string procedimientoAlmacenado, params Object[]
args)
{
var da = new
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlComm
and) Comando(procedimientoAlmacenado));
if (args.Length != 0)
CargarParametros(da.SelectCommand, args);
return da;
} // end CrearDataAdapter

//Finalmente, es el turno de definir CrearDataAdapter, el


cual aprovecha el método Comando para crear el comando necesario.
protected override System.Data.IDataAdapter
CrearDataAdapterSql(string comandoSql)
{
var da = new
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlComm
and) ComandoSql(comandoSql));
return da;
} // end CrearDataAdapterSql

/*
* Definiremos dos constructores especializados, uno que
reciba como argumentos los valores de Nombre del Servidor
* y de base de datos que son necesarios para acceder a
datos, y otro que admita directamente la cadena de conexión
completa.
* Los constructores se definen como procedimientos
160
públicos de nombre New.
161
*/
162
public SqlServer()
163
{
Base = "";
Servidor = "";
Usuario = "";
Password = "";
}// end DatosSQLServer

public SqlServer(string cadenaConexion)


{ CadenaConexion = cadenaConexion; }// end DatosSQLServer

public SqlServer(string servidor, string @base)


{
Base = @base;
Servidor = servidor;
}// end DatosSQLServer

public SqlServer(string servidor, string @base, string


usuario, string password)
{
Base = @base;
Servidor = servidor;
Usuario = usuario;
Password = password;
}// end DatosSQLServer
}// end class DatosSQLServer
}// end namespace
Esta es la tercer entrega, probablemente será la más corta pero no la última aún. El
motivo de su longitud es por que es una clase que se utiliza como medio para crear la
flexibilidad y portabilidad de fuentes de datos, en éste caso motores de base de
datos.

También daremos por terminada la capa de Acceso a Datos, entonces así no mezclamos
el código y será más fácil seguirlo posteriormente. También pertenecerá al namespace
AccesoDatos. Lo llamo conexión por que es la clase con las otras capas interactuaran en
modo directo.
Para ello creamos un objeto estático de la clase GDatos que instanciará de la clase
SqlServer. Creo que ya van captando el rumbo de esto no? si crearamos otra clase por
ejemplo Oracle.cs o MySQL.cs, solamente cambiariamos una linea de código, donde el
objeto GDatos del tipo GDatos, sea SqlServer, Oracle u otro motor que codifiquemos.
Podemos hacerlo con ODBC, OleDB para conexiones genéricas. No les parece
grandioso que solo deban tocar parte de una línea de código para portar la App a
cualquier otro motor de Base de Datos?

namespace AccesoDatos
{
1 public class Conexion
2 {
3 public static GDatos GDatos;
4 public static bool IniciarSesion(string
5 nombreServidor, string baseDatos, string usuario, string
6 password)
7 {
8 GDatos = new SqlServer(nombreServidor,
9 baseDatos, usuario, password);
10 return GDatos.Autenticar();
11 } //fin inicializa sesion
12
13 public static void FinalizarSesion()
14 {
15 GDatos.CerrarConexion();
16 } //fin FinalizaSesion
17
18 }//end class util
}//end namespace

En la siguiente entrega veremos como crear la capa de Negocio y vincularla a estas


capas de Acceso a Datos.

En la cuarta entrega veremos una capa nueva, la capa de Negocios, como ya dije en los
artículos anteriores hemos dado por terminado la capa de Acceso a Datos.

Aquí es donde diremos como debe procesarse la información. Para este caso no voy a
crear una estructura compleja de BBDD ya que el código de C# ya lleva bastante, pero
reflejará claramente como se usa ésta capa en casos más complejos.

Primeramente crearemos una tabla realmente simple, compuesta por 3 campos llamada
Cliente.

1 CREATE TABLE [dbo].[Cliente](


[IdCliente] [tinyint] NOT NULL,
2 [DescripcionCliente] [varchar](50) NOT NULL,
3 [Siglas] [varchar](15) NULL,
4 CONSTRAINT [PK_Cliente] PRIMARY KEY CLUSTERED
5 (
6 [IdCliente] ASC
7 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
8 IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
9 ON [PRIMARY]
) ON [PRIMARY]

Preferentemente trabajaremos con procedimientos almacenados como Zeus manda.


Para crear, listar y buscar tendremos 3 distintos procedimientos.

1 CREATE PROCEDURE [dbo].[insCliente]


2 @id as int,
3 @nombre varchar(50),
4 @sigla varchar(15)
5 AS
6 BEGIN
7 insert into Cliente values(@id, @nombre, @sigla);
8 END
1 CREATE PROCEDURE [dbo].[slcCliente]
2 AS
3 BEGIN
4 select * from Cliente;
5 END
1 CREATE PROCEDURE [dbo].[slcCliente]
2 AS
3 BEGIN
4 select * from Cliente;
5 END

Con esto ya tenemos creada la estructura de la base de datos completa, al menos hasta
donde vamos a utilizar. Estos SP, se pueden considerar parte de la capa de negocios, no
precisamente siempre tiene que estar en la aplicación, es lo que venía escribiendo en las
primeras partes del tutorial.

Del lado de la aplicación utilizaremos el mapeo ORM (doy por sabido que
conocen que esto, sino pueden investigarlo aquí), ya que es una de la prácticas más
utilizadas y probadas que ahorran código y ayudan a cumplir con varios conceptos de la
OOP. Dentro del mismo proyecto de biblioteca de clases, que estamos teniendo en
nuestra solución (hasta ahora no hemos creado ningún tipo de interfaz de usuario). Creo
conveniente crear una carpeta en la raíz del proyecto llamada Orm, justamente para
seguir la convención (trato de seguirlas todas, derrepente se me escapa alguna, sabrán
disculparme :S). Al crear esta carpeta, y crear clases dentro de ella también se creará un
nuevo nivel de espacios de nombres: AccesoDatos.Orm.

A esta la llamaremos Cliente.cs, y así una clase por cada tabla que tengamos en nuestra
BBDD. En ella crearemos 3 variables, que representarán los atributos de Cliente
(mapeando los campos de la tabla). Al mismo tiempo de crear estos, crearemos sus
setters y getters.

1 public short IdCliente { get; set; }


2 public string Descripcion { get; set; }
3 public string Sigla { get; set; }

Ahora es momento de utilizar el potencial del código que hemos venido escribiendo,
veanlo en acción en los 3 métodos que crearemos, uno para dar de Alta, un registro de
cliente, en la BBDD, otro para buscar, y otro para listar todos.

{ Conexion.GDatos.Ejecutar("insCliente", cliente.IdCliente,
cliente.Descripcion, cliente.Sigla, 3); }
1
2
public DataTable Listar()
3
{ return Conexion.GDatos.TraerDataTable("slcCliente"); }
4
5
public Cliente Listar(int idCliente)
6
{
7
var cliente = new Cliente();
8
DataTable dt =
9
Conexion.GDatos.TraerDataTable("slcClienteById", idCliente);
10
11
if (dt.Rows.Count > 0)
12
{
13
cliente.IdCliente = Convert.ToInt16(dt.Rows[0][0]);
14
cliente.Descripcion = Convert.ToString(dt.Rows[0]
15
[1]);
16
cliente.Sigla = Convert.ToString(dt.Rows[0][2]);
17
18
return cliente;
19
}
20
return null;
}

En esta clase, deben crear todos los métodos, que correspondan a la clase cliente.
Pueden crear todos los que quieran, sin importar que ésta clase quede muy larga,
simplemente deben encargarse, que sea aquí donde corresponde una acción en sí. Como
ven estamos aplicando sobrecarga de métodos en ésta clase (no lo confundan con
polimorfismo).
La clase completa quedaría así:

1 using System;
2 using System.Data;
3
4 namespace AccesoDatos.Orm
5 {
6 public class Cliente
7 {
8 // lista de atributos con setters y getters
9 public short IdCliente { get; set; }
10 public string Descripcion { get; set; }
11 public string Sigla { get; set; }
12
13 // métodos
14 public void Crear(Cliente cliente)
15 { Conexion.GDatos.Ejecutar("insCliente", cliente.IdCliente,
16 cliente.Descripcion, cliente.Sigla, 3); }
17
18 public DataTable Listar()
19 { return Conexion.GDatos.TraerDataTable("slcCliente"); }
20
21 public Cliente Listar(int idCliente)
{
var cliente = new Cliente();
22
DataTable dt =
23
Conexion.GDatos.TraerDataTable("slcClienteById", idCliente);
24
25
if (dt.Rows.Count > 0)
26
{
27
cliente.IdCliente = Convert.ToInt16(dt.Rows[0][0]);
28
cliente.Descripcion = Convert.ToString(dt.Rows[0]
29
[1]);
30
cliente.Sigla = Convert.ToString(dt.Rows[0][2]);
31
32
return cliente;
33
}
34
return null;
35
}
36
}
}

Con ésto vemos como se implementa la capa de negocios. En la siguiente entrega


veremos ya la capa de Presentación, en lo posible, utilizares todas las clases hechas ya
incluida ésta en una aplicación WinForm y otra WebForm, verán que no sólo es
portable a cualquier proveedor de datos, sino que es portable a distintas plataformas
de ejecución.

Con ésta entrega cumpliremos con la capa de Presentación, utilizaremos todo lo que
hemos visto hasta ahora aplicados a una interfaz de usuario, y como lo prometí, lo
veremos implementado en winForm como en webForm.

El primer ejemplo será Desktop, crearemos un formulario con una apariencia semejante
al que ven en la imagen.
Evidentemente, un sistema real no lo harán así, el botón conectar emula el
comportamiento de una pantalla de login, el boton crear mandará a la BBDD los datos
de la caja, Listar rellenará la grilla y Buscar By Id se encargará de devolvernos un
registro a partir de lo que carguemos en la caja de Id. Otra implementación interesante
sería agregarle un identity a la tabla cliente, pero para el ejemplo ya servirá esto. Para
esto crearemos 2 proyectos más dentro de nuestra solución, uno será una aplicación
Windows Form con Visual C#, y la otra un sitio Web.

El código que pondremos en el botón conectar es como sigue, recuerden que es


conveniente armarlo dinamicamente con una pantalla de login especialmente el usuario
y el pass. Con esto logramos armar toda la capa de Acceso a Datos, no se mantiene
conectada la aplicación, sino solo sus valores de conexión en memoria.

try
1
{
2
Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "123");
3
MessageBox.Show(String.Format("{0}", "Se conecto
4
exitosamente"));
5
}
6
catch (Exception ex)
7
{
8
MessageBox.Show(ex.Message);
9
}

Para crear un nuevo cliente instanciamos un objeto cliente del tipo Cliente, le seteamos
sus atributos, a partir de los valores de la caja de texto, e invocamos el método crear
enviandole el nuevo objeto cliente.

1 var cliente = new Cliente();


2 try
3 {
cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);
4
cliente.Descripcion = txtDescripcionCliente.Text;
5
cliente.Sigla = txtSiglaCliente.Text;
6
7
cliente.Crear(cliente);
8
MessageBox.Show(String.Format("{0}", "Se creo
9
exitosamente"));
10
}
11
catch (Exception ex)
12
{
13
MessageBox.Show(ex.Message);
14
}

Luego de esto escribimos el código de listado de todos los clientes, y lo cargamos en la


grilla. Se dan cuenta que necesitamos muy pocas lineas de código en la capa de
Presentación, y que no tiene una dependencia de la BBDD?. Si se dan cuenta,
cuando vamos a devolver muchos registros no podemos utilizar un montón de instancias
de Cliente, sino simplemente devolvemos un DataTable, DataSet o DataReader.

1 var cliente = new Cliente();


2 try
3 {
4 grilla.DataSource = cliente.Listar();
5 }
6 catch (Exception ex)
7 {
8 MessageBox.Show(ex.Message);
9 }

Finalmente el código de búsqueda quedaría algo asi. Cómo sabemos que si


buscamos por la PK, siempre nos devolverá un sólo registro, lo podemos crear como un
objeto Cliente.

var cliente = new Cliente();


1
try
2
{
3
cliente = cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
4
5
if (cliente != null)
6
{
7
txtDescripcionCliente.Text = cliente.Descripcion;
8
txtSiglaCliente.Text = cliente.Sigla;
9
}
10
else
11
{ MessageBox.Show(String.Format("{0}", "No existia el cliente
12
buscado")); }
13
}
14
catch (Exception ex)
15
{
16
MessageBox.Show(ex.Message);
17
}

El código completo quedaría así:

1 using System;
2 using System.Windows.Forms;
3 using AccesoDatos;
4 using AccesoDatos.Orm;
5
6 namespace Test
7 {
8 public partial class Form1 : Form
9 {
10 public Form1()
11 {
12 InitializeComponent();
13 }
14
15 private void btnConectar_Click(object sender, EventArgs e)
16 {
17 try
18 {
19 Conexion.IniciarSesion("127.0.0.1", "Queryable",
20 "sa", "***");
21 MessageBox.Show(String.Format("{0}", "Se conecto
22 exitosamente"));
23 }
24 catch (Exception ex)
25 {
26 MessageBox.Show(ex.Message);
27 }
28 }
29
30 private void btnListar_Click(object sender, EventArgs e)
31 {
32 var cliente = new Cliente();
33 try
34 {
35 grilla.DataSource = cliente.Listar();
36 }
37 catch (Exception ex)
38 {
39 MessageBox.Show(ex.Message);
40 }
41 }
42
43 private void btnCrear_Click(object sender, EventArgs e)
44 {
45 var cliente = new Cliente();
46 try
47 {
48 cliente.IdCliente =
49 Convert.ToInt16(txtIdCliente.Text);
50 cliente.Descripcion = txtDescripcionCliente.Text;
51 cliente.Sigla = txtSiglaCliente.Text;
52
53 cliente.Crear(cliente);
54 MessageBox.Show(String.Format("{0}", "Se creo
55 exitosamente"));
56 }
57 catch (Exception ex)
58 {
59 MessageBox.Show(ex.Message);
60 }
61 }
62
63 private void btnBuscarById_Click(object sender, EventArgs e)
64 {
var cliente = new Cliente();
try
{
cliente =
65
cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
66
67
if (cliente != null)
68
{
69
txtDescripcionCliente.Text =
70
cliente.Descripcion;
71
txtSiglaCliente.Text = cliente.Sigla;
72
}
73
else
74
{ MessageBox.Show(String.Format("{0}", "No existia
75
el cliente buscado")); }
76
}
77
catch (Exception ex)
78
{
79
MessageBox.Show(ex.Message);
80
}
}
}
}

Con respecto a la parte web, tendremos 2 partes, el código ASP.net, y el c#, veamoslo,
se los dejo completamente de una:

1 <%@ Page Language="C#" AutoEventWireup="true"


2 CodeFile="Cliente.aspx.cs" Inherits="Cliente" %>
3
4 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
5 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
6
7 <html xmlns="http://www.w3.org/1999/xhtml">
8 <head runat="server">
9 <title></title>
10 </head>
11 <body>
12 <form id="frmCliente" runat="server">
13 <div>
14
15 <asp:Label ID="Id" runat="server" Text="Id: "></asp:Label>
16 <asp:TextBox ID="txtIdCliente" runat="server"></asp:TextBox>
17 <br />
18 <asp:Label ID="Label1" runat="server" Text="Descripcion:
19 "></asp:Label>
20 <asp:TextBox ID="txtDescripcionCliente"
21 runat="server"></asp:TextBox>
22 <asp:TextBox ID="txtuser" runat="server">sa</asp:TextBox>
23 <br />
24 <asp:Label ID="Label2" runat="server" Text="Siglas:
25 "></asp:Label>
26 <asp:TextBox ID="txtSiglasCliente"
27 runat="server"></asp:TextBox>
28 <br />
29 <hr style="margin-top: 0px; margin-bottom: 0px" />
30 <asp:GridView ID="grilla" runat="server" CellPadding="4"
31 ForeColor="#333333"
32 GridLines="None">
33 <AlternatingRowStyle BackColor="White" />
<EditRowStyle BackColor="#2461BF" />
<FooterStyle BackColor="#507CD1" Font-Bold="True"
ForeColor="White" />
<HeaderStyle BackColor="#507CD1" Font-Bold="True"
ForeColor="White" />
<PagerStyle BackColor="#2461BF" ForeColor="White"
HorizontalAlign="Center" />
<RowStyle BackColor="#EFF3FB" />
34
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True"
35
ForeColor="#333333" />
36
<SortedAscendingCellStyle BackColor="#F5F7FB" />
37
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
38
<SortedDescendingCellStyle BackColor="#E9EBEF" />
39
<SortedDescendingHeaderStyle BackColor="#4870BE" />
40
</asp:GridView>
41
<asp:Label ID="Label3" runat="server"
42
Text="Estado:"></asp:Label>
43
<asp:Label ID="lblEstado" runat="server"></asp:Label>
44
<br />
45
<asp:Button ID="btnConectar" runat="server" Text="Conectar"
46
onclick="btnConectar_Click" />
47
<asp:Button ID="btnCrear" runat="server"
48
onclick="btnCrear_Click"
49
Text="Crear" />
50
<asp:Button ID="btnListar" runat="server"
51
onclick="btnListar_Click"
52
Text="Listar" />
53
<asp:Button ID="btnListarById" runat="server"
54
onclick="btnListarById_Click"
Text="Listar By Id" />
<br />

</div>
</form>
</body>
</html>
1 using System;
2
3 // using AccesoDatos;
4
5 public partial class Cliente : System.Web.UI.Page
6 {
7 protected void Page_Load(object sender, EventArgs e)
8 {
9
10 }
11 protected void btnConectar_Click(object sender, EventArgs e)
12 {
13 try
14 {
15 lblEstado.Text = "";
16 AccesoDatos.Conexion.IniciarSesion("127.0.0.1",
17 "Queryable", "sa", "***");
18 lblEstado.Text = String.Format("{0}", "Se conecto
19 exitosamente");
20 }
21 catch (Exception ex)
22 {
23 lblEstado.Text = String.Format(ex.Message);
24 }
25 }
protected void btnListar_Click(object sender, EventArgs e)
{
var cliente = new AccesoDatos.Orm.Cliente();
26 try
27 {
28 grilla.DataSource = cliente.Listar();
29 grilla.DataBind();
30 lblEstado.Text = String.Format("{0}", "Se listo
31exitosamentë");
32 }
33 catch (Exception ex)
34 {
35 lblEstado.Text = ex.Message;
36 }
37 }
38 protected void btnCrear_Click(object sender, EventArgs e)
39 {
40 var cliente = new AccesoDatos.Orm.Cliente();
41 try
42 {
43 cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);
44 cliente.Descripcion = txtDescripcionCliente.Text;
45 cliente.Sigla = txtSiglasCliente.Text;
46
47 cliente.Crear(cliente);
48 lblEstado.Text = String.Format("{0}", "Se creo
49 exitosamente");
50 }
51 catch (Exception ex)
52 {
53 lblEstado.Text = ex.Message;
54 }
55 }
56 protected void btnListarById_Click(object sender, EventArgs e)
57 {
58 var cliente = new AccesoDatos.Orm.Cliente();
59 try
60 {
61 cliente =
62 cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
63
64 if (cliente != null)
65 {
66 txtDescripcionCliente.Text = cliente.Descripcion;
67 txtSiglasCliente.Text = cliente.Sigla;
68 }
69 else
70 { lblEstado.Text = String.Format("{0}", "No existia el
71 cliente buscado"); }
72 }
73 catch (Exception ex)
74 {
75 lblEstado.Text = ex.Message;
}
}
}
Si pueden agregar/aportar mejoras y funcionalidad a estas clases, serán bienvenidas
de hecho éste código lo creo un MVP de Microsoft y yo le agregue algunas
funcionalidades..