post icon

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

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 :P

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
2
using System;
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
2
3
4
5
6
7
8
9
10
#region "Declaración de Variables"
 
protected string MServidor = "";
protected string MBase = "";
protected string MUsuario = "";
protected string MPassword = "";
protected string MCadenaConexion = "";
protected IDbConnection MConexion;
 
#endregion

Lo siguiente por hacer es muy sencillo, crear los setters y getters de nuestros atributos anteriormente definidos:

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
#region "Setters y Getters"
 
// 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 conexión 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 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
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
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
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
#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;
} // end TraerDataset
 
//Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parámetros.
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 parámetros.
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 parámetros.
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)
		// 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 parámetros.
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 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
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
#region "Acciones"
 
protected abstract IDbConnection CrearConexion(string cadena);
protected abstract IDbCommand Comando(string procedimientoAlmacenado);
protected abstract IDbCommand ComandoSql(string comandoSql);
protected abstract IDataAdapter CrearDataAdapter(string procedimientoAlmacenado, params Object[] args);
protected abstract IDataAdapter CrearDataAdapterSql(string comandoSql);
protected abstract 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 parámetros. 
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;
} // 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
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 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

El código completo lo pueden ver aqui:

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
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
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
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
using System;
using System.Data;
 
namespace AccesoDatos
{
    public abstract class GDatos
    {
        #region "Declaración de Variables"
 
        protected string MServidor = "";
        protected string MBase = "";
        protected string MUsuario = "";
        protected string MPassword = "";
        protected string MCadenaConexion = "";
        protected IDbConnection MConexion;
 
        #endregion
 
        #region "Setters y Getters"
 
        // 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 conexión 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
 
        #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;
        } // end TraerDataset
 
 
        //Obtiene un DataSet a partir de un Procedimiento Almacenado y sus parámetros. 
        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 parámetros. 
        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 parámetros. 
        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)
                // 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 parámetros. 
        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
 
        #region "Acciones"
 
        protected abstract IDbConnection CrearConexion(string cadena);
        protected abstract IDbCommand Comando(string procedimientoAlmacenado);
        protected abstract IDbCommand ComandoSql(string comandoSql);
        protected abstract IDataAdapter CrearDataAdapter(string procedimientoAlmacenado, params Object[] args);
        protected abstract IDataAdapter CrearDataAdapterSql(string comandoSql);
        protected abstract 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 parámetros. 
        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;
        } // 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

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 lógicas.
Parte 2 | Parte 3 | Parte 4 | Parte 5

Comentarios desde Facebook:

  1. avatar
    luisesp Google Chrome Windows
    7 junio 2014 at 12:14 #

    Hola, seria interesante y útil que hicieras un ejemplo con java, existen muchos ejemplos de desarrollo en capas, pero la que presentas me pareció excelente.

  2. avatar
    Jeferson PERU Google Chrome Windows
    12 julio 2012 at 00:35 #

    Amigo Como puedo iniciar una session y que en el siguiente formulario diga Bienvenido “Jeferson” y sus respectivos links cerrar session
    y tambien los permisos que tiene cada usuario .
    Yo pensaba hacerlo asi:
    1.cuando digite mi usuario y contraseña ,presiono aceptar
    2.Me mande al formulario principal(claro solo con los permisos delegados en la base de datos) http://jumbofiles.com/pnqwbepw8pmt/formprincipal.jpg.html
    claro que si soy el admin me aparesca todas las opciones habilitadas y a los que no como podria inhabilitar y que aparesca el bienvenido para cada usuario que inicie sesion.
    de antemano te doy gracias

  3. avatar
    GeekZero PARAGUAY Google Chrome Windows
    26 enero 2012 at 14:21 #

    prueba agregando ésto al inicio de tu SP

    SET NOCOUNT OFF;
  4. avatar
    oscar Mozilla Firefox Windows
    25 enero 2012 at 23:44 #

    Excelente Tutorial, Tengo una pequeña consulta

    Porque en la funcion public int Ejecutar(string procedimientoAlmacenado, params Object[] args) cuando yo afecto la tabla con un DELETE en un SP la variable var resp = com.ExecuteNonQuery(); siempre me trae -1 pero efectivamente la DB si tuvo filas afectas. Que no estoy haciendo bien que siempre me trae -1?

    Saludes

  5. avatar
    Osman GUATEMALA Mozilla Firefox Windows
    30 noviembre 2011 at 16:01 #

    Excelente Manual, estoy tratando de mejorar en estos temas y este material está muy bien explicado y muy completo en su codigo fuente.

    Muchisimas gracias por tomarte el tiempo de realizarlo.

    Saludos desde Guatemala.

  6. avatar
    Jose Bullon PERU Internet Explorer Windows
    5 agosto 2011 at 11:40 #

    Seria bueno explicar con ejemplos de codigo la creacion de las capas con conexiones en Oracle.

  7. avatar
    JuanPa PERU Mozilla Firefox Windows
    29 junio 2011 at 23:40 #

    hola ke tal…
    aun no me respondes, la anterior duda, debes estar algo ocupado…
    y venia con otra duda…
    tengo mi clase de entidad de Clientes
    en la cual pongo un metodo

    <pre lang="csharp">
    public String Buscar(string nom)
    {
      return Conexion.GDatos.TraerDataTable("search_Clientes", nom).ToString();
    }
    </pre>
    

    como podras deducir tengo un procedimiento almacenado ke acepta un parametro @cli el cual se lo paso por “nom”

    en la clase donde va el formulario o la presentacion

    <pre lang="csharp">
    private void txtBuscar_TextChanged(object sender, EventArgs e)
    {
      try
      {         
        if (cliente != null)
        {
           string nom = this.txtBuscar.Text;
           this.grdClientes.DataSource = cliente.Buscar(nom);
           this.grdClientes.DataSource = cliente.Listar();
        }
        else
        { 
           MessageBox.Show(String.Format("{0}", "No existia el cliente buscado")); 
        }
      }
      catch (Exception ex)
      {
        MessageBox.Show("Error: \n" + ex.Message, "Mensaje");
      }
    }
    </pre>
    
    • avatar
      JuanPa PERU Mozilla Firefox Windows
      30 junio 2011 at 00:15 #

      ups… bueno resulta ke me da un error
      “Index was outside the bounds of the array”

      puedo hacerlo de otra manera?

  8. avatar
    JuanPa PERU Mozilla Firefox Windows
    16 junio 2011 at 01:06 #

    Hola ke tal… tu articulo es lo mejor ke pude encontrar en la red …me esta sirviendo bastante… para conocer lo recondito de a bases de datos se refiere ya ke recien voy en la primera parte. Pero asi como me sirve tambien me confunde…
    tengo preguntas …ufff… MIL!
    Recien estoy empezando con esto de la programacion en capas… y bueno lo de los get/set lo entiendo, algo… lo ke no entiendo porke todos tienen get-set a excepcion de la CadenadeConexion… no deberia devolver algo?… tambien porke es abstracto?

    Con respecto a las funciones ke declaras… me imagino ke es fruto del tiempo programando para saber como es ke actuaran ciertas cosas, me pregunto porke declaras la respuesta (resp) en la funcion TraerValorOutput() como un objeto y no como cualkier cadena dices ke funciona con SPs ke tengan variables de tipo output… como es eso?

    otra: porke dices ke es valor escalar? un valor escalar no se refiere a un unico valor y va con ExecuteScalar()? y sin embargo lo ejecutas con ExecuteNonQuery()

    Y por ultimo ke son eso de las transacciones?

    Para novatos como es bastante complicado entenderlo… kiza me
    recomiendes algo para leer…

    Sin nada mas con ke molestarlo me despido, esperando su pronta respuesta…

    Muchas gracias. Es tremendo ^^,

  9. avatar
    Javi SPAIN Mozilla Firefox Windows
    7 abril 2011 at 15:25 #

    Hola, tengo una pregunta sobre el código. Nunca se usan las transacciones?

    No estoy muy seguro pero creo que el valor de MTransaccion es null.

    Un saludo. Fantástico aporte.

    • avatar
      GeekZero PARAGUAY Google Chrome Windows
      12 mayo 2011 at 11:14 #

      Hola Javi, por este comentario me decías? Para usar las transacciones tienes que agregar éstas lineas, sino las consultas no serán transacciones y verás con valores null:

      1
      2
      3
      
      conexion.GDatos.IniciarTransaccion();
      conexion.GDatos.AbortarTransaccion();
      conexion.GDatos.TerminarTransaccion();
      • avatar
        Miguel PERU Internet Explorer Windows
        20 junio 2011 at 13:49 #

        Me podrias explicar con un ejemplo como implementar al insertar un registro “las transacciones”, he aumentado las lineas que indicas pero no logro hacer que funcione

        Saludos

      • avatar
        Byron HONDURAS Mozilla Firefox Windows
        4 julio 2011 at 19:03 #

        Gracias por compartir tu conocimiento, me ha servido mucho, quiero preguntarte, si el inicio de la transaccion lo hago en la capa de negocio x ejemplo antes de
        conexion.GDatos.IniciarTransaccion()
        conexion.Gdatos.Ejecutar(Procedimieto) o antes de llamarlo en la capa de presentacion asi:
        conexion.GDatos.IniciarTransaccion()
        cliente.crear(cliente) Gracias de antemano

      • avatar
        Edu PERU Google Chrome Windows
        14 agosto 2014 at 10:59 #

        Hola

        por favor podrias decirme donde se debe agregar las transacciones.

        Gracias:smile:

  10. avatar
    Ciro Pacheco COLOMBIA Mozilla Firefox Windows
    6 abril 2011 at 09:56 #

    Cordial saludo;

    Muchas gracias por los desarrolladores de este Tutorial, me han ayudado mucho en mis inicios con C Sharp y Firebird, eternamente Agradecido, el Dios de los programadores os Bendiga.

    Yo adapte la clase firebird para conectarme con ODBC, y funciona…

  11. avatar
    Moldun CHILE Internet Explorer Windows
    12 enero 2011 at 13:28 #

    Excelente !!!! estoy empezando con C# y esto me esta sirviendo de una manera fenomeno!!!!

    TE PASASTE!!!!

    • avatar
      Angel MEXICO Google Chrome Windows
      10 febrero 2011 at 02:32 #

      Neta que si esta fenomenal, espero ser tan gradioso como tu algun dia, milllll gracias

  12. avatar
    Julian ARGENTINA Google Chrome Windows
    26 diciembre 2010 at 16:34 #

    GeekZero: No te lo tomes como una critica , me parece buenisimo tu articulo, pero asi como vos fuiste muy solidario publicando un articulo explicando esto, me parece una forma de ayudar marcarte lo que no me cierra.
    Lo hago con la mas buena intencion del mundo, me pareceria egoista ver algun error y quedarme callado, por eso estoy compartiendo con vos mis opiniones.
    Aparte todos tenemos errores, es muy comun confundirse en una porcion de codigo , no creo que sea nada que te desmerezca lo que te comento.

    Con respecto a la parte 3 , vos pusiste:

    public static GDatos GDatos;
    GDatos = new SqlServer(nombreServidor, baseDatos, usuario, password);

    A mi no me funciono de esa manera, por que estas intentando instanciar una clase abstracta (que no es posible).
    De la manera que si me funciono es poniendo:

    public static SqlServer GDatos;
    GDatos = new SqlServer(nombreServidor, baseDatos, usuario, password);

    Saludos!

    • avatar
      GeekZero PARAGUAY Google Chrome Windows
      26 diciembre 2010 at 19:13 #

      Excelente me parecen las criticas constructivas, es cuando mas se aprende siempre. Ojala hubieran mas lectores asi :D. Lo que no me quedo muy claro es porque dices que se intenta instanciar de la clase GDatos? Es cierto ella es abstracta y no se puede instanciar, pero recuerda que las instancias se hacen con la sentencia new. Y en esta linea no se está instanciando GDatos, sino se esta instanciando SqlServer en un objeto GDatos:

      1
      2
      
      public static GDatos GDatos;
      GDatos = new SqlServer(nombreServidor, baseDatos, usuario, password);

      Porque es posible esto? Como SqlServer hereda de GDatos se puede hacer un UpCasting. El mismo consiste en instanciar una clase heredada en el objeto padre de sí mismo. Si fuera al revés se estaría haciendo un DownCasting.
      Te pondré un ejemplo sencillo, creamos una clase abstracta llamada Persona de este modo:

      1
      2
      3
      4
      5
      6
      7
      8
      
      namespace up_down_cast
      {
          abstract class Persona
          {
              public int Doc { get; set; }
              public string Nombres { get; set; }
          }
      }

      Luego otra clase llamada medico, que hereda de Persona

      1
      2
      3
      4
      5
      6
      7
      
      namespace up_down_cast
      {
          class Medico : Persona
          {
              public int RegistroMedico { get; set; }
          }
      }

      Si quisieramos utilizar normalmente esta clase hariamos algo asi:

      1
      2
      3
      4
      5
      
      Medico medico = new Medico();
      medico.Doc = 1222;
      medico.Nombres = "Jose Vera";
      medico.RegistroMedico = 434;
      MessageBox.Show(medico.Nombres + @" " + medico.Doc + @" " + medico.RegistroMedico);

      Pero tambien podemos crear de un medico, una persona (ya que lo es en el fondo, no?).

      1
      2
      3
      4
      
      Persona persona = new Medico();
      persona.Doc = 1222;
      persona.Nombres = "Jose Vera";
      MessageBox.Show(persona.Nombres + @" " + persona.Doc);

      Lo que no podriamos hacer es esto (tal como dices):

      1
      
      Persona persona = new Persona();

      Haciendo la equivalencia, Persona = GDatos (clase abstracta) y Medico = SqlServer (clase instanciable).
      Ahora, si te dio un error sería bueno saber que decía el mismo. O puedes subir tus clases para verlas, que con gusto lo haría.

      • avatar
        Julian ARGENTINA Google Chrome Windows
        26 diciembre 2010 at 20:47 #

        GeekZero:
        no sabia lo de upcasting y downcasting. Ahora que revise bien mi codigo , no me dejaba hacer eso porque yo le habia agregado un metodo a mi clase heredada el cual no estaba declarado en la clase abstracta (eso me pasa por no leer con atencion los errores)
        Ahora que declare el metodo en la clase abstracta me deja hacer el upCasting.
        Ya mi aplicacion esta funcionando en 3 capas gracias a tu articulo :D Gracias!.
        Saludos

      • avatar
        Ary CUBA Mozilla Firefox Windows
        5 marzo 2013 at 14:28 #

        Hola, estoy trabajando en un aplicación, que tiene como objetivo el desarrollo de un compilador a bytecodes, he separado la logica de funcionamiento en dos módulos, uno para realizar la fase de análisis(chequeo léxico, sintáctico y semántico) y otro para realizar la fase de síntesis(generación de código intermedio y optimización), mi pregunta es la siguiente, puedo afirmar que estoy utilizando una arquitectura en N capas??

  13. avatar
    Julian ARGENTINA Google Chrome Windows
    25 diciembre 2010 at 14:25 #

    Buenas,
    No entiendo por que el metodo Autenticar devuelve siempre true.

    Saludos!

    • avatar
      GeekZero PARAGUAY Google Chrome Windows
      25 diciembre 2010 at 15:15 #

      Es porque este dentro de este getter

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      
      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

      ya se hace la apertura de la conexion. Si pruebas agregando un breakpoint en la pregunta sobre el estado de la conexion lo veras..

      • avatar
        Julian ARGENTINA Google Chrome Windows
        26 diciembre 2010 at 14:46 #

        Claro, pero yo me preguntaba para que es booleana la funcion si siempre va a devolver true .. igualmente no afecta en nada al funcionamiento.
        Ah, y tambien fijate que en la parte 3 estas tratando de instanciar la clase Gdatos que es abstracta ..

        Me sirvio mucho tu articulo , gracias.

        • avatar
          GeekZero PARAGUAY Google Chrome Windows
          26 diciembre 2010 at 15:00 #

          Asi mismo el funcionamiento no se ve afectado, puse de ambas maneras para que cada uno utilice el modo que mas le gusta. Personalmente elimimo la apertura en el getter.

          Con respeto a GDatos, es cierto existe una clase abstracta llamada así, pero también he creado un objeto estatico llamada GDatos del tipo GDatos.

          Como siempre digo, mi código puede contener errores o puedes encontrar una mejor solucion, si lo haz hecho sería bueno que lo compartas con todos :D

          Saludos..

  14. avatar
    saulos PERU Mozilla Firefox Windows
    17 diciembre 2010 at 16:12 #

    muy bueno el tutorial, en realidad es dificil encontrar algo bueno sobre la arquitectura de 3 capas

  15. avatar
    DIOCaraballo PARAGUAY Mozilla Firefox Windows
    15 agosto 2010 at 20:58 #

    perdón, ahora ví que está en la parte de Trackbacks/Pingbacks xD

    • avatar
      GeekZero PARAGUAY Google Chrome Windows
      15 agosto 2010 at 22:18 #

      El sistema de Entradas Relacionadas que estoy usando, solo relaciona con los articulos mas viejos a su creacion, debería expandir la funcionalidad de eso..

      En el PB/TB no aparecen todos, de igual manera coloque al final del artículo los enlaces, en un rato más agrego las referencias en las otras partes..

  16. avatar
    DIOCaraballo PARAGUAY Mozilla Firefox Windows
    15 agosto 2010 at 20:56 #

    estaría bueno que entre los artículos relacionados esté la continuación de éste post =]

Trackbacks/Pingbacks

  1. Programación en 3 Capas con C# - Parte 5 | DevTroce.com WordPress - 17 agosto 2010

    […] de hecho éste código lo creo un MVP de Microsoft y yo le agregue algunas funcionalidades.. Parte 1 | Parte 2 | Parte 3 | Parte 4 Edit […]

  2. Ejecutar código VisualBasic .Net en de SQL Server | DevTroce.com WordPress - 25 julio 2010

    […] los últimos artículos estábamos escribiendo contenido destrozador sobre SQL Server & .Net, y me hizo pensar por que no hacer un MIX de ambas cosas en un sólo post para no perder la racha […]

  3. Programación en 3 Capas con C# - Parte 4 | DevTroce.com WordPress - 13 julio 2010

    […] entregas anteriores las pueden encontrar aquí: 1, 2 y 3. Con ésto vemos como se implementa la capa de negocios. En la siguiente entrega veremos ya […]

  4. Programación en 3 Capas con C# | DevTroce.com WordPress - 12 julio 2010

    […] parte 1 y 2 sobre los […]

  5. Tweets that mention Programación en 3 Capas con C# | DevTroce.com -- Topsy.com UNITED STATES - 12 julio 2010

    […] This post was mentioned on Twitter by cleonati, DevTroce. DevTroce said: Como programar en n-Capas con C# (Parte 1): Este es un tema polémico del que se habla mucho y… http://goo.gl/fb/LCqoC […]

  6. Programacion en 3 Capas con C# - SQL Server | DevTroce.com WordPress - 12 julio 2010

    […] 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 […]

Responder