Programar en c#

32
Como programar en 3 capas con c# y sql server 2008 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 está plagado de sitios donde preguntan cómo 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 primera 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 cómo 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).

description

tres capas

Transcript of Programar en c#

Page 1: Programar en c#

Como programar en 3 capas con c# y sql server 2008

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 está plagado de sitios donde preguntan cómo 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 primera 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 cómo 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

Page 2: Programar en c#

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 máquina,

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 deBiblioteca de Clases, el mismo tendrá 3 clases

principales para representar la capa de Acceso a Datos, la primera

llamaremosGDatos.cs, la misma le asignaremos el

namespace AccesoDatos, y una clase abstracta con el mismo nombre.

Haremos uso de estos 2 namespace:

12

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

deDeclaración de Variables en la misma creamos las variables o

atributos para la conexion a la BBDD, más un objeto de interfaz de

conexión para que sea implementada de manera específica por la clase

hija, si se han dado cuenta estamos usando ya conceptos

de OOP avanzados, y lo seguiremos usando fuertemente en el

transcurso del artículo. Esta es una clase que obligatoriamente debe ser

hereda por otra. El nivel de acceso por eso están definidas como

protected para que sean modificadas por si misma o por sus clases

derivadas.

1 #region "Declaración de Variables"

Page 3: Programar en c#

2345678910

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:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152

#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 haceif (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

Page 4: Programar en c#

5354555657

datosreturn 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

objetoIDataReader que es una interfaz de implementación general.

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

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

Page 5: Programar en c#

5051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117

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 valorif (par.Direction == ParameterDirection.InputOutput || par

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 valorif (par.Direction == ParameterDirection.InputOutput || par

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 valorif (par.Direction == ParameterDirection.InputOutput || par

ParameterDirection.Output)resp = par.Value;

Page 6: Programar en c#

118119120121122123124125126127128129130131132133134135136137138139140

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 Entradapublic 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.123456789101112131415161718192021222324252627282930313233

#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 Objectprotected 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)

Page 7: Programar en c#

343536373839404142434445464748495051525354555657585960616263

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

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:

12345678910111213141516171819202122232425262728

#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{ MTransaccion.Commit(); }finally{

MTransaccion = null;EnTransaccion = false;

}// end finally

Page 8: Programar en c#

29303132333435363738394041424344

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

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

Page 9: Programar en c#

495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116

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

Page 10: Programar en c#

117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184

// 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[] { 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 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 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

Page 11: Programar en c#

185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252

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

Page 12: Programar en c#

253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320

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

Page 13: Programar en c#

321322323324325326327328329330331332333334335

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.

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

anteriorAccesoDatos. La misma será una clase que hereda de la clase

GDatos1 using System;

Lo siguiente que crearemos en una colección de hashtable

parapreservar por así decirlo los comandos ejecutados y accederlos

conmayor 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

quesobreescibirá el de su padre, y creará el ConnectionString y lo

retornorá.1 public override sealed string CadenaConexion

Page 14: Programar en c#

23456789

10111213141516171819202122232425262728

{get{

if (MCadenaConexion.Length == 0){

if (MBase.Length != 0 &amp;&amp; MServidor.Length != 0){

var sCadena = new System.Text.StringBuilder("");sCadena.Append("data source=;");sCadena.Append("initial catalog=;");sCadena.Append("user id=;");sCadena.Append("password=;");sCadena.Append("persist security info=True;");sCadena.Replace("", Servidor);sCadena.Replace("", Base);sCadena.Replace("", Usuario);sCadena.Replace("", Password);

return sCadena.ToString();}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

invocaremos12345678

protected override void CargarParametros(System.Data.IDbCommand com, Object[] args){

for (int i = 1; i < com.Parameters.Count; i++) { var p = (System.Data.SqlClient.SqlParameter)com.Parameters[i]; p.Value = i <= args.Length ? args[i - 1] ?? DBNull.Value : null; }}

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

10111213141516

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

Page 15: Programar en c#

17181920

ColComandos.Add(procedimientoAlmacenado, com);}//end elsecom.Connection = (System.Data.SqlClient.SqlConnection)Conexion;com.Transaction = (System.Data.SqlClient.SqlTransaction)MTransaccion;return com;

}// end Comando

Como no puedo sobrecargar un método con la misma cantidad de

parámetros que recibe, y del mismo tipo, me veo obligado a crear un

método extra para crear el comando si queremos ejecutar

sentenciasSQL 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(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, {

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

dequery’s SQL12345

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.

123456789

10

public SqlServer(){

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

}// end DatosSQLServer

public SqlServer(string cadenaConexion){ CadenaConexion = cadenaConexion; }// end DatosSQLServer

Page 16: Programar en c#

1112131415161718192021222324

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

123456789

10111213141516171819202122232425262728293031323334353637383940414243444546474849

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.

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.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 MCadenaConexion = CadenaConexion;

}// end get set { MCadenaConexion = value; } // end set }// end CadenaConexion

Page 17: Programar en c#

5051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899

100101102103104105106107108109110111112113114115116117

/* * 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] ?? DBNull.Value : null; } // end for } // end CargarParametros

/* * En el procedimiento Comando, se buscará primero si ya existe el comando en dicha Hashtable para retornarla * (convertida en el tipo correcto). Caso contrario, se procederá a la 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, 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) 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(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 CrearDataAdapter(string procedimientoAlmacenado, {

Page 18: Programar en c#

118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163

var da = new System.Data.SqlClient.SqlDataAdapter((System.Data.SqlClient.SqlCommand 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 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}

Esta es la tercer entrega, probablemente será la más corta pero no la

última aún. El motivo de su longitud es por que es una clase que se

utiliza como medio para crear la flexibilidad y portabilidad de fuentes

de datos, en éste caso motores de base de datos.

También daremos por terminada la capa de Acceso a

Datos, entonces así no mezclamos el código y será más fácil seguirlo

posteriormente. También pertenecerá al namespace AccesoDatos. Lo

llamo conexión por que es la clase con las otras capas interactuaran en

modo directo.

Para ello creamos un objeto estático de la clase GDatos que instanciará

Page 19: Programar en c#

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

tipoGDatos, 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? 123456789

101112131415161718

namespace AccesoDatos{ public class Conexion { public static GDatos GDatos; public static bool IniciarSesion(string nombreServidor, string baseDatos, string usuario, 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.123456789

CREATE TABLE [dbo].[Cliente]([IdCliente] [tinyint] NOT NULL,[DescripcionCliente] [varchar](50) NOT NULL,[Siglas] [varchar](15) NULL,

CONSTRAINT [PK_Cliente] PRIMARY KEY CLUSTERED(

[IdCliente] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS [PRIMARY]) ON [PRIMARY]

Preferentemente trabajaremos con procedimientos

almacenadoscomo Zeus manda. Para crear, listar y buscar tendremos

3 distintos procedimientos.

Page 20: Programar en c#

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 ygetters.123

public short IdCliente { get; set; }public string Descripcion { get; set; }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.

Page 21: Programar en c#

123456789

1011121314151617181920

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

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

conpolimorfismo).

La clase completa quedaría así:123456789

10111213141516171819202122232425262728293031323334

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}

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;

Page 22: Programar en c#

3536

} }}

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ónWinForm 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 enwinForm como en webForm.

El primer ejemplo será Desktop, crearemos un formulario con una

apariencia semejante al que ven en la imagen.

Evidentemente, un sistema real no lo harán así, el botón conectar emula

el comportamiento de una pantalla de login, el boton crear mandará a

la BBDD los datos de la caja, Listar rellenará la grilla y Buscar By Id se

encargará de devolvernos un registro a partir de lo que carguemos en la

caja de Id. Otra implementación interesante sería agregarle

un identitya 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.

Page 23: Programar en c#

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.

123456789

1011121314

var cliente = new Cliente();try{

cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text);cliente.Descripcion = txtDescripcionCliente.Text;cliente.Sigla = txtSiglaCliente.Text;

cliente.Crear(cliente);MessageBox.Show(String.Format("{0}", "Se creo

exitosamente"));}catch (Exception ex){

MessageBox.Show(ex.Message);}

Luego de esto escribimos el 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);}

Page 24: Programar en c#

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.

123456789

1011121314151617

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

123456789

10111213141516171819202122232425262728293031323334353637383940

using System;using System.Windows.Forms;using AccesoDatos;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); } }

Page 25: Programar en c#

41424344454647484950515253545556575859606162636465666768697071727374757677787980

private void btnCrear_Click(object sender, EventArgs e) { var cliente = new Cliente(); try { cliente.IdCliente = Convert.ToInt16(txtIdCliente.Text); cliente.Descripcion = txtDescripcionCliente.Text; cliente.Sigla = txtSiglaCliente.Text;

cliente.Crear(cliente); MessageBox.Show(String.Format("{0}", "Se creo exitosamente")); } catch (Exception ex) { MessageBox.Show(ex.Message); } }

private void btnBuscarById_Click(object sender, EventArgs e) { var cliente = new Cliente(); try { cliente = cliente.Listar(Convert.ToInt16(txtIdCliente.Text));

if (cliente != null) { txtDescripcionCliente.Text = cliente.Descripcion; txtSiglaCliente.Text = cliente.Sigla; } else { MessageBox.Show(String.Format("{0}", "No existia el cliente buscado")); } } 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:

123456789

10111213141516171819202122

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

Page 26: Programar en c#

2324252627282930313233343536373839404142434445464748495051525354

<br /> <hr style="margin-top: 0px; margin-bottom: 0px" /> <asp:GridView ID="grilla" runat="server" CellPadding="4" ForeColor="#333333" GridLines="None"> <AlternatingRowStyle BackColor="White" /> <EditRowStyle BackColor="#2461BF" /> <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <RowStyle BackColor="#EFF3FB" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <SortedAscendingCellStyle BackColor="#F5F7FB" /> <SortedAscendingHeaderStyle BackColor="#6D95E1" /> <SortedDescendingCellStyle BackColor="#E9EBEF" /> <SortedDescendingHeaderStyle BackColor="#4870BE" /> </asp:GridView> <asp:Label ID="Label3" runat="server" Text="Estado:"></asp:Label> <asp:Label ID="lblEstado" runat="server"></asp:Label> <br /> <asp:Button ID="btnConectar" runat="server" Text="Conectar" onclick="btnConectar_Click" /> <asp:Button ID="btnCrear" runat="server" onclick="btnCrear_Click" Text="Crear" /> <asp:Button ID="btnListar" runat="server" onclick="btnListar_Click" Text="Listar" /> <asp:Button ID="btnListarById" runat="server" onclick="btnListarById_Click" Text="Listar By Id" /> <br />

</div> </form></body></html>

123456789

1011121314151617181920212223242526272829303132333435

using System;

// using AccesoDatos;

public partial class Cliente : System.Web.UI.Page{ protected void Page_Load(object sender, EventArgs e) {

} protected void btnConectar_Click(object sender, EventArgs e) { try { lblEstado.Text = ""; AccesoDatos.Conexion.IniciarSesion("127.0.0.1", "Queryable", "sa", "***"); lblEstado.Text = String.Format("{0}", "Se conecto exitosamente"); } catch (Exception ex) { lblEstado.Text = String.Format(ex.Message); } } protected void btnListar_Click(object sender, EventArgs e) { var cliente = new AccesoDatos.Orm.Cliente(); try { grilla.DataSource = cliente.Listar(); grilla.DataBind(); lblEstado.Text = String.Format("{0}", "Se listo exitosamentë"); } catch (Exception ex) { lblEstado.Text = ex.Message;

Page 27: Programar en c#

36373839404142434445464748495051525354555657585960616263646566676869707172737475

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