tutorial-3 capas

40
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

Transcript of tutorial-3 capas

Page 1: tutorial-3 capas

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

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

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

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

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

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

Primeramente vamos a crear una solución con un proyecto de Biblioteca de Clases, el mismo tendrá 3 clases principales para representar la capa de Acceso a Datos, la primera llamaremos GDatos.cs, la misma le asignaremos el namespace AccesoDatos, y una clase abstracta con el mismo nombre. Haremos uso de estos 2 namespace:

1 using System;

Page 2: tutorial-3 capas

2 using System.Data;

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

12345678910

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

123456789101112131415161718192021222324252627282930313233

#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; }

Page 3: tutorial-3 capas

343536373839404142434445464748495051525354555657

 #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 abreif (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.

123456789101112131415161718192021222324252627

#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;

Page 4: tutorial-3 capas

28293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788

} // 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 SQLpublic 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 metodopublic object TraerValorOutput(string procedimientoAlmacenado){

// asignar el string sql al commandvar com = Comando(procedimientoAlmacenado);// ejecutar el commandcom.ExecuteNonQuery();// declarar variable de retornoObject resp = null;

 // recorrer los parametros del SPforeach (IDbDataParameter par in com.Parameters)

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

if (par.Direction == ParameterDirection.InputOutput

Page 5: tutorial-3 capas

8990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140

|| 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 commandvar com = Comando(procedimientoAlmacenado);// cargar los parametros del SPCargarParametros(com, args);// ejecutar el commandcom.ExecuteNonQuery();// declarar variable de retornoObject resp = null;

 // recorrer los parametros del SPforeach (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 commandvar com = ComandoSql(comadoSql);// ejecutar el commandcom.ExecuteNonQuery();// declarar variable de retornoObject 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

Page 6: tutorial-3 capas

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 SQLpublic 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.

1234567891011121314151617181920212223242526272829303132333435363738

#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 BBDDpublic bool Autenticar(){

if (Conexion.State != ConnectionState.Open)Conexion.Open();

return true;}// end Autenticar // metodo sobrecargado para autenticarse contra el motor de BBDDpublic bool Autenticar(string vUsuario, string vPassword){

MUsuario = vUsuario;MPassword = vPassword;MConexion = CrearConexion(CadenaConexion);

 MConexion.Open();return true;

}// end Autenticar  // cerrar conexionpublic void CerrarConexion(){

if (Conexion.State != ConnectionState.Closed)MConexion.Close();

Page 7: tutorial-3 capas

39404142434445464748495051525354555657585960616263

} // end CerrarConexion  // Ejecuta un Procedimiento Almacenado en la base. public int Ejecutar(string procedimientoAlmacenado){ return Comando(procedimientoAlmacenado).ExecuteNonQuery(); } // end Ejecutar // Ejecuta un query sqlpublic 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 forreturn 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:

12345678910111213141516171819202122

#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 tryfinally{ EnTransaccion = false; }

}// end IniciarTransaccion  //Confirma la transacción activa. public void TerminarTransaccion(){

try

Page 8: tutorial-3 capas

23242526272829303132333435363738394041424344

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

1234567891011121314151617181920212223242526272829303132333435

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.

Page 9: tutorial-3 capas

36373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596

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,

Page 10: tutorial-3 capas

979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157

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) {

Page 11: tutorial-3 capas

158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218

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();

Page 12: tutorial-3 capas

219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279

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

Page 13: tutorial-3 capas

280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335

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

Page 14: tutorial-3 capas

  #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

Page 15: tutorial-3 capas

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

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

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

1 using System;

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

1 static readonly System.Collections.Hashtable ColComandos = new System.Collections.Hashtable();

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

12345678910111213141516171819202122232425262728

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("data

source=<SERVIDOR>;");sCadena.Append("initial

catalog=<BASE>;");sCadena.Append("user id=<USER>;");

sCadena.Append("password=<PASSWORD>;");sCadena.Append("persist security

info=True;");sCadena.Replace("<SERVIDOR>",

Servidor);sCadena.Replace("<BASE>", Base);sCadena.Replace("<USER>", Usuario);sCadena.Replace("<PASSWORD>",

Password); 

return sCadena.ToString();

Page 16: tutorial-3 capas

}throw new Exception("No se puede establecer

la cadena de conexión en la clase SQLServer");}return MCadenaConexion;

}// end getset{ MCadenaConexion = value; } // end set

}// end CadenaConexion

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

12345678

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] : null;} // end for

} // end CargarParametros

Luego para crear el Comando a ejecutar crearemos un método que revisará en el hashTable anteriormente creado, si ya se lo ha hecho o no. En caso que estemos dentro de una transacción conectado, necesitamos crear una segunda conexión para la lectura de los parámetros de Procedimiento almacenado.

1234567891011121314151617181920

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 elsecom.Connection =

(System.Data.SqlClient.SqlConnection)Conexion;com.Transaction =

(System.Data.SqlClient.SqlTransaction)MTransaccion;return com;

}// end Comando

Page 17: tutorial-3 capas

Como no puedo sobrecargar un método con la misma cantidad de parámetros que recibe, y del mismo tipo, me veo obligado a crear un método extra para crear el comando si queremos ejecutar sentencias SQL directamente desde la App.

12345

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

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

12

protected override System.Data.IDbConnection CrearConexion(string cadenaConexion){ return new System.Data.SqlClient.SqlConnection(cadenaConexion); }

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

1234567

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 ejecución de query’s SQL

12345

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

var da = new System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand) ComandoSql(comandoSql));

return da;} // end CrearDataAdapterSql

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

12345678

public SqlServer(){

Base = "";Servidor = "";Usuario = "";Password = "";

}// end DatosSQLServer 

Page 18: tutorial-3 capas

9101112131415161718192021222324252627

 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 código completo quedaría así:

12345678910111213141516171819202122232425262728293031323334353637

using System; namespace AccesoDatos{ public class SqlServer : GDatos { /* * Continuaremos con el método Comando, procediendo de igual forma que en los anteriores. * En este caso, además, implementaremos un mecanismo de “preservación” de los Comandos creados, * para acelerar su utilización. 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) { 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>;");

Page 19: tutorial-3 capas

38394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798

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 conexión en la clase DatosSQLServer"); } return null; }// end get set { MCadenaConexion = value; } // end set }// end CadenaConexion   /* * Agregue ahora la definición del procedimiento CargarParametros, el cual deberá asignar cada valor * al parámetro 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 ejecución de procedimientos almacenados que devuelven un valor como parámetro de salida, * la cantidad de elementos en el vector de argumentos, puede no corresponder con la cantidad de parámetros. * 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] : 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 creación del mismo, * y su agregado en el repositorio. Dado que cabe la posibilidad de que ya estemos dentro de una transacción, * es necesario abrir una segunda conexión a la base de datos, para obtener la definición de los parámetros * del procedimiento Almacenado (caso contrario da error, por intentar leer sin tener asignado el * objeto Transaction correspondiente). Además, el comando,

Page 20: tutorial-3 capas

99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159

obtenido por cualquiera de los mecanismos * debe recibir la conexión y la transacción correspondientes (si no hay Transacción, la variable es null, * y ese es el valor que se le pasa al objeto Command) */ 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 Conexión de SqlClient, utilizando la cadena de conexión 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 método Comando para crear el comando necesario. protected override System.Data.IDataAdapter

Page 21: tutorial-3 capas

160161162163

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  //Finalmente, es el turno de definir CrearDataAdapter, el cual aprovecha el método Comando para crear el comando necesario. protected override System.Data.IDataAdapter CrearDataAdapterSql(string comandoSql) { var da = new System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand) ComandoSql(comandoSql)); return da; } // end CrearDataAdapterSql  /* * Definiremos dos constructores especializados, uno que reciba como argumentos los valores de Nombre del Servidor * y de base de datos que son necesarios para acceder a datos, y otro que admita directamente la cadena de conexión completa. * Los constructores se definen como procedimientos públicos de nombre New. */ 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 }// end class DatosSQLServer}// end namespace

Page 22: tutorial-3 capas

Esta es la tercer entrega, probablemente será la más corta pero no la última aún. El motivo de su longitud es por que es una clase que se utiliza como medio para crear la flexibilidad y portabilidad de fuentes de datos, en éste caso motores de base de datos.

También daremos por terminada la capa de Acceso a Datos, entonces así no mezclamos el código y será más fácil seguirlo posteriormente. También pertenecerá al namespace

Page 23: tutorial-3 capas

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

123456789101112131415161718

namespace AccesoDatos{ public class Conexion { public static GDatos GDatos; public static bool IniciarSesion(string nombreServidor, string baseDatos, string usuario, string password) { GDatos = new SqlServer(nombreServidor, baseDatos, usuario, password); return GDatos.Autenticar(); } //fin inicializa sesion  public static void FinalizarSesion() { GDatos.CerrarConexion(); } //fin FinalizaSesion  }//end class util}//end namespace

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

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

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

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

1 CREATE TABLE [dbo].[Cliente](

Page 24: tutorial-3 capas

23456789

[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.

12345678

CREATE PROCEDURE [dbo].[insCliente]@id as int,@nombre varchar(50),@sigla varchar(15)

ASBEGIN

insert into Cliente values(@id, @nombre, @sigla);END

12345

CREATE PROCEDURE [dbo].[slcCliente]ASBEGIN

select * from Cliente;END

12345

CREATE PROCEDURE [dbo].[slcCliente]ASBEGIN

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 aplicación, es lo que venía escribiendo en las primeras partes del tutorial.

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

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

12public short IdCliente { get; set; }public string Descripcion { get; set; }

Page 25: tutorial-3 capas

3 public string Sigla { get; set; }

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

1234567891011121314151617181920

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

123456789101112131415161718192021

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; }  // métodos 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)

Page 26: tutorial-3 capas

222324252627282930313233343536

{ 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; } }}

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

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

El primer ejemplo será Desktop, crearemos un formulario con una apariencia semejante al que ven en la imagen.

Page 27: tutorial-3 capas

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

El código que pondremos en el botón conectar es como sigue,  recuerden que es conveniente armarlo dinamicamente con una pantalla de login especialmente el usuario y el pass. Con esto logramos armar toda la capa de Acceso a Datos, no se mantiene conectada la aplicación, sino solo sus valores de conexión en memoria.

123456789

try{

Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "123");MessageBox.Show(String.Format("{0}", "Se conecto

exitosamente"));}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 método crear enviandole el nuevo objeto cliente.

123

var cliente = new Cliente();try{

Page 28: tutorial-3 capas

4567891011121314

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 código de listado de todos los clientes, y lo cargamos en la grilla. Se dan cuenta que necesitamos muy pocas lineas de código en la capa de Presentación, y que no tiene una dependencia de la BBDD?. Si se dan cuenta, cuando vamos a devolver muchos registros no podemos utilizar un montón de instancias de Cliente, sino simplemente devolvemos un DataTable, DataSet o DataReader.

123456789

var cliente = new Cliente();try{

grilla.DataSource = cliente.Listar();}catch (Exception ex){

MessageBox.Show(ex.Message);}

Finalmente el código de búsqueda quedaría algo asi. Cómo sabemos que si buscamos por la PK, siempre nos devolverá un sólo registro, lo podemos crear como un objeto Cliente.

1234567891011121314151617

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 código completo quedaría así:

123

using System;using System.Windows.Forms;using AccesoDatos;

Page 29: tutorial-3 capas

45678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364

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) {

Page 30: tutorial-3 capas

65666768697071727374757677787980

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); } } }}

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

123456789101112131415161718192021222324252627282930313233

<%@ 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" />

Page 31: tutorial-3 capas

343536373839404142434445464748495051525354

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

12345678910111213141516171819202122232425

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); } }

Page 32: tutorial-3 capas

2627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475

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"); } } catch (Exception ex) { lblEstado.Text = ex.Message; } }}

Si pueden agregar/aportar mejoras y funcionalidad a estas clases, serán bienvenidas de hecho éste código lo creo un MVP de Microsoft y yo le agregue algunas funcionalidades..

Page 33: tutorial-3 capas