Este es un tema polémico del que se habla mucho y nada, digo que se habla mucho porque al buscar algo de información en Internet, uno se da cuenta, que esta plagado de sitios donde preguntan como aplicar programación en 3 capas, o N-Capas, pero en muy pocos lugares se responde con algo cierto y concreto, la mayoría hacen referencia a libros gordos que tardarías en leer semanas (no estoy en contra de la lectura, es un proceso largo nada más y casi todos buscamos aprenderlo un poco más rápido). Este artículo también será bastante largo y me aventuro a decir que me tomará varias noches escribirlo completamente, pero no será nada comparado con un libro con un lomo de 15 centímetros
La primer gran confusión que noto, es que la mayoría no sabe diferenciar entre los conceptos
1. Arquitectura de 3 capas: se basa más bien en como será construido el entorno, una manera de decirlo en romper el clásico concepto Cliente-Servidor para introducir conceptos como Back End (Base de Datos), Middleware (Servidor de Aplicaciones), Front End (Interfaz de Usuario). Este es un concepto grande que no veremos ahora, pero lo remarco para hacer entender que no tiene nada que ver con la programación en capas. Se acerca más a un concepto físico.
2. Programación en 3 (n) capas: este es el tema en cuestión y estira más hacia un concepto lógico. En cómo partimos, agrupamos, clasificamos, optimizamos nuestro código. El mismo introduce conceptos como Capa de Acceso a Datos (Esta nos permite conectarnos a la fuente de datos y operar contra ella), Capa de Negocios (es la que se encarga de procesar todo, validaciones, etc. la misma suele distribuirse en la aplicación en sí y en la BBDD), y Capa de Presentación (es más bien lo que el usuario percibe, su interfaz gráfica por lo gral).
Creo que con esos conceptos introductorios ya estamos preparados para comprender mejor ciertos aspectos de este paradigma. Para resaltar por último, gracias a la separación en capas quiere decir que podemos cambiar de proveedor de base de datos, y no necesitaremos reescribir toda la aplicación de vuelta, sino solamente esa pequeña capa y reutilizaríamos la interfaz y las reglas de negocios, o también podemos mantener las reglas de negocios y el motor de base de datos, y fácilmente cambiarnos de una interfaz WinForm a WebForm, siempre la más dura de cambiar en la de negocios ya que afecta en un nivel mínimo a las otras 2 capas.
Creo que ya es suficiente teoría de momento y podemos comenzar con la acción, el código que voy a ir escribiendo lo haré en Visual Studio 2010 por que es la que tengo instalada ahora mismo en la maquina, pero funciona desde la versión 2005 con el framework 2.0 en adelante, ya que ADO.Net no ha sufrido grandes cambios desde esa versión, así que ustedes lo pueden ir creando en la versión del IDE o framework que más gusten.
Primeramente vamos a crear una solución con un proyecto de Biblioteca de Clases, el mismo tendrá 3 clases principales para representar la capa de Acceso a Datos, la primera llamaremos GDatos.cs, la misma le asignaremos el namespace AccesoDatos, y una clase abstracta con el mismo nombre. Haremos uso de estos 2 namespace:
1 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







en
en
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
prueba agregando ésto al inicio de tu SP
Gracias eso era.
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
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.
Seria bueno explicar con ejemplos de codigo la creacion de las capas con conexiones en Oracle.
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>ups… bueno resulta ke me da un error
“Index was outside the bounds of the array”
puedo hacerlo de otra manera?
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 ^^,
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.
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:
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
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
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…
Excelente !!!! estoy empezando con C# y esto me esta sirviendo de una manera fenomeno!!!!
TE PASASTE!!!!
Neta que si esta fenomenal, espero ser tan gradioso como tu algun dia, milllll gracias
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!
Excelente me parecen las criticas constructivas, es cuando mas se aprende siempre. Ojala hubieran mas lectores asi
. 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:
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:
Luego otra clase llamada medico, que hereda de Persona
Si quisieramos utilizar normalmente esta clase hariamos algo asi:
Pero tambien podemos crear de un medico, una persona (ya que lo es en el fondo, no?).
Lo que no podriamos hacer es esto (tal como dices):
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.
GeekZero:
Gracias!.
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
Saludos
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??
Buenas,
No entiendo por que el metodo Autenticar devuelve siempre true.
Saludos!
Es porque este dentro de este getter
ya se hace la apertura de la conexion. Si pruebas agregando un breakpoint en la pregunta sobre el estado de la conexion lo veras..
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.
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
Saludos..
muy bueno el tutorial, en realidad es dificil encontrar algo bueno sobre la arquitectura de 3 capas
perdón, ahora ví que está en la parte de Trackbacks/Pingbacks xD
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..
estaría bueno que entre los artículos relacionados esté la continuación de éste post =]