Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Net
¿Ha creado usted software?…¿Si?, entonces sabe lo complejo que resulta crear rutinas y
funciones para cada uno de los formularios, importar todas las referencias del motor de base de
datos en cada uno de los formularios, cambiar código aquí y allá (porque tiene copias del mismo
código en muchos lugares), escribir la lógica de validación de campos dentro del evento, corregir
los bugs que pudieran presentarse y no se diga de implementarles mejoras al software, etc., ¿No?
entonces no se preocupe este es un buen manual de como evitarse muchos dolores de cabeza en
diseño de su arquitectura a seguir.
Para que se de una mejor idea de que hablo, por favor descargue este proyecto de ejemplo,
ejecútelo, analice el código, observe como para realizar una misma funcionalidad en dos lugares
diferentes tuvimos que escribir casi las mismas líneas de código.
Separar responsabilidades, cada capa tiene una función especifica y no interviene con la
de las demás.
Reutilizar código
La separación de roles en tres capas, hace mas fácil reemplazar o modificar a una, sin
afectar a los módulos restantes
El código de la capa intermedia puede ser reutilizado por múltiples
Capacidad de migrar nuestro motor de Base de Datos sin grandes impactos al resto del
proyecto.
Poder cambiar el Front de nuestra aplicación sin afectar a la lógica de nuestra aplicación ni
a la Base de datos
Bien como ya hemos mencionado La Arquitectura de diseño 3 capas, consiste en dividir el diseño
del software en sus tres principales componentes:
[Fecha]
1
completamente desconocido el “front”, es decir, jamás sabrá si nuestra aplicación es una aplicación
web o desktop. Se encarga de recibir las peticiones de la Capa de Lógica de Negocio, ejecutar
dichas acciones y devolver el resultado a la misma capa.
4. Capa de Entidades o Entity Layer: Aunque aparentemente es una cuarta capa realmente
no lo es, esta capa se encarga de contener todos aquellos objetos (clases) que representan al
negocio, y esta es la única que puede ser instanciada en las 3 capas anteriores, es decir, solo ella
puede tener comunicación con el resto pero su función se limita a únicamente ser un puente de
transporte de datos. Esta capa complementa a la Capa de Negocio
Hasta aquí, hemos visto la teoría de lo que representa la Arquitectura 3 Capas, pero…
[Fecha]
2
2. Diríjase al “Explorador de soluciones” y haga click derecho sobre el proyecto que acabamos de
crear:
[Fecha]
3
4. Seleccione, Instalado –> Visual C# –> Windows –> Aplicación de Windows Forms –> En el
campo Nombre escriba, “Tienda-Presentacion” y presione el botón “Aceptar”.
[Fecha]
4
nombre “Tienda-AccesoDatos”
Observe que cuando creamos el proyecto principal en automático se creo un proyecto vacío,
eliminemos ese proyecto para dejar la siguiente estructura:
Ahora ya tenemos nuestra estructura completa, observe que creamos 4 proyectos dentro del
principal (que lo iniciamos como proyecto vacío), recuerde que el proyecto de Entidades en este
caso “Tienda-Entidades” no es una capa, mas bien, complementa a la capa de Lógica de Negocio.
[Fecha]
5
Continuemos con nuestro diseño…
Capa de Entidades
Recuerde que en esta capa estarán alojados los objetos (clases) con los cuales estaremos
trabajando, con estos objetos estaremos persistiendo las tablas de la base de datos que
utilizaremos.
La base de datos que usaremos contiene una sola tabla llamada “Producto” la cual contiene 5
campos (Id ‘int’, Descripción ‘nvarchar’, Marca ‘nvarchar’, Precio ‘nvarchar’), por lo cual la Capa de
Entidades contendrá un Objeto llamado Producto con 5 propiedades publicas cada uno de ellos
respetando el tipo de dato con el cual esta declarado en la base de datos.
1. Agregue una clase al proyecto “Tienda-Entidades” y llámela “EProducto” (La letra ‘E’ es por
conveción, es decir, todos los objetos de nuestra capa de Entidades llevaran la letra ‘E’ al principio
para que desde donde las lleguemos a utilizar sepamos que ese Objeto es una Entidad evitando
con esto confusiones con otros objetos), dentro agregue las siguientes líneas de código:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tienda_Entidades
{
public class EProducto
{
public int Id { get; set; }
public string Descripcion { get; set; }
public string Marca { get; set; }
public decimal Precio { get; set; }
}
}
Si tuviéramos mas tablas en nuestra base de datos tendríamos que crear la misma cantidad de
objetos en nuestra capa de Entidades y dentro contendrían la misma cantidad de propiedades
como campos tiene la tabla siempre respetando el tipo de dato con lo que estos campos están
declarados.
[Fecha]
6
Recordemos que la capa de datos es usada por la capa de lógica y la capa de lógica es llamada
por la capa de presentación, así que usaremos ese mismo orden para crear nuestras
configuraciones y nuestras líneas de código.
[Fecha]
7
<configuration>
<configSections>
</configSections>
<connectionStrings>
<add name="cnnString"
connectionString="Data Source=C:\Proyecto-
3Capas\Database1.sdf"
providerName="Microsoft.SqlServerCe.4.0" />
</connectionStrings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"
/>
</startup>
</configuration>
[Fecha]
8
5. Agregue una segunda referencia ahora a System.Data.SqlServeCe (esta librería no
siempre aparece, si este es su caso presione el botón Examinar localice la Dll dentro de
archivos de programa y selecciónela), presione Aceptar.
[Fecha]
9
8. Solución –> Proyectos –> Seleccione “Tienda-Entidades”
Observe como se a creado un nuevo elemento en nuestra carpeta de Referencias del proyecto
“Tienda-AccesoDatos”
[Fecha]
10
9. Inserte una clase nueva llamada “ProductoDal” de donde Dal vendrá de Data Access Layer,
dentro de la clase que acaba de crear tiene que declarar el espacio de
nombres System.Configuration, System.Data.SqlServerCe y del proyecto de Entidades, además
tiene que declarar la clase como publica (para que la Capa de Lógica de Negocio pueda tener
acceso a ella)
Ya tenemos el puente de comunicación entre nuestras Entidades y nuestra Capa de Datos. Solo
resta comenzar a codificar las acciones que querramos hacer con nuestra tabla Producto, recuerde
que el objeto ProductoDal únicamente tiene como responsabilidad trabajar con todo aquello
relacionado con Producto, así que comencemos haciendo la codificación para Insertar,
[Fecha]
11
Traer todos los registros existentes en nuestra tabla Producto, Traer, Actualizar y Eliminar por Id.
Para hacer esta tarea mas sencilla le proporcionare el código que deberá de poner en la clase
“ProductoDal”, pero, trate de escribir el código para vaya analizando la estructura del mismo.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//nuestras importaciones del Espacio de nombres que estaremos utilizando,
//recuerde que estas son las referencias que realizamos hace unos momentos...
using System.Configuration;
using System.Data.SqlServerCe;
using Tienda_Entidades;
namespace Tienda_AccesoDatos
{
//Definimos el acceso de nuestra clase como public, asegurando con esto su accesibilidad desde
//otros proyectos.
public class ProductoDal
{
//Primero y siguiendo el orden de las acciones CRUD
//Crearemos un Método que se encarga de insertar un nuevo Producto es nuestra tabla Producto
/// <summary>
/// Inserta un nuevo Producto en la tabla Producto
/// </summary>
/// <param name="producto">Entidad contenedora de los valores a insertar</param>
/// <autor>José Luis García Bautista</autor>
public void Insert(EProducto producto)
{
//Creamos nuestro objeto de conexion usando nuestro archivo de configuraciones
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
{
cnx.Open();
//Declaramos nuestra consulta de Acción Sql parametrizada
const string sqlQuery =
"INSERT INTO Producto (Descripcion, Marca, Precio) VALUES (@descripcion, @marca, @precio)";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
{
//El primero de los cambios significativos con respecto al ejemplo descargado es que aqui...
//ya no leeremos controles sino usaremos las propiedades del Objeto EProducto de nuestra capa
//de entidades...
cmd.Parameters.AddWithValue("@descripcion", producto.Descripcion);
cmd.Parameters.AddWithValue("@marca", producto.Marca);
cmd.Parameters.AddWithValue("@precio", producto.Precio);
cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// Devuelve una lista de Productos ordenados por el campo Id de manera Ascendente
/// </summary>
/// <returns>Lista de productos</returns>
/// <autor>José Luis García Bautista</autor>
[Fecha]
12
public List<EProducto> GetAll()
{
//Declaramos una lista del objeto EProducto la cual será la encargada de
//regresar una colección de los elementos que se obtengan de la BD
//
//La lista substituye a DataTable utilizado en el proyecto de ejemplo
List<EProducto> productos = new List<EProducto>();
/// <summary>
/// Devuelve un Objeto Producto
/// </summary>
/// <param name="idProducto">Id del producto a buscar</param>
/// <returns>Un registro con los valores del Producto</returns>
/// <autor>José Luis García Bautista</autor>
public EProducto GetByid(int idProducto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
{
cnx.Open();
[Fecha]
13
cmd.Parameters.AddWithValue("@id", idProducto);
SqlCeDataReader dataReader = cmd.ExecuteReader();
if (dataReader.Read())
{
EProducto producto = new EProducto
{
Id = Convert.ToInt32(dataReader["Id"]),
Descripcion = Convert.ToString(dataReader["Descripcion"]),
Marca = Convert.ToString(dataReader["Marca"]),
Precio = Convert.ToDecimal(dataReader["Precio"])
};
return producto;
}
}
}
return null;
}
/// <summary>
/// Actualiza el Producto correspondiente al Id proporcionado
/// </summary>
/// <param name="producto">Valores utilizados para hacer el Update al registro</param>
/// <autor>José Luis García Bautista</autor>
public void Update(EProducto producto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
{
cnx.Open();
const string sqlQuery =
"UPDATE Producto SET Descripcion = @descripcion, Marca = @marca, Precio = @precio WHERE Id = @id";
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
{
cmd.Parameters.AddWithValue("@descripcion", producto.Descripcion);
cmd.Parameters.AddWithValue("@marca", producto.Marca);
cmd.Parameters.AddWithValue("@precio", producto.Precio);
cmd.Parameters.AddWithValue("@id", producto.Id);
cmd.ExecuteNonQuery();
}
}
}
/// <summary>
/// Elimina un registro coincidente con el Id Proporcionado
/// </summary>
/// <param name="idproducto">Id del registro a Eliminar</param>
/// <autor>José Luis García Bautista</autor>
public void Delete(int idproducto)
{
using (SqlCeConnection cnx = new
SqlCeConnection(ConfigurationManager.ConnectionStrings["cnnString"].ToString()))
{
cnx.Open();
const string sqlQuery = "DELETE FROM Producto WHERE Id = @id";
[Fecha]
14
using (SqlCeCommand cmd = new SqlCeCommand(sqlQuery, cnx))
{
cmd.Parameters.AddWithValue("@id", idproducto);
cmd.ExecuteNonQuery();
}
}
}
}
}
Observe como desde nuestros diferentes Métodos y funciones estamos haciendo uso del
objeto EProducto declarado en la capa de Entidades…Observe también como nuestra
clase ProductosDal no valida que el valor de las propiedades de EProducto contengan datos o
sean del tipo de dato correcto porque ese ¿es trabajo de?…La Capa de Lógica de Negocio
Recuerde que la capa de Lógica es la encargada de establecer toda la lógica que el negocio
establece para llevar a cabo una acción o después de haber realizado un proceso, y esta se
comunica directamente con la Capa de Acceso a Datos, por lo cual tenemos que hacer la
referencia al Proyecto “Tienda-AccesoDatos” y para ello:
[Fecha]
15
Observe como se acaban de agregar nuestras referencias:
9. Agregue una nueva clase y llámela “ProductoBol”, haga los using de las referencias que
acabamos de crear, establezca el nivel de acceso como public y copie la siguiente estructura de
código:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//
//Hacemos las importaciones del espacio de nombres de los dos proyectos
que referenciamos //observe como esta capa solo referencio a Tienda-
[Fecha]
16
AccessData y no a Tienda-Presentacion //observe también como aquí no es
requerida la referencia a System.Data.SqlServerCe
using Tienda_AccesoDatos;
using Tienda_Entidades;
namespace Tienda_LogicaNegocio
{
public class ProductoBol
{
//Instanciamos nuestra clase ProductoDal para poder utilizar sus miembros
private ProductoDal _productoDal = new ProductoDal();
//
//El uso de la clase StringBuilder nos ayudara a devolver los mensajes de las validaciones
public readonly StringBuilder stringBuilder = new StringBuilder();
//
//Creamos nuestro método para Insertar un nuevo Producto, vae como este método no valida los el contenido
//de las propiedades, sino que manda a llamar a una Función que tiene como tarea única hacer esta validación
//
public void Registrar(EProducto producto)
{
if(ValidarProducto(producto))
{
if (_productoDal.GetByid(producto.Id) == null)
{
_productoDal.Insert(producto);
}
else
_productoDal.Update(producto);
}
}
if(stringBuilder.Length == 0)
{
return _productoDal.GetByid(idProduct);
}
return null;
}
if (stringBuilder.Length == 0)
{
_productoDal.Delete(idProduct);
[Fecha]
17
}
}
if (string.IsNullOrEmpty(producto.Descripcion))
stringBuilder.Append("El campo Descripción es obligatorio");
if (string.IsNullOrEmpty(producto.Marca))
stringBuilder.Append(Environment.NewLine + "El campo Marca es obligatorio");
if (producto.Precio <= 0)
stringBuilder.Append(Environment.NewLine + "El campo Precio es obligatorio");
return stringBuilder.Length == 0;
}
}
}
Analice cada uno de los métodos y funciones que creamos en nuestro primer objeto de nuestra
capa de Negocio, observe:
[Fecha]
18
11. En el formulario que tenemos por default llamado “Form1” diseñe una interfaz como la
siguiente:
12. Observe las siguientes líneas de código, genere los eventos involucrados y copie el código de
ejemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
//
//Hacemos las importaciones del espacio de nombres de los dos proyectos
que referenciamos
//observe como esta capa solo referencio a Tienda-LogicNegocio y a
Tienda-Entidades
//observe como no se referencia a la clase de acceso a Datos
using Tienda_LogicaNegocio;
using Tienda_Entidades;
namespace Tienda_Presentacion
{
public partial class Form1 : Form
{
//
//
[Fecha]
19
//Creamos las instancias de la clase Eproducto y ProductoBol
private EProducto _producto;
private readonly ProductoBol _productoBol = new ProductoBol();
public Form1()
{
InitializeComponent();
}
//
//Creamos los métodos generales llenando y leyendo objetos
//
private void Guardar()
{
try
{
if (_producto == null)
_producto = new EProducto();
_producto.Id = Convert.ToInt32(txtId.Text);
_producto.Descripcion = txtDescripcion.Text;
_producto.Marca = txtMarca.Text;
_producto.Precio = Convert.ToDecimal(txtPrecio.Text);
_productoBol.Registrar(_producto);
if (_productoBol.stringBuilder.Length != 0)
{
MessageBox.Show(_productoBol.stringBuilder.ToString(), "Para continuar:");
}
else
{
MessageBox.Show("Producto registrado/actualizado con éxito");
TraerTodos();
}
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
}
}
if (productos.Count > 0)
{
dgvDatos.AutoGenerateColumns = false;
dgvDatos.DataSource = productos;
dgvDatos.Columns["columnId"].DataPropertyName = "Id";
dgvDatos.Columns["columnDescripcion"].DataPropertyName = "Descripcion";
dgvDatos.Columns["columnMarca"].DataPropertyName = "Marca";
dgvDatos.Columns["columnPrecio"].DataPropertyName = "Precio";
}
else
[Fecha]
20
MessageBox.Show("No existen producto Registrado");
}
if (_producto != null)
{
txtId.Text = Convert.ToString(_producto.Id);
txtDescripcion.Text = _producto.Descripcion;
txtMarca.Text = _producto.Marca;
txtPrecio.Text = Convert.ToString(_producto.Precio);
}
else
MessageBox.Show("El Producto solicitado no existe");
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
}
}
TraerTodos();
}
catch (Exception ex)
{
MessageBox.Show(string.Format("Error: {0}", ex.Message), "Error inesperado");
}
}
//
//
//Usamos nuestros metodos y funciones generales, observe como no hemos repetido codigo en ningun lado
//haciendo con esto que nuestras tareas de actualizacion sean mas sencillas para nosotros o para
//al asignado en realizarlas...
private void btnAgregar_Click(object sender, EventArgs e)
{
Guardar();
}
[Fecha]
21
{
e.SuppressKeyPress = true;
TraerPorId(Convert.ToInt32(txtId.Text));
}
}
Guardar();
}
}
De nuevo observe como esta capa desconoce si existe una capa de Datos y mucho menos que
motor de base de datos se utiliza, tampoco se encarga de implementar las reglas de validación ni
de lógica de negocio, su tarea es interactuar con el usuario pidiendo y desplegando información.
Solo nos resta probar el proyecto…pero eso, será tarea suya…Ejecute el proyecto, inserte un
nuevo registro, busque un registro por id, edite su información, elimine un producto, depure el
código línea a línea para viva de paso a paso como es que va pasando de capa de capa enviando
y trayendo información.
Aquí termina nuestro articulo sobre Arquitectura 3 Capas, espero haya sido de su agrado y que la
explicación haya sido lo bastante clara, en caso de que tenga alguna duda por favor deje su
pregunta en la sección de comentarios o escríbame por medio del formulario de contacto.
[Fecha]
22
[Fecha]
23