Вы находитесь на странице: 1из 28

Como

programar en n-Capas con C# (Parte 1)


Este es un tema polmico del que se habla mucho y nada, digo que se habla mucho porque al buscar algo de
informacin en Internet, uno se da cuenta, que esta plagado de sitios donde preguntan como aplicar programacin en 3
capas, o N-Capas, pero en muy pocos lugares se responde con algo cierto y concreto, la mayora hacen referencia a
libros gordos que tardaras en leer semanas (no estoy en contra de la lectura, es un proceso largo nada ms y casi todos
buscamos aprenderlo un poco ms rpido). Este artculo tambin 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 centmetros
La primer gran confusin que noto, es que la mayora no sabe diferenciar entre los conceptos
1. Arquitectura de 3 capas: se basa ms bien en como ser construido el entorno, una manera de decirlo en romper el
clsico 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 programacin en capas. Se acerca ms a un concepto fsico.
2. Programacin en 3 (n) capas: este es el tema en cuestin y estira ms hacia un concepto lgico. En cmo partimos,
agrupamos, clasificamos, optimizamos nuestro cdigo. 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 aplicacin en s y en la BBDD), y Capa de
Presentacin (es ms bien lo que el usuario percibe, su interfaz grfica 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 separacin en capas quiere decir que podemos cambiar de proveedor
de base de datos, y no necesitaremos reescribir toda la aplicacin de vuelta, sino solamente esa pequea capa
y reutilizaramos la interfaz y las reglas de negocios, o tambin podemos mantener las reglas de negocios y el motor de
base de datos, y fcilmente cambiarnos de una interfaz WinForm a WebForm, siempre la ms dura de cambiar en la de
negocios ya que afecta en un nivel mnimo a las otras 2 capas.
Creo que ya es suficiente teora de momento y podemos comenzar con la accin, el cdigo 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
versin 2005 con el framework 2.0 en adelante, ya que ADO.Net no ha sufrido grandes cambios desde esa versin, as
que ustedes lo pueden ir creando en la versin del IDE o framework que ms gusten.
Primeramente vamos a crear una solucin 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 cdigo con regiones para una mayor comodidad en la lectura del mismo, la
primer regin es la de Declaracin de Variables en la misma creamos las variables o atributos para la conexion a la
BBDD, ms un objeto de interfaz de conexin para que sea implementada de manera especfica 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 artculo. Esta es una clase que obligatoriamente debe ser hereda por otra. El nivel de acceso por eso
estn definidas como protected para que sean modificadas por si misma o por sus clases derivadas.
1
2
3
4
5
6
7
8
9
10

#region "Declaracin de Variables"


protected
protected
protected
protected
protected
protected

string MServidor = "";


string MBase = "";
string MUsuario = "";
string MPassword = "";
string MCadenaConexion = "";
IDbConnection MConexion;

#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
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

// Nombre del equipo servidor de datos.


public string Servidor
{
get { return MServidor; }
set { MServidor = value; }
} // end Servidor
// Nombre de la base de datos a utilizar.
public string Base
{
get { return MBase; }
set { MBase = value; }
} // end Base
// Nombre del Usuario de la BD.
public string Usuario
{
get { return MUsuario; }
set { MUsuario = value; }
} // end Usuario
// Password del Usuario de la BD.
public string Password
{
get { return MPassword; }
set { MPassword = value; }
} // end Password
// Cadena de conexin completa a la base.
public abstract string CadenaConexion
{ get; set; }
#endregion
#region "Privadas"
// Crea u obtiene un objeto para conectarse a la base de datos.
protected IDbConnection Conexion
{
get
{
// si aun no tiene asignada la cadena de conexion lo hace
if (MConexion == null)
MConexion = CrearConexion(CadenaConexion);
// si no esta abierta aun la conexion, lo abre
if (MConexion.State != ConnectionState.Open)
MConexion.Open();
// retorna la conexion en modo interfaz, para que se adapte a cualquier
implementacion de los distintos fabricantes de motores de bases de datos
return MConexion;
} // end get
} // end Conexion
#endregion

Creamos ahora los mtodos 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 implementacin general.
1
2
3
4
5
6
7
8

#region "Lecturas"
// Obtiene un DataSet a partir de un Procedimiento Almacenado.
public DataSet TraerDataSet(string procedimientoAlmacenado)
{
var mDataSet = new DataSet();
CrearDataAdapter(procedimientoAlmacenado).Fill(mDataSet);
return mDataSet;

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

} // end TraerDataset
//Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parmetros.
public DataSet TraerDataSet(string procedimientoAlmacenado, params Object[] args)
{
var mDataSet = new DataSet();
CrearDataAdapter(procedimientoAlmacenado, args).Fill(mDataSet);
return mDataSet;
} // end TraerDataset
// Obtiene un DataSet a partir de un Query Sql.
public DataSet TraerDataSetSql(string comandoSql)
{
var mDataSet = new DataSet();
CrearDataAdapterSql(comandoSql).Fill(mDataSet);
return mDataSet;
} // end TraerDataSetSql
// Obtiene un DataTable a partir de un Procedimiento Almacenado.
public DataTable TraerDataTable(string procedimientoAlmacenado)
{ return TraerDataSet(procedimientoAlmacenado).Tables[0].Copy(); } // end
TraerDataTable
//Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parmetros.
public DataTable TraerDataTable(string procedimientoAlmacenado, params Object[] args)
{ return TraerDataSet(procedimientoAlmacenado, args).Tables[0].Copy(); } // end
TraerDataTable
//Obtiene un DataTable a partir de un Query SQL
public DataTable TraerDataTableSql(string comandoSql)
{ return TraerDataSetSql(comandoSql).Tables[0].Copy(); } // end TraerDataTableSql
// Obtiene un DataReader a partir de un Procedimiento Almacenado.
public IDataReader TraerDataReader(string procedimientoAlmacenado)
{
var com = Comando(procedimientoAlmacenado);
return com.ExecuteReader();
} // end TraerDataReader
// Obtiene un DataReader a partir de un Procedimiento Almacenado y sus parmetros.
public IDataReader TraerDataReader(string procedimientoAlmacenado, params object[]
args)
{
var com = Comando(procedimientoAlmacenado);
CargarParametros(com, args);
return com.ExecuteReader();
} // end TraerDataReader
// Obtiene un DataReader a partir de un Procedimiento Almacenado.
public IDataReader TraerDataReaderSql(string comandoSql)
{
var com = ComandoSql(comandoSql);
return com.ExecuteReader();
} // end TraerDataReaderSql
// Obtiene un Valor Escalar a partir de un Procedimiento Almacenado. Solo funciona con
SP's que tengan
// definida variables de tipo output, para funciones escalares mas abajo se declara un
metodo
public object TraerValorOutput(string procedimientoAlmacenado)
{
// asignar el string sql al command
var com = Comando(procedimientoAlmacenado);
// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del SP
foreach (IDbDataParameter par in com.Parameters)

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140

// si tiene parametros de tipo IO/Output retornar ese valor


if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor
// Obtiene un Valor a partir de un Procedimiento Almacenado, y sus parmetros.
public object TraerValorOutput(string procedimientoAlmacenado, params Object[] args)
{
// asignar el string sql al command
var com = Comando(procedimientoAlmacenado);
// cargar los parametros del SP
CargarParametros(com, args);
// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del SP
foreach (IDbDataParameter par in com.Parameters)
// si tiene parametros de tipo IO/Output retornar ese valor
if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor
// Obtiene un Valor Escalar a partir de un Procedimiento Almacenado.
public object TraerValorOutputSql(string comadoSql)
{
// asignar el string sql al command
var com = ComandoSql(comadoSql);
// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del Query (uso tipico envio de varias sentencias
sql en el mismo command)
foreach (IDbDataParameter par in com.Parameters)
// si tiene parametros de tipo IO/Output retornar ese valor
if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor
// 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 mtodos abstractos,
para que las clases derivadas estn obligadas a implementarlas a su manera, en un modo especifico, ya que los objetos
connection, command, dataadapter, son muy especficos y deben ser implementados por cada una.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

#region "Acciones"
protected abstract
protected abstract
protected abstract
protected abstract
Object[] args);
protected abstract
protected abstract

IDbConnection CrearConexion(string cadena);


IDbCommand Comando(string procedimientoAlmacenado);
IDbCommand ComandoSql(string comandoSql);
IDataAdapter CrearDataAdapter(string procedimientoAlmacenado, params
IDataAdapter CrearDataAdapterSql(string comandoSql);
void CargarParametros(IDbCommand comando, Object[] args);

// metodo sobrecargado para autenticarse contra el motor de BBDD


public bool Autenticar()
{
if (Conexion.State != ConnectionState.Open)
Conexion.Open();
return true;
}// end Autenticar
// metodo sobrecargado para autenticarse contra el motor de BBDD
public bool Autenticar(string vUsuario, string vPassword)
{
MUsuario = vUsuario;
MPassword = vPassword;
MConexion = CrearConexion(CadenaConexion);
MConexion.Open();
return true;
}// end Autenticar

// cerrar conexion
public void CerrarConexion()
{
if (Conexion.State != ConnectionState.Closed)
MConexion.Close();
}
// end CerrarConexion

// Ejecuta un Procedimiento Almacenado en la base.


public int Ejecutar(string procedimientoAlmacenado)
{ return Comando(procedimientoAlmacenado).ExecuteNonQuery(); } // end Ejecutar
// Ejecuta un query sql
public int EjecutarSql(string comandoSql)
{ return ComandoSql(comandoSql).ExecuteNonQuery(); } // end Ejecutar
//Ejecuta un Procedimiento Almacenado en la base, utilizando los parmetros.
public int Ejecutar(string procedimientoAlmacenado, params Object[] args)
{
var com = Comando(procedimientoAlmacenado);
CargarParametros(com, args);
var resp = com.ExecuteNonQuery();
for (var i = 0; i < com.Parameters.Count; i++)
{
var par = (IDbDataParameter)com.Parameters[i];
if (par.Direction == ParameterDirection.InputOutput || par.Direction ==
ParameterDirection.Output)
args.SetValue(par.Value, i - 1);
}// end for
return resp;

63 } // end Ejecutar
#endregion
Ahora bien, no podemos olvidarnos de la seccin transaccional, no se utiliza normalmente en todos lados desde la
aplicacin, pero en procesos dependientes es necesario, as que si necesitamos usarlo, podemos crearlo de este modo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

#region "Transacciones"
protected IDbTransaction MTransaccion;
protected bool EnTransaccion;
//Comienza una Transaccin en la base en uso.
public void IniciarTransaccion()
{
try
{
MTransaccion = Conexion.BeginTransaction();
EnTransaccion = true;
}// end try
finally
{ EnTransaccion = false; }
}// end IniciarTransaccion

//Confirma la transaccin activa.


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

//Cancela la transaccin activa.


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

El cdigo completo lo pueden ver aqui:


1
2
3
4
5
6
7
8
9
10
11
12
13
14

using System;
using System.Data;
namespace AccesoDatos
{
public abstract class GDatos
{
#region "Declaracin de Variables"
protected
protected
protected
protected
protected

string
string
string
string
string

MServidor = "";
MBase = "";
MUsuario = "";
MPassword = "";
MCadenaConexion = "";

protected IDbConnection MConexion;


15
16
#endregion
17
18
#region "Setters y Getters"
19
20
// Nombre del equipo servidor de datos.
21
public string Servidor
22
{
23
get { return MServidor; }
24
25
set { MServidor = value; }
} // end Servidor
26
27
// Nombre de la base de datos a utilizar.
28
public string Base
29
{
30
get { return MBase; }
31
set { MBase = value; }
32
} // end Base
33
34
// Nombre del Usuario de la BD.
35
public string Usuario
36
{
37
get { return MUsuario; }
38
set { MUsuario = value; }
39
} // end Usuario
40
41
// Password del Usuario de la BD.
42
public string Password
43
{
44
get { return MPassword; }
45
set { MPassword = value; }
46
} // end Password
47
48
// Cadena de conexin completa a la base.
49
public abstract string CadenaConexion
50
{ get; set; }
51
52
#endregion
53
54
#region "Privadas"
55
56
// Crea u obtiene un objeto para conectarse a la base de datos.
57
protected IDbConnection Conexion
58
{
59
get
60
{
61
// si aun no tiene asignada la cadena de conexion lo hace
62
if (MConexion == null)
63
MConexion = CrearConexion(CadenaConexion);
64
65
// si no esta abierta aun la conexion, lo abre
66
if (MConexion.State != ConnectionState.Open)
67
MConexion.Open();
68
69
// retorna la conexion en modo interfaz, para que se adapte a
70
71 cualquier implementacion de los distintos fabricantes de motores de bases de datos
return MConexion;
72
} // end get
73
} // end Conexion
74
75
#endregion
76
77
#region "Lecturas"
78
79
// Obtiene un DataSet a partir de un Procedimiento Almacenado.
80
public DataSet TraerDataSet(string procedimientoAlmacenado)
81
{
82
var mDataSet = new DataSet();
83
CrearDataAdapter(procedimientoAlmacenado).Fill(mDataSet);
84

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

return mDataSet;
} // end TraerDataset

//Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parmetros.


public DataSet TraerDataSet(string procedimientoAlmacenado, params Object[]
args)
{
var mDataSet = new DataSet();
CrearDataAdapter(procedimientoAlmacenado, args).Fill(mDataSet);
return mDataSet;
} // end TraerDataset
// Obtiene un DataSet a partir de un Query Sql.
public DataSet TraerDataSetSql(string comandoSql)
{
var mDataSet = new DataSet();
CrearDataAdapterSql(comandoSql).Fill(mDataSet);
return mDataSet;
} // end TraerDataSetSql
// Obtiene un DataTable a partir de un Procedimiento Almacenado.
public DataTable TraerDataTable(string procedimientoAlmacenado)
{ return TraerDataSet(procedimientoAlmacenado).Tables[0].Copy(); } // end
TraerDataTable

//Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parmetros.


public DataTable TraerDataTable(string procedimientoAlmacenado, params
Object[] args)
{ return TraerDataSet(procedimientoAlmacenado, args).Tables[0].Copy(); } //
end TraerDataTable
//Obtiene un DataTable a partir de un Query SQL
public DataTable TraerDataTableSql(string comandoSql)
{ return TraerDataSetSql(comandoSql).Tables[0].Copy(); } // end
TraerDataTableSql
// Obtiene un DataReader a partir de un Procedimiento Almacenado.
public IDataReader TraerDataReader(string procedimientoAlmacenado)
{
var com = Comando(procedimientoAlmacenado);
return com.ExecuteReader();
} // end TraerDataReader

// Obtiene un DataReader a partir de un Procedimiento Almacenado y sus


parmetros.
public IDataReader TraerDataReader(string procedimientoAlmacenado, params
object[] args)
{
var com = Comando(procedimientoAlmacenado);
CargarParametros(com, args);
return com.ExecuteReader();
} // end TraerDataReader
// Obtiene un DataReader a partir de un Procedimiento Almacenado.
public IDataReader TraerDataReaderSql(string comandoSql)
{
var com = ComandoSql(comandoSql);
return com.ExecuteReader();
} // end TraerDataReaderSql
// Obtiene un Valor Escalar a partir de un Procedimiento Almacenado. Solo
funciona con SP's que tengan
// definida variables de tipo output, para funciones escalares mas abajo se
declara un metodo
public object TraerValorOutput(string procedimientoAlmacenado)
{
// asignar el string sql al command

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

var com = Comando(procedimientoAlmacenado);


// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del SP
foreach (IDbDataParameter par in com.Parameters)
// si tiene parametros de tipo IO/Output retornar ese valor
if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor

// Obtiene un Valor a partir de un Procedimiento Almacenado, y sus parmetros.


public object TraerValorOutput(string procedimientoAlmacenado, params Object[]
args)
{
// asignar el string sql al command
var com = Comando(procedimientoAlmacenado);
// cargar los parametros del SP
CargarParametros(com, args);
// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del SP
foreach (IDbDataParameter par in com.Parameters)
// si tiene parametros de tipo IO/Output retornar ese valor
if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor
// Obtiene un Valor Escalar a partir de un Procedimiento Almacenado.
public object TraerValorOutputSql(string comadoSql)
{
// asignar el string sql al command
var com = ComandoSql(comadoSql);
// ejecutar el command
com.ExecuteNonQuery();
// declarar variable de retorno
Object resp = null;
// recorrer los parametros del Query (uso tipico envio de varias
sentencias sql en el mismo command)
foreach (IDbDataParameter par in com.Parameters)
// si tiene parametros de tipo IO/Output retornar ese valor
if (par.Direction == ParameterDirection.InputOutput || par.Direction
== ParameterDirection.Output)
resp = par.Value;
return resp;
} // end TraerValor

// 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

225 object[] args)


226
{
227
var com = Comando(procedimientoAlmacenado);
CargarParametros(com, args);
228
return com.ExecuteScalar();
229
} // end TraerValorEscalar
230
231
// Obtiene un Valor de una funcion Escalar a partir de un Query SQL
232
public object TraerValorEscalarSql(string comandoSql)
233
234
{
235
var com = ComandoSql(comandoSql);
return com.ExecuteScalar();
236
} // end TraerValorEscalarSql
237
238
239
#endregion
240
241
#region "Acciones"
242
protected abstract IDbConnection CrearConexion(string cadena);
243
protected abstract IDbCommand Comando(string procedimientoAlmacenado);
244
protected abstract IDbCommand ComandoSql(string comandoSql);
245
protected abstract IDataAdapter CrearDataAdapter(string
246
247 procedimientoAlmacenado, params Object[] args);
protected abstract IDataAdapter CrearDataAdapterSql(string comandoSql);
248
protected abstract void CargarParametros(IDbCommand comando, Object[] args);
249
250
// metodo sobrecargado para autenticarse contra el motor de BBDD
251
public bool Autenticar()
252
253
{
if (Conexion.State != ConnectionState.Open)
254
Conexion.Open();
255
return true;
256
}// end Autenticar
257
258
// metodo sobrecargado para autenticarse contra el motor de BBDD
259
public bool Autenticar(string vUsuario, string vPassword)
260
261
{
MUsuario = vUsuario;
262
263
MPassword = vPassword;
264
MConexion = CrearConexion(CadenaConexion);
265
266
MConexion.Open();
return true;
267
}// end Autenticar
268
269
270
// cerrar conexion
271
public void CerrarConexion()
272
273
{
if (Conexion.State != ConnectionState.Closed)
274
MConexion.Close();
275
276
}
277
// end CerrarConexion
278
279
280
// Ejecuta un Procedimiento Almacenado en la base.
281
public int Ejecutar(string procedimientoAlmacenado)
282
{ return Comando(procedimientoAlmacenado).ExecuteNonQuery(); } // end Ejecutar
283
284
// Ejecuta un query sql
285
public int EjecutarSql(string comandoSql)
286
{ return ComandoSql(comandoSql).ExecuteNonQuery(); } // end Ejecutar
287
288
//Ejecuta un Procedimiento Almacenado en la base, utilizando los parmetros.
289
public int Ejecutar(string procedimientoAlmacenado, params Object[] args)
290
291
{
292
var com = Comando(procedimientoAlmacenado);
293
CargarParametros(com, args);
294
var resp = com.ExecuteNonQuery();

for (var i = 0; i < com.Parameters.Count; i++)


295
296
{
297
var par = (IDbDataParameter)com.Parameters[i];
if (par.Direction == ParameterDirection.InputOutput || par.Direction
298
299 == ParameterDirection.Output)
300
args.SetValue(par.Value, i - 1);
}// end for
301
return resp;
302
} // end Ejecutar
303
304
305
306
#endregion
307
308
#region "Transacciones"
309
protected IDbTransaction MTransaccion;
310
protected bool EnTransaccion;
311
312
//Comienza una Transaccin en la base en uso.
313
public void IniciarTransaccion()
314
315
{
try
316
317
{
318
MTransaccion = Conexion.BeginTransaction();
EnTransaccion = true;
319
}// end try
320
finally
321
{ EnTransaccion = false; }
322
}// end IniciarTransaccion
323
324
325
//Confirma la transaccin activa.
326
public void TerminarTransaccion()
327
328
{
try
329
330
{ MTransaccion.Commit(); }
finally
331
332
{
MTransaccion = null;
333
EnTransaccion = false;
334
}// end finally
335
}// end TerminarTransaccion

//Cancela la transaccin activa.


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

#endregion
}// end class gDatos
}// end namespace
Nota: visto que el post, se est quedando muy largo, lo separar en partes e ir agrengando de a poco las otras clases y
luego las otras capas lgicas.

Como programar en n-Capas con C# SQL


Server (Parte 2)
Continuando con la segunda entrega de la programacin en n-Capas, (la primera lo pueden ver aqui). Hasta el
momento solo creamos una clase abstracta que servir de padre para las dems 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 algn momento, lo nico que deberamos hacer es agregar una clase semejante a sta con la
implementacin 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 lnea de cdigo en una clase superior que veremos en la
tercer entrega.
Para sta clase el nico uso que haremos ser del namspace System. Tambin 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 coleccin de hashtable para preservar por as decirlo los comandos ejecutados y
accederlos con mayor velocidad si ya fue ejecutado una vez.
1

static readonly System.Collections.Hashtable ColComandos = new


System.Collections.Hashtable();

El primer mtodo que tendr esta clase, es un mtodo sellado que sobreescibir el de su padre, y crear el
ConnectionString y lo retornor.
public override sealed string CadenaConexion
1
{
2
get
3
{
4
if (MCadenaConexion.Length == 0)
5
{
6
if (MBase.Length != 0 &amp;&amp; MServidor.Length != 0)
7
{
8
var sCadena = new System.Text.StringBuilder("");
9
sCadena.Append("data source=;");
10
sCadena.Append("initial catalog=;");
11
sCadena.Append("user id=;");
12
sCadena.Append("password=;");
13
sCadena.Append("persist security info=True;");
14
sCadena.Replace("", Servidor);
15
sCadena.Replace("", Base);
16
sCadena.Replace("", Usuario);
17
sCadena.Replace("", Password);
18
19
return sCadena.ToString();
20
}
21
throw new Exception("No se puede establecer la cadena de
22
conexin en la clase SQLServer");
23
}
24
return MCadenaConexion;
25
}// end get
26
set
27
{ MCadenaConexion = value; } // end set
28
}// end CadenaConexion

sta es una de las caracteristicas mas llamativas y tiles, que nos permitir cargar una cantidad n de parmetros,
a los SP que invocaremos
1 protected override void CargarParametros(System.Data.IDbCommand com, Object[] args)
2{

for (int i = 1; i < com.Parameters.Count; i++)


3
4
{
5
var p = (System.Data.SqlClient.SqlParameter)com.Parameters[i];
p.Value = i <= args.Length ? args[i - 1] ?? DBNull.Value : null;
6
7
}
8}
Luego para crear el Comando a ejecutar crearemos un mtodo que revisar en el hashTable anteriormente creado, si ya
se lo ha hecho o no. En caso que estemos dentro de una transaccin conectado, necesitamos crear una segunda
conexin para la lectura de los parmetros de Procedimiento almacenado
protected override System.Data.IDbCommand Comando(string procedimientoAlmacenado)
1 {
2
System.Data.SqlClient.SqlCommand com;
if (ColComandos.Contains(procedimientoAlmacenado))
3
com =
4
5 (System.Data.SqlClient.SqlCommand)ColComandos[procedimientoAlmacenado];
else
6
7
{
var con2 = new System.Data.SqlClient.SqlConnection(CadenaConexion);
8
con2.Open();
9
com = new System.Data.SqlClient.SqlCommand(procedimientoAlmacenado,
10
11 con2)
12
{ CommandType = System.Data.CommandType.StoredProcedure };
13
System.Data.SqlClient.SqlCommandBuilder.DeriveParameters(com);
14
con2.Close();
15
con2.Dispose();
ColComandos.Add(procedimientoAlmacenado, com);
16
}//end else
17
18
com.Connection = (System.Data.SqlClient.SqlConnection)Conexion;
com.Transaction = (System.Data.SqlClient.SqlTransaction)MTransaccion;
19
return com;
20
}// end Comando
Como no puedo sobrecargar un mtodo con la misma cantidad de parmetros que recibe, y del mismo tipo, me veo
obligado a crear un mtodo 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);
return com;
5
}// end Comando

Ahora crearemos la conexin a partir de la cadena de conexin


1 protected override System.Data.IDbConnection CrearConexion(string 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 mtodo es para las llamadas a
Procedimientos

1
2
3
4
5
6
7

protected override System.Data.IDataAdapter CrearDataAdapter(string


procedimientoAlmacenado, params Object[] args)
{
var da = new
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand)
Comando(procedimientoAlmacenado));
if (args.Length != 0)
CargarParametros(da.SelectCommand, args);
return da;
} // end CrearDataAdapter

La siguiente es casi los mismo que la anterior, pero para ejecucin de querys SQL

protected override System.Data.IDataAdapter CrearDataAdapterSql(string comandoSql)


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

Nada ms queda crear los constructores, creamos 4 sobrecargas del mismo segn lo que necesitemos ms
adelante.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public SqlServer()
{
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

El cdigo completo quedara as:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

using System;
namespace AccesoDatos
{
public class SqlServer : GDatos
{
/*
* Continuaremos con el mtodo Comando, procediendo de igual forma que en los
anteriores.
* En este caso, adems, implementaremos un mecanismo de preservacin de los
Comandos creados,
* para acelerar su utilizacin. Esto es, cada procedimiento que sea accedido,
se guardar
* en memoria hasta que la instancia del objeto se destruya. Para ello,
declararemos una variable
* como HashTable para la clase, con el modificador Shared (compartida) que
permite
* persistir la misma entre creaciones de objetos
*/
static readonly System.Collections.Hashtable ColComandos = new
System.Collections.Hashtable();

public override sealed string CadenaConexion


{
get
{
if (MCadenaConexion.Length == 0)
{
if (MBase.Length != 0 && MServidor.Length != 0)

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
10

{
var sCadena = new System.Text.StringBuilder("");
sCadena.Append("data source=<SERVIDOR>;");
sCadena.Append("initial catalog=<BASE>;");
sCadena.Append("user id=<USER>;");
sCadena.Append("password=<PASSWORD>;");
sCadena.Append("persist security info=True;");
sCadena.Append("user id=sa;packet size=4096");
sCadena.Replace("<SERVIDOR>", Servidor);
sCadena.Replace("<BASE>", Base);
sCadena.Replace("<USER>", Usuario);
sCadena.Replace("<PASSWORD>", Password);
return sCadena.ToString();
}
throw new Exception("No se puede establecer la cadena de conexin
en la clase DatosSQLServer");
}
return MCadenaConexion = CadenaConexion;
}// end get
set
{ MCadenaConexion = value; } // end set
}// end CadenaConexion

/*
* Agregue ahora la definicin del procedimiento CargarParametros, el cual
deber asignar cada valor
* al parmetro que corresponda (considerando que, en el caso de SQLServer,
el parameter 0
* siempre corresponde al return Value del Procedimiento Almacenado). Por
otra parte, en algunos casos,
* como la ejecucin de procedimientos almacenados que devuelven un valor como
parmetro de salida,
* la cantidad de elementos en el vector de argumentos, puede no corresponder
con la cantidad de parmetros.
* Por ello, se decide comparar el indicador con la cantidad de argumentos
recibidos, antes de asignar el valor.
* protected override void CargarParametros(System.Data.IDbCommand Com,
System.Object[] Args)
*/
protected override void CargarParametros(System.Data.IDbCommand com, Object[]
args)
{
for (int i = 1; i < com.Parameters.Count; i++)
{
var p = (System.Data.SqlClient.SqlParameter)com.Parameters[i];
p.Value = i <= args.Length ? args[i - 1] ?? DBNull.Value : null;
} // end for
} // end CargarParametros

/*
* En el procedimiento Comando, se buscar primero si ya existe el comando en
dicha Hashtable para retornarla
* (convertida en el tipo correcto). Caso contrario, se proceder a la
creacin del mismo,
* y su agregado en el repositorio. Dado que cabe la posibilidad de que ya
estemos dentro de una transaccin,
* es necesario abrir una segunda conexin a la base de datos, para obtener la
definicin de los parmetros
* del procedimiento Almacenado (caso contrario da error, por intentar leer
sin tener asignado el
* objeto Transaction correspondiente). Adems, el comando, obtenido por
cualquiera de los mecanismos
* debe recibir la conexin y la transaccin correspondientes (si no hay
Transaccin, la variable es null,
* y ese es el valor que se le pasa al objeto Command)
*/

0
10
1
10
2
10
3
10
4
10
5
10
6
10
7
10
8
10
9
11
0
11
1
11
2
11
3
11
4
11
5
11
6
11
7
11
8
11
9
12
0
12
1
12
2
12
3
12
4
12
5
12
6
12
7
12
8
12
9
13
0
13
1
13
2
13
3
13
4
13

protected override System.Data.IDbCommand Comando(string


procedimientoAlmacenado)
{
System.Data.SqlClient.SqlCommand com;
if (ColComandos.Contains(procedimientoAlmacenado))
com =
(System.Data.SqlClient.SqlCommand)ColComandos[procedimientoAlmacenado];
else
{
var con2 = new System.Data.SqlClient.SqlConnection(CadenaConexion);
con2.Open();
com = new System.Data.SqlClient.SqlCommand(procedimientoAlmacenado,
con2) { CommandType = System.Data.CommandType.StoredProcedure };
System.Data.SqlClient.SqlCommandBuilder.DeriveParameters(com);
con2.Close();
con2.Dispose();
ColComandos.Add(procedimientoAlmacenado, com);
}//end else
com.Connection = (System.Data.SqlClient.SqlConnection)Conexion;
com.Transaction = (System.Data.SqlClient.SqlTransaction)MTransaccion;
return com;
}// end Comando
protected override System.Data.IDbCommand ComandoSql(string comandoSql)
{
var com = new System.Data.SqlClient.SqlCommand(comandoSql,
(System.Data.SqlClient.SqlConnection)Conexion,
(System.Data.SqlClient.SqlTransaction)MTransaccion);
return com;
}// end Comando

/*
* Luego implementaremos CrearConexion, donde simplemente se devuelve una
nueva instancia del
* objeto Conexin de SqlClient, utilizando la cadena de conexin del objeto.
*/
protected override System.Data.IDbConnection CrearConexion(string
cadenaConexion)
{ return new System.Data.SqlClient.SqlConnection(cadenaConexion); }

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


mtodo Comando para crear el comando necesario.
protected override System.Data.IDataAdapter CrearDataAdapter(string
procedimientoAlmacenado, params Object[] args)
{
var da = new
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand)Comando(procedi
mientoAlmacenado));
if (args.Length != 0)
CargarParametros(da.SelectCommand, args);
return da;
} // end CrearDataAdapter
//Finalmente, es el turno de definir CrearDataAdapter, el cual aprovecha el
mtodo Comando para crear el comando necesario.
protected override System.Data.IDataAdapter CrearDataAdapterSql(string
comandoSql)
{
var da = new
System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand)ComandoSql(coma
ndoSql));
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

5 admita directamente la cadena de conexin completa.


* Los constructores se definen como procedimientos pblicos de nombre New.
13
*/
6
public SqlServer()
13
{
7
Base = "";
13
Servidor = "";
8
Usuario = "";
13
Password = "";
9
}// end DatosSQLServer
14
0
14
public SqlServer(string cadenaConexion)
1
{ CadenaConexion = cadenaConexion; }// end DatosSQLServer
14
2
14
public SqlServer(string servidor, string @base)
3
14
{
Base = @base;
4
14
Servidor = servidor;
}// end DatosSQLServer
5
14
6
public SqlServer(string servidor, string @base, string usuario, string
14
7 password)
14
{
Base = @base;
8
14
Servidor = servidor;
9
Usuario = usuario;
15
Password = password;
}// end DatosSQLServer
0
}// end class DatosSQLServer
15
1 }
15
2
15
3
15
4
15
5
15
6
15
7
15
8
15
9
16
0
16
1
16
2
16
3

1.
Marcelo
6 febrero 2013 at 16:42 #
El mejor artculo sobre programacin en capas en C# que he visto, til y muy didctico. Modifiqu el
cdigo de la clase SqlServer para acceder a Postgresql y funcion correctamente cuando implementas
query sql, pero siempre devuelve errores con procedimientos almacenados con parmetros. Es evidente
que Sql Server y Postgresql manejan esto de forma diferente, pero ya los solucion.

A continuacin transcribo el cdigo para trabajar con Postgresql (es necesario bajar el proveedor Npgsql
.)
Saludos
using System;
using System.Data;
using Npgsql;
using NpgsqlTypes;
namespace LsiDatos
{
public class ServidorPG : GDatos
{
static readonly System.Collections.Hashtable ColComandos = new System.Collections.Hashtable();
public override sealed string CadenaConexion
{
get
{
if (MCadenaConexion.Length == 0)
{
if (MBase.Length != 0 && MServidor.Length != 0)
{
var sCadena = new System.Text.StringBuilder();
sCadena.Append(Server=;);
sCadena.Append(Port=5432;);
sCadena.Append(User Id=;);
sCadena.Append(Password=;);
sCadena.Append(Database=;);
sCadena.Append(Encoding=UTF8);
sCadena.Replace(, Servidor);
sCadena.Replace(, Base);
sCadena.Replace(, Usuario);
sCadena.Replace(, Password);
return sCadena.ToString();
}
throw new Exception(No se pudo definir la cadena de conexin para la clase DatosPostgresql);
}
return MCadenaConexion = CadenaConexion;
}// end get
set
{ MCadenaConexion = value; } // end set
}// end CadenaConexion
protected override void CargarParametros(System.Data.IDbCommand com, Object[] args)
{
for (int i = 1; i <= com.Parameters.Count; i++)
{
var p = (Npgsql.NpgsqlParameter)com.Parameters[i -1];
p.Value = i <= args.Length ? args[i 1] ?? DBNull.Value : null;
} // end for
} // end CargarParametros

protected override System.Data.IDbCommand Comando(string procedimientoAlmacenado)


{
Npgsql.NpgsqlCommand com;
if (ColComandos.Contains(procedimientoAlmacenado))
com = (Npgsql.NpgsqlCommand)ColComandos[procedimientoAlmacenado];
else
{
var con2 = new Npgsql.NpgsqlConnection(CadenaConexion);
con2.Open();
com = new Npgsql.NpgsqlCommand(procedimientoAlmacenado, con2);
com.CommandType = CommandType.StoredProcedure;
Npgsql.NpgsqlCommandBuilder.DeriveParameters(com);
con2.Close();
con2.Dispose();
ColComandos.Add(procedimientoAlmacenado, com);
}//end else
com.Connection = (Npgsql.NpgsqlConnection)Conexion;
com.Transaction = (Npgsql.NpgsqlTransaction)MTransaccion;
return com;
}// end Comando
protected override System.Data.IDbCommand ComandoSql(string comandoSql)
{
var com = new
Npgsql.NpgsqlCommand(comandoSql,(Npgsql.NpgsqlConnection)Conexion,(Npgsql.NpgsqlTransactio
n)MTransaccion);
return com;
}// end Comando
protected override System.Data.IDbConnection CrearConexion(string cadenaConexion)
{
return new Npgsql.NpgsqlConnection(cadenaConexion);
}
protected override System.Data.IDataAdapter CrearDataAdapter(string procedimientoAlmacenado,
params Object[] args)
{
var da = new
Npgsql.NpgsqlDataAdapter((Npgsql.NpgsqlCommand)Comando(procedimientoAlmacenado));
if (args.Length != 0)
CargarParametros(da.SelectCommand, args);
return da;
} // end CrearDataAdapter
protected override System.Data.IDataAdapter CrearDataAdapterSql(string comandoSql)
{
var da = new Npgsql.NpgsqlDataAdapter((Npgsql.NpgsqlCommand)ComandoSql(comandoSql));
return da;
} // end CrearDataAdapterSql
public ServidorPG()
{
Base = "";
Servidor = "";
Usuario = "";
Password = "";
}

public ServidorPG(string cadenaConexion)


{ CadenaConexion = cadenaConexion; }// end DatosSQLServer
public ServidorPG(string servidor, string @base)
{
Base = @base;
Servidor = servidor;
}// end DatosSQLServer
public ServidorPG(string servidor, string @base, string usuario, string password)
{
Base = @base;
Servidor = servidor;
Usuario = usuario;
Password = password;
}// end DatosSQLServer
}
}

Como programar en n-Capas con C# (Parte 3)


Esta es la tercer entrega, probablemente ser la ms corta pero no la ltima an. 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.
Tambin daremos por terminada la capa de Acceso a Datos, entonces as no mezclamos el cdigo y ser ms fcil
seguirlo posteriormente. Tambin pertenecer al namespace AccesoDatos. Lo llamo conexin por que es la clase con
las otras capas interactuaran en modo directo.

Para ello creamos un objeto esttico 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 cdigo, donde el objeto GDatos del tipo GDatos, sea SqlServer, Oracle u otro
motor que codifiquemos. Podemos hacerlo con ODBC, OleDB para conexiones genricas. No les parece
grandioso que solo deban tocar parte de una lnea de cdigo para portar la App a cualquier otro motor de Base
de Datos?
namespace AccesoDatos
1
{
2
public class Conexion
3
{
4
public static GDatos GDatos;
5
public static bool IniciarSesion(string nombreServidor, string baseDatos,
6
string usuario, string password)
7
{
8
GDatos = new SqlServer(nombreServidor, baseDatos, usuario, password);
9
return GDatos.Autenticar();
10
} //fin inicializa sesion
11
12
public static void FinalizarSesion()
13
{
14
GDatos.CerrarConexion();
15
} //fin FinalizaSesion
16
17
}//end class util
18
}//end namespace

En la siguiente entrega veremos como crear la capa de Negocio y vincularla a estas capas de Acceso a Datos.

Como programar en n-Capas con C# (Parte 4)


En la cuarta entrega veremos una capa nueva, la capa de Negocios, como ya dije en los artculos anteriores
hemos dado por terminado la capa de Acceso a Datos.
Aqu es donde diremos como debe procesarse la informacin. Para este caso no voy a crear una estructura
compleja de BBDD ya que el cdigo de C# ya lleva bastante, pero reflejar claramente como se usa sta capa
en casos ms complejos.
Primeramente crearemos una tabla realmente simple, compuesta por 3 campos llamada Cliente.
1
2
3
4
5
6
7
8
9

CREATE TABLE [dbo].[Cliente](


[IdCliente] [tinyint] NOT NULL,
[DescripcionCliente] [varchar](50) NOT NULL,
[Siglas] [varchar](15) NULL,
CONSTRAINT [PK_Cliente] PRIMARY KEY CLUSTERED
(
[IdCliente] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Preferentemente trabajaremos con procedimientos almacenados como Zeus manda. Para crear, listar y buscar
tendremos 3 distintos procedimientos.
1
2
3
4
5
6
7
8
1
2
3
4
5

CREATE PROCEDURE [dbo].[insCliente]


@id as int,
@nombre varchar(50),
@sigla varchar(15)
AS
BEGIN
insert into Cliente values(@id, @nombre, @sigla);
END
CREATE PROCEDURE [dbo].[slcCliente]
AS
BEGIN
select * from Cliente;
END

1
2
3
4
5

CREATE PROCEDURE [dbo].[slcCliente]


AS
BEGIN
select * from Cliente;
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
aplicacin, es lo que vena escribiendo en las primeras partes del tutorial.
Del lado de la aplicacin utilizaremos el mapeo ORM (doy por sabido que conocen que esto, sino pueden
investigarlo aqu), ya que es una de la prcticas ms utilizadas y probadas que ahorran cdigo y ayudan a
cumplir con varios conceptos de la OOP. Dentro del mismo proyecto de biblioteca de clases, que estamos
teniendo en nuestra solucin (hasta ahora no hemos creado ningn tipo de interfaz de usuario). Creo
conveniente crear una carpeta en la raz del proyecto llamada Orm, justamente para seguir la convencin (trato
de seguirlas todas, derrepente se me escapa alguna, sabrn disculparme :S). Al crear esta carpeta, y crear clases
dentro de ella tambin 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 representarn 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 cdigo que hemos venido escribiendo, veanlo en accin en los 3
mtodos que crearemos, uno para dar de Alta, un registro de cliente, en la BBDD, otro para buscar, y otro para
listar todos.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

{ Conexion.GDatos.Ejecutar("insCliente", cliente.IdCliente, cliente.Descripcion,


cliente.Sigla, 3); }
public DataTable Listar()
{ return Conexion.GDatos.TraerDataTable("slcCliente"); }
public Cliente Listar(int idCliente)
{
var cliente = new Cliente();
DataTable dt = Conexion.GDatos.TraerDataTable("slcClienteById", idCliente);
if (dt.Rows.Count > 0)
{
cliente.IdCliente = Convert.ToInt16(dt.Rows[0][0]);
cliente.Descripcion = Convert.ToString(dt.Rows[0][1]);
cliente.Sigla = Convert.ToString(dt.Rows[0][2]);
return cliente;
}
return null;
}

En esta clase, deben crear todos los mtodos, 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 accin en s. Como ven estamos aplicando sobrecarga de mtodos en sta clase (no lo
confundan con polimorfismo).
La clase completa quedara as:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

using System;
using System.Data;
namespace AccesoDatos.Orm
{
public class Cliente
{
// lista de atributos con setters y getters
public short IdCliente { get; set; }
public string Descripcion { get; set; }
public string Sigla { get; set; }
// mtodos
public void Crear(Cliente cliente)
{ Conexion.GDatos.Ejecutar("insCliente", cliente.IdCliente,
cliente.Descripcion, cliente.Sigla, 3); }
public DataTable Listar()
{ return Conexion.GDatos.TraerDataTable("slcCliente"); }
public Cliente Listar(int idCliente)
{
var cliente = new Cliente();
DataTable dt = Conexion.GDatos.TraerDataTable("slcClienteById", idCliente);
if (dt.Rows.Count > 0)
{
cliente.IdCliente = Convert.ToInt16(dt.Rows[0][0]);
cliente.Descripcion = Convert.ToString(dt.Rows[0][1]);
cliente.Sigla = Convert.ToString(dt.Rows[0][2]);

return cliente;
}
return null;

32
33
34
35
36

}
}
}

Con sto vemos como se implementa la capa de negocios. En la siguiente entrega veremos ya la capa de
Presentacin, en lo posible, utilizares todas las clases hechas ya incluida sta en una aplicacin WinForm y
otra WebForm, vern que no slo es portable a cualquier proveedor de datos, sino que es portable a distintas
plataformas de ejecucin.

Como programar en n-Capas con C# (Parte 5)


Con sta entrega cumpliremos con la capa de Presentacin, 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 harn as, el botn 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 implementacin
interesante sera agregarle un identity a la tabla cliente, pero para el ejemplo ya servir esto. Para esto
crearemos 2 proyectos ms dentro de nuestra solucin, uno ser una aplicacin Windows Form con Visual C#,
y la otra un sitio Web.
El cdigo que pondremos en el botn 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 aplicacin, sino solo sus valores de conexin en memoria.
1 try
2{
3
4

Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "123");


MessageBox.Show(String.Format("{0}", "Se conecto exitosamente"));

5
6
7
8
9

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

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 mtodo crear enviandole el nuevo objeto cliente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

var cliente = new Cliente();


try
{
cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);
cliente.Descripcion = txtDescripcionCliente.Text;
cliente.Sigla = txtSiglaCliente.Text;
cliente.Crear(cliente);
MessageBox.Show(String.Format("{0}", "Se creo exitosamente"));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

Luego de esto escribimos el cdigo de listado de todos los clientes, y lo cargamos en la grilla. Se dan cuenta
que necesitamos muy pocas lineas de cdigo en la capa de Presentacin, y que no tiene una dependencia
de la BBDD?. Si se dan cuenta, cuando vamos a devolver muchos registros no podemos utilizar un montn de
instancias de Cliente, sino simplemente devolvemos un DataTable, DataSet o DataReader.
1
2
3
4
5
6
7
8
9

var cliente = new Cliente();


try
{
grilla.DataSource = cliente.Listar();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

Finalmente el cdigo de bsqueda quedara algo asi. Cmo sabemos que si buscamos por la PK, siempre nos
devolver un slo registro, lo podemos crear como un objeto Cliente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

var cliente = new Cliente();


try
{
cliente = cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
if (cliente != null)
{
txtDescripcionCliente.Text = cliente.Descripcion;
txtSiglaCliente.Text = cliente.Sigla;
}
else
{ MessageBox.Show(String.Format("{0}", "No existia el cliente buscado")); }
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}

El cdigo completo quedara as:


1 using System;
2 using System.Windows.Forms;
3 using AccesoDatos;

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

using AccesoDatos.Orm;
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnConectar_Click(object sender, EventArgs e)
{
try
{
Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "***");
MessageBox.Show(String.Format("{0}", "Se conecto exitosamente"));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnListar_Click(object sender, EventArgs e)
{
var cliente = new Cliente();
try
{
grilla.DataSource = cliente.Listar();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnCrear_Click(object sender, EventArgs e)
{
var cliente = new Cliente();
try
{
cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);
cliente.Descripcion = txtDescripcionCliente.Text;
cliente.Sigla = txtSiglaCliente.Text;
cliente.Crear(cliente);
MessageBox.Show(String.Format("{0}", "Se creo exitosamente"));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void btnBuscarById_Click(object sender, EventArgs e)
{
var cliente = new Cliente();
try
{
cliente = cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
if (cliente != null)
{
txtDescripcionCliente.Text = cliente.Descripcion;
txtSiglaCliente.Text = cliente.Sigla;
}
else
{ MessageBox.Show(String.Format("{0}", "No existia el cliente
buscado")); }

74
75
76
77
78
79
80

}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Cliente.aspx.cs"


Inherits="Cliente" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="frmCliente" runat="server">
<div>
<asp:Label ID="Id" runat="server" Text="Id: "></asp:Label>
<asp:TextBox ID="txtIdCliente" runat="server"></asp:TextBox>
<br />
<asp:Label ID="Label1" runat="server" Text="Descripcion: "></asp:Label>
<asp:TextBox ID="txtDescripcionCliente" runat="server"></asp:TextBox>
<asp:TextBox ID="txtuser" runat="server">sa</asp:TextBox>
<br />
<asp:Label ID="Label2" runat="server" Text="Siglas: "></asp:Label>
<asp:TextBox ID="txtSiglasCliente" runat="server"></asp:TextBox>
<br />
<hr style="margin-top: 0px; margin-bottom: 0px" />
<asp:GridView ID="grilla" runat="server" CellPadding="4" ForeColor="#333333"
GridLines="None">
<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" />
<SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333"
/>
<SortedAscendingCellStyle BackColor="#F5F7FB" />
<SortedAscendingHeaderStyle BackColor="#6D95E1" />
<SortedDescendingCellStyle BackColor="#E9EBEF" />
<SortedDescendingHeaderStyle BackColor="#4870BE" />
</asp:GridView>
<asp:Label ID="Label3" runat="server" Text="Estado:"></asp:Label>
<asp:Label ID="lblEstado" runat="server"></asp:Label>
<br />
<asp:Button ID="btnConectar" runat="server" Text="Conectar"
onclick="btnConectar_Click" />
<asp:Button ID="btnCrear" runat="server" onclick="btnCrear_Click"
Text="Crear" />
<asp:Button ID="btnListar" runat="server" onclick="btnListar_Click"
Text="Listar" />
<asp:Button ID="btnListarById" runat="server" onclick="btnListarById_Click"
Text="Listar By Id" />
<br />
</div>
</form>
</body>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

</html>
using System;
// using AccesoDatos;
public partial class Cliente : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnConectar_Click(object sender, EventArgs e)
{
try
{
lblEstado.Text = "";
AccesoDatos.Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "***");
lblEstado.Text = String.Format("{0}", "Se conecto exitosamente");
}
catch (Exception ex)
{
lblEstado.Text = String.Format(ex.Message);
}
}
protected void btnListar_Click(object sender, EventArgs e)
{
var cliente = new AccesoDatos.Orm.Cliente();
try
{
grilla.DataSource = cliente.Listar();
grilla.DataBind();
lblEstado.Text = String.Format("{0}", "Se listo exitosament");
}
catch (Exception ex)
{
lblEstado.Text = ex.Message;
}
}
protected void btnCrear_Click(object sender, EventArgs e)
{
var cliente = new AccesoDatos.Orm.Cliente();
try
{
cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);
cliente.Descripcion = txtDescripcionCliente.Text;
cliente.Sigla = txtSiglasCliente.Text;
cliente.Crear(cliente);
lblEstado.Text = String.Format("{0}", "Se creo exitosamente");
}
catch (Exception ex)
{
lblEstado.Text = ex.Message;
}
}
protected void btnListarById_Click(object sender, EventArgs e)
{
var cliente = new AccesoDatos.Orm.Cliente();
try
{
cliente = cliente.Listar(Convert.ToInt16(txtIdCliente.Text));
if (cliente != null)
{
txtDescripcionCliente.Text = cliente.Descripcion;
txtSiglasCliente.Text = cliente.Sigla;
}
else
{ lblEstado.Text = String.Format("{0}", "No existia el cliente buscado"); }
}

70
71
72
73
74
75 }

catch (Exception ex)


{
lblEstado.Text = ex.Message;
}
}

Si pueden agregar/aportar mejoras y funcionalidad a estas clases, sern bienvenidas


creo un MVP de Microsoft y yo le agregue algunas funcionalidades..

de hecho ste cdigo lo

Вам также может понравиться