Introducción
Hemos entrado en un momento clave en la evolución del presente del articulo, aquí es donde concretamente se verán las 3 capas interactuando entre si.
El mismo fue evolucionado de artículos previos:
[N-Tier] – Desarrollo en capas - Ejemplo Facturación – Parte 1
[N-Tier] – Desarrollo en capas - Ejemplo Facturación – Parte 2
En la ultima oportunidad se había reestructurado la aplicación para que soportara 2 capas, la presentación accedía directo a la de datos para trabajar con las entidades.
Además se había agregado un proyecto que representa las entidades del negocio, el cual cruzaba todas las capas y era usado como medio de trasporte de datos entre las mismas. Este estaba implementado por medios de dataset tipado para representar las entidades.
Estructura del proyecto
La nueva arquitectura agrego un proyecto adicional del tipo “Class Library”, el cual se ubicara en medio de la capa de presentación y la de datos, desacoplándolas.
Esta nueva capa representara la fachada de entrada al dominio de la aplicación, mas adelante cuando se implementen servicio para distribuir la aplicación cumplirá un papel fundamental para aislar el dominio.
También se reestructuro el proyecto de entidades, ahora ya no se usan dataset tipados para representar las entidades, estas fueron reemplazas por clases custom, es por eso que se verán nombre como ser: “CustomerEntity”, “InvoiceEntity”, etc.
El cambio en las entidades afecto la capa de datos, ya no se usa el DataAdapter para cargar los datatable que representaban a la entidad, fue necesario reemplazarlos por DataReader, estos son óptimos para la lectura secuencial de los registros devueltos por la query, y el armado de las instancias de la entidad.
La imagen representa los distintos proyectos y como se referencian entre si:
En esta nueva distribución de capas será imprescindible que la presentación se comunique siempre con la fachada de negocio, la cual abstraerá las operaciones transaccionales, y creara un único punto de entrada al sistema, si bien no se aprecia la importancia de lo dicho con este ejemplo, si a futuro fuera necesario cambiar la presentación, quizás por una web o con WPF, no se perdería todo el trabajo realizado, ya que las reglas de negocio y persistencia quedan intacticas.
Algo que seguro traer molestia al desarrollar aplicando esta técnica es que la mayoría de las operaciones serán un pasamano por la capa de negocio, esta solo tomara lo que la presentación retorne y lo devolverá ala presentación, sin efectuar ninguna operación en medio, para la mayoría de las operaciones de consulta será así, pero en otras circunstancias se vera la importancia de esta capa, sobre todo al persistir entidades complejas.
El uso de entidades con clases en lugar de dataset tipados, también aporta una mejora importante, las clases permiten extender funcionalidad y relacionar entidades fácilmente, como ser el caso del calculo de Total en la entidad de facturación.
A continuación se analizarían las operaciones que han sufrido cambios durante la transformación a las 3 capas.
Grabar/Actualizar un Cliente
Durante la operación de confirmación de la factura se notara el cambio en la técnica utilizada para persistir la información del cliente, anteriormente desde la presentación se decidía si se actualizaba o insertaba la entidad, ahora es la capa de negocio quien decide que operación debe realizarse.
[Presentación]
if (cliente == null) cliente = new CustomerEntity(); cliente.FirstName = txtNombre.Text; cliente.LastName = txtApellido.Text; cliente.Company = txtCompañia.Text; cliente.Address = txtDireccion.Text; cliente.Email = txtEmail.Text; cliente = CustomerBO.Save(cliente);
[Business Layer]
public static CustomerEntity Save(CustomerEntity customer) { if (CustomerDAL.Exists(customer.CustomerId)) return CustomerDAL.Update(customer); else return CustomerDAL.Create(customer); }
[Data Access]
public static class CustomerDAL { public static bool Exists(int id) { int nrorecord = 0; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string sql = @"SELECT Count(*) FROM Customer WHERE CustomerId = @customerId"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("customerId", id); nrorecord = Convert.ToInt32(cmd.ExecuteScalar()); } return nrorecord > 0; } public static CustomerEntity Create(CustomerEntity customer) { using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { string sql = @"INSERT INTO Customer (FirstName, LastName, Company, Address, Email) VALUES (@firstName, @lastName, @company, @address, @email) SELECT SCOPE_IDENTITY()"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@firstName", customer.FirstName); cmd.Parameters.AddWithValue("@lastName", customer.LastName); cmd.Parameters.AddWithValue("@company", customer.Address); cmd.Parameters.AddWithValue("@address", customer.Company); cmd.Parameters.AddWithValue("@email", customer.Email); customer.CustomerId = Convert.ToInt32(cmd.ExecuteScalar()); } return customer; } public static CustomerEntity Update(CustomerEntity customer) { using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string sql = @"UPDATE Customer SET FirstName = @firstName, LastName = @lastName, Company = @company, Address = @address, Email = @email WHERE CustomerId = @customerid"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@firstName", customer.FirstName); cmd.Parameters.AddWithValue("@lastName", customer.LastName); cmd.Parameters.AddWithValue("@company", customer.Address); cmd.Parameters.AddWithValue("@address", customer.Company); cmd.Parameters.AddWithValue("@email", customer.Email); cmd.Parameters.AddWithValue("@customerid", customer.CustomerId); cmd.ExecuteNonQuery(); } return customer; } }
La capa de negocio valida si la entidad existe o no, y procede a ejecutar la operación correcta para cada caso, usando como identificador el id de la entidad.
Proceso de Facturación
Con respecto a la implementación del articulo previo el proceso de facturación sufrió un cambio importante, ya no se envía dos entidades separadas para procesar, las cuales representaban al encabezado de la factura y sus líneas, sino que una única entidad posee una colección o lista genérica vinculada que permite cargar los datos de la asociación.
[Presentación]
#region Creo el Encabezado\Linea de la Factura InvoiceEntity invoice = new InvoiceEntity(); invoice.CustomerId = cliente.CustomerId; invoice.InvoiceDate = DateTime.Now.Date; invoice.BillingAddress = txtDireccion.Text; foreach (DataGridViewRow row in dgvLineaCompra.Rows) { InvoiceLinesEntity invoiceLine = new InvoiceLinesEntity(); invoiceLine.TrackId = Convert.ToInt32(row.Cells["Track"].Value); invoiceLine.UnitPrice = Convert.ToDecimal(row.Cells["PrecioUnitario"].Value); invoiceLine.Quantity = Convert.ToInt32(row.Cells["Cantidad"].Value); invoice.Lineas.Add(invoiceLine); } InvoiceBO.RegistrarFacturacion(invoice); #endregion
[Business Layer]
public static class InvoiceBO { public static void RegistrarFacturacion(InvoiceEntity invoice) { // // inicializo la transacciones // using (TransactionScope scope = new TransactionScope()) { // // Creo la factura y sus lineas // InvoiceDAL.Create(invoice); // // Actualizo el total // InvoiceDAL.UpdateTotal(invoice.InvoiceId, invoice.Total); scope.Complete(); } } }
[Data Access]
public static class InvoiceDAL { public static void Create(InvoiceEntity invoice) { using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); // // Creacion de la Factura // string sqlFactura = @"INSERT INTO Invoice (CustomerId, InvoiceDate, BillingAddress, Total) VALUES (@customerId, @date, @address, @total) SELECT SCOPE_IDENTITY()"; using (SqlCommand cmd = new SqlCommand(sqlFactura, conn)) { cmd.Parameters.AddWithValue("@customerId", invoice.CustomerId); cmd.Parameters.AddWithValue("@date", invoice.InvoiceDate); cmd.Parameters.AddWithValue("@address", invoice.BillingAddress); cmd.Parameters.AddWithValue("@total", 0); invoice.InvoiceId = Convert.ToInt32(cmd.ExecuteScalar()); } string sqlLineaFactura = @"INSERT INTO InvoiceLine (InvoiceId, TrackId, UnitPrice, Quantity) VALUES (@invoiceid, @trackid, @unitprice, @quantity) SELECT SCOPE_IDENTITY()"; using (SqlCommand cmd = new SqlCommand(sqlLineaFactura, conn)) { foreach (InvoiceLinesEntity invoiceLine in invoice.Lineas) { // // como se reutiliza el mismo objeto SqlCommand es necesario limpiar los parametros // de la operacion previa, sino estos se iran agregando en la coleccion, generando un fallo // cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@invoiceid", invoice.InvoiceId); cmd.Parameters.AddWithValue("@trackid", invoiceLine.TrackId); cmd.Parameters.AddWithValue("@unitprice", invoiceLine.UnitPrice); cmd.Parameters.AddWithValue("@quantity", invoiceLine.Quantity); // // Si bien obtenermos el id de linea de factura, este no es usado // en la aplicacion // invoiceLine.InvoiceLineId = Convert.ToInt32(cmd.ExecuteScalar()); } } } } /// <summary> /// Actualizacion del Total de la Factura /// </summary> public static void UpdateTotal(int idInvoice, decimal total) { using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string sqlUpdateTotal = @"UPDATE Invoice SET Total = @total WHERE InvoiceId = @InvoiceId"; using (SqlCommand cmd = new SqlCommand(sqlUpdateTotal, conn)) { cmd.Parameters.AddWithValue("@total", total); cmd.Parameters.AddWithValue("@InvoiceId", idInvoice); cmd.ExecuteNonQuery(); } } } }
Un punto importante a remarcar es que ahora la capa de negocio orquesta todas las operaciones y por ende es esta la que lleva la transacción de las entidades, en el artículo anterior se había comentado este punto, justamente porque la presentación no debía ser responsable de asegurar la operación. Es mas a la presentación se le ha quitado la referencia a la librería System.Transactions.
Durante la transformación se separo una de las funcionalidades correspondiente a al actualización del total de la factura, en el ejemplo previo se hacia la sumatoria mientras se creaba cada línea, y al final se actualizaba la entidad “invoice” con el valore resultante. Ahora esta se realiza en una operación separada y coordinada por la capa de negocio, se toma el id de factura devuelto por la operación anterior, y se sumando los valores de las línea internamente en la propia entidad que representa la factura de cliente, ya que ahora esta posee la colección relacionada, una operación lambda en el método de extensión de suma fue mas que suficiente.
public class InvoiceEntity { public InvoiceEntity() { this.Lineas = new List<InvoiceLinesEntity>(); } public int InvoiceId { get; set; } public int CustomerId { get; set; } public DateTime InvoiceDate { get; set; } public string BillingAddress { get; set; } public List<InvoiceLinesEntity> Lineas { get; set; } public decimal Total { get { return this.Lineas.Sum(x => x.UnitPrice * x.Quantity); } } }
Conclusión
Si bien aun quedan muchos puntos por explorar, este simple ejemplo puede servir de guía para comenzar con futuros desarrollos.
Es cierto que algunos aspectos que podrían haberse implementado, como ser:
- La entidad InvoiceEntity aun conserva la propiedad CustomerId, cuando debería reemplazarse por una propiedad del tipo CustomerEntity, lo cual no se realizo para no aumentar la complejidad.
- No se implementaron controles fuerte de errores y como comunicarlos hacia la presentación para ser tratados, este punto se vera en un articulo futuro.
- No se agregaron reglas de negocio restrictivas, como podría ser, por ejemplo, si el cliente supera tiene un monto determinado de facturas impagas no se permita la facturación en curso.
En posteriores artículos tratare estos tema con mas detalle, y otros como ser:
-la utilización de reportadores como Crystal Reports o Reporting Service en aplicación con capas,
-la creación de repositorios de acceso a datos que soporten distintas base de datos,
-el cambio de la presentación a un proyecto web para marcar la reutilización, así como también al desconexión de la capas de presentación y negocio mediante servicios para poder hacer uso de un ambiente distribuido.
Ejemplo de código
El proyecto fue desarrollado con Visual Studio 2008
Se debe tener presente Sql Server 2008 Express para poder acceder a la db integrada al proyecto.
[C#] |
[VB.NET] |
Muy bueno, Leandro, gracias una vez mas por compartirlo.
ResponderEliminarHola, muy bueno el aporte Leandro, la verdad es que estoy aprendiendo mucho con este proyecto de desarrollo en capas.
ResponderEliminarBuenas noches, baje los archivos, pero no me funcionan si alguien sabe, que debo hacer se los agradezco
ResponderEliminarhola Lixander
ResponderEliminarHas validado los requisitos que menciono al final del articulos
O sea estas usando VS2008 y Sql Express 2008 ? tienes el servicio de sql server iniciado
Por ahi si mencionas el problema, o mensaje que obtienes, pueda dar alguna pista mas precisa del problema puntual
saludos
Buenas tardes,
ResponderEliminarGracias por tu respuesta.
Estoy trabajando con visual studio 2008 y sql server 2008.
Este es el error.
The database 'D:\TRANSACCIONES\[VB.NET]CHINOOK3CAPAS\CHINOOK\BIN\DEBUG\CHINOOK.MDF' cannot be opened because it is version 655. This server supports version 612 and earlier. A downgrade path is not supported. Could not open new database 'D:\TRANSACCIONES\[VB.NET]CHINOOK3CAPAS\CHINOOK\BIN\DEBUG\CHINOOK.MDF'. CREATE DATABASE is aborted. An attempt to attach an auto-named database
hola Lixander
ResponderEliminarPor lo que puedo ver del mensaje de error, es que la version de sql Server Express que usas no es la 2008. Ese mensaje esta indicando que la version del .mdf fue creado con una version mas nueva que el motor de base de datos que tienes instalado.
Prueba de descargar he instalar la version que encuentras en este link
Sql Server Express 2008
Esta es la que yo use
saludos
Leandro, que pena, pero esto afectaria en algo las claves y las bases de datos que tengo registradas en sql server 2008.
ResponderEliminarEn otras palabras me afecta el desarrollo que tengoen sql server 2008
Hola
ResponderEliminarLeandro ¿tiene que ser necesariamente la version express del 2008? o puede ser simplemente la 2008.Además el link que pusiste ahí lleva a la descarga de un ejecutable que parece un virus, luego que elijes a cual de los windows quieres instalar al de 32 o 64.
Saludos.
hola Augusto
ResponderEliminarSi puedes usa la version 2008 del Sql Server sin problemas, recomende la version express porque es lo mas practico para tener en una PC de escritorio, las versiones mayores de sql server suelen estar en servidores de base de datos.
Por lo del virus y el .exe, la verdad no sabria que decirte, accedi al archivo, lo descargue en mi pc y no tuve problema, es mas no hay ningun .exe dentro del zip.
Tampoco tiene ningun instador el ejemplo es un simple zip, no eliges version alguna, estas seguro que es el link del articulo el que estas usando.
saludos
hola, te queria consultar como podria hacer si quisiera en el metodo Save de la capa de negocio, que si ya existe el cliente me envie un mensaje notificandomelo (que no haga el Update) y si no existe que lo cree.
ResponderEliminarMi duda es como pasar este mensaje de la capa de nogocio para que lo tome la capa de presentacion y lo muestre desde ya muchas gracias.
hola Bruno
ResponderEliminardisculpa la demora en contestar
Lo que podrias hacer es lanzar una Exception desde la capa de negocio, que por supuesto atraparas en la capa de datos y mostraras el mensaje.
Lo ideal seria que no sea una Exception cualquiera sino una creada por ti, de forma especial
justamente como explico aqui:
Control de Errores para proyecto en capas
veras en el link que se lanza una BusinessException, indicando que es un error procedente de una validacion del negocio, no es un error cualquiera, por lo tanto lo atrapas y lo muestras de forma diferentes
saludos
hola Leandro gracias por tu respuesta, la duda que tengo es que esta bien si lanzo la excepcion en la BLL y la atrapo en la UI?, no entiendo por que la deberia atrapar en la DAL.
ResponderEliminarYo hice lo siguiente segun el tu articulo del link:
defini una clase BussinnesException en un proyecto Comun, que es donde tb estan las Entidades por jemplo;
namespace Comun.Excepciones
{
public class BussinessExcepcion: ApplicationException
{
public BussinessExcepcion()
: base("Ya existe la Cuenta") {}
luego lanzo la excepcion en la BLL:
public static Cuentas Crear(Cuentas cuentas)
{
if (!CuentasDAL.Existe(cuentas.Nro_cuenta))
return CuentasDAL.Crear(cuentas);
else
throw new Comun.Excepciones.BussinessExcepcion();
}
y luego la capturo en la UI:
try {.........
cuentas = CuentasBLL.Crear(cuentas);}
catch (Comun.Excepciones.BussinessExcepcion ex)
{MessageBox.Show(ex.Message);}
es asi como se propaga una excepcion?
Por otra parte como puedo hacer para que la clase BussinessExcepcion que hice me muestre distintos tipos de mensajes como en el ejemplo que pusiste:
throw new BusinessException("Usuario no existe");
y
throw new BusinessException("Usuario bloqueado");
muchas gracias nuevamente.
hola
ResponderEliminarla duda que tengo es que esta bien si lanzo la excepcion en la BLL y la atrapo en la UI?
disculpa me equivoque al escribirlo, es correcto en UI atrapas la Exception
es asi como se propaga una excepcion?
perfecto has entendi de maravilla es justo donde apuntaba
como puedo hacer para que la clase BussinessExcepcion que hice me muestre distintos tipos de mensajes
podrias usar:
public class BussinessExcepcion: ApplicationException
{
public BussinessExcepcion() : base("")
{
}
public BussinessExcepcion(string message) : base(message)
{
}
}
como veras se define un constructor con el mensaje, y otro sin ninguno.
saludos
Excelente articulo, es uno de los pocos ejemplos claros, entendibles y que ademas funcionan.
ResponderEliminarTe comento que como desarrollador C# siempre chequeo tu pagina en busca de tips.
Saludos
El que tiene otra version de sql2008(no express) lo que puede hacer es:
ResponderEliminarEn el "sqlServer Management Studio" adjuntar la DB
, luego en visualStudio, en el serverExplorer conectarse al servidor, presionar boton derecho sobre la DB, elegir propiedades y copiar el Connection String
, luego ir a app.config y remplazar el connectionString por el correcto
, luego f5
Saludos
Martin
cómo sería el metodo para eliminar una item de compra en la grilla?
ResponderEliminarSaludos
Martin
Primero que nada... felicidades Leonardo, excelente blog!...
ResponderEliminarLa verdad es que sigo tu blog y el de JuanK.. ;)
@Martin
Lo ideal es borrar de la lista el producto, y luego refrescar el DataSoruce del DataGridView..
Una forma seria utilizar la propiedad del DataGridView..
Lista.RemoveAtDGV.CurrentCell.RowIndex);
Luego le pones null al datadource y le asignas la lista.... por desgracia el metodo refresh del DGV no hace ese trabajo :S.
PD: Leonardo, aun sigo esperando como enlazar el errorprovider desde BussinessLayer con la capa de presentacion, para manejarlos de manera centralizada :P
SuerteX :)
hola Alan
ResponderEliminarPero el ErrorProvider es un control que trabaja a nivel de presentacion y la capa de negocio esta desconectada de este
Quizas se pueda enlazar en el evento Validating de algun control, invocando a la funcionalidad de validacion de la capa de negocio, realizando alguna verificacion que devuelva si esta pasa (o no pasa) con la info que se le provea
entonces si la capa de negocio dice que no paso, activas el ErrorProvider en la presentacion, pero no se me ocurre algo centralizado
saludos
Gracias por la publicacion fue de mucha ayuda, pero tengo una gran duda, cual es la mejor forma de manejar las excepciones, por ejemplo un error de conexion a la BD, esta poder pasarla a la capa de presentancion (web form) y almacenar la exepcion original en un log.
ResponderEliminarhola Freddy
ResponderEliminarpoidrias implementar algo como lo comentado en una consulta del foro de msdn
http://social.msdn.microsoft.com/Forums/es/vcses/thread/31410a75-d0a3-4264-b910-324fc8a576d6
tambien podrias implementar un control global de errores
[Winforms] Control global de Errores – Implementar log
saludos
Muchas Gracias Leandro... ya entendi el concepto.
ResponderEliminarEn este caso, utilizarias una capa para el manejo de errores? Como los manejarias?
ResponderEliminarhola Ele
ResponderEliminarlo haria de la misma forma en que lo explique en estos casos
http://social.msdn.microsoft.com/Forums/es/vcses/thread/31410a75-d0a3-4264-b910-324fc8a576d6
http://social.msdn.microsoft.com/Forums/es/vcses/thread/a5d62a43-f4e8-4b9f-b871-f4c02081368d
saludos
Hola. El Using antes de la conexión emula de cierta forma el:
ResponderEliminarconexion.Open()
////
conexion.Close()
?
hola educationchildren
ResponderEliminarel using asegura una seccion en donde la conexion estara activa y disponible
el Open() debes hacerlo, salvo que uses un DataAdapter que lo hace automatico, peor con el ExecuteNonQuery() y con un DataReder debes hacer el open
lo que si no es necesario es el Close
saludos
Leandro una consulta es posible utilizar NuevaLinea() en el evento selectedIndexChanged del dgvLineaCompra pero despues de colocar la cantidad. Asi no utilizo el boton +?
ResponderEliminarGracias
hola Richard
ResponderEliminarpero de esa forma estarias agregando un nuevo registro por cada cambio de seleccion de la fila del grid, no lo vio muy controlado a esa accion
quizas podrias si asignar al grid una tecla que agregue registros, por ahi el Alt+ A, o alguna otra que lance la accion
podrias usar el ProcessCmdKey para detectar esta accion sobre el datagridview
http://social.msdn.microsoft.com/Forums/es/vcses/thread/4fa3d568-5e37-4ec9-bea6-e50da352d65a
saludos
Gracias, lo voy a probar. Feliz Año Nuevo Leandro
ResponderEliminarcomo pasar datos de un texbox a un lixbox
ResponderEliminarhola josias
ResponderEliminarno encontre mucha relacion entre esta pregunta y el tema del articulo
pero bueno, agregar un item a un listbox seria tan simple como usar
private void btnagregar_Click(..){
ListBox1.Items.Add(TextBox1.Text);
}
saludos
Leandro;
ResponderEliminarEnhorabuena por la calidad de tus artículos, y gracias por la labor que haces para la comunidad .NET.
Unas preguntas:
En la aplicaición "[N-Tier] – Desarrollo en capas - Ejemplo Facturación – Parte 3", no veo cómo cierras las conexiones y los datareader, ni tampoco cómo capturas los errores (Try Catch).
He leído """el Open() debes hacerlo, salvo que uses un DataAdapter que lo hace automatico, peor con el ExecuteNonQuery() y con un DataReder debes hacer el open
lo que si no es necesario es el Close""". Pero no es necesario hacer el close del datareader y de la conexión.
¿Alguna explicación?. Muchas gracias. Un saludo.
hola Álvaro
ResponderEliminarlas conexiones no las cierro porque estas las defino en un bloque "using"
este me asegura que el objeto se destruye
no le implemente un control de errores al desarrollo para no complicarlo
pero podria aplicarse algo como ser
[Winforms] Control global de Errores – Implementar Log
exacto con el dataadapter el open no es necesario, con el datareader si
pero el cierre como comente no lo haces porque al salir del bloque "using" se efectua esta operacion
igualmente recuerda que ado.net mantiene un pool de conexion, por lo tanto el cierre en si no es tan asi, sino que mentiene la conexion vigente por si se estable ce una nueva y reutilizar las instancias
saludos
Gracias Leandro;
ResponderEliminarUnas preguntas:
1-En la capa de acceso a datos, ¿hay que crear sólo clases que representen tablas de la BBDD?, es decir, si en la BBDD hay 3 tablas, ¿en la capa de acceso a datos habrá 3 capas?.
2- En la capa de negocio, ¿hay que crear las clases que enlazan con las clases de la capa de datos, como has hecho tú con Customer, Invoice y Track, que existen tanto en la capa de negocio como en la de acceso a datos, más clases que no tienen correspondencia con tablas en la BBDD, pero tienen cierta lógica de negocio, como pudieran ser clases para elaborar informes, y que van a llamarse desde los formularios de la capa de presentación?.
Gracias, un saludo.
hola Álvaro
ResponderEliminar1 -
imagino habras querido decir que debe haber 3 clases, en lugar de capas
en principio no es una regla que sea asi, si es lo mas comun, pero podrias hacer que una tabla se represente por mas de una clase, porque en .nmet pdorias representar herencia y demas asociaciones que mepeen a mas de una tabla
2-
la respuesta es similar a ala enterior, no es una regla que sea asi, podrias crear funcionalidad con clases con nombre difernte en la capa de negocio que haga uso de la capa de datos de forma distinta
por ejemplo podria crear clases de negocio relacionado a un CRM que haga uso de la info del customer, en este caso la clase ya no tendran uno a uno en el nombre
recuerda que aqui implemente un ejemplo simple que puede aplciar los conceptos basicos, mientras se respeten las responsabilidades de cada clase alcanza para que sea separacion en capas, despues los nombre pueden variar, o sea mientras desde la presentacion no te conectes directo a los datos, eso si seria algo incorrecto
saludos
Hola, gracias por los aportes. Tengo 2 dudas a ver si podes ayudar. Estoy con una app para windows, si uso base de datos sqlserver es necesario instalar el servidor en la pc cliente?. y la otra es que neceisot saber saber como usar las consultas q tengo guardadas dentro de la base de datos access.
ResponderEliminarSaludos y gracias
hola Juan
ResponderEliminarel servicio de sql server siempre es necesario instalarlo, ya sea en la pc del cliente o en una pc que actuara como servidor
no entendi lo de la consulta en access, te refieres a una vista ?
saludos
Hola, access te permite guardar dentro de la base de datos (tablas, vistas, macros, formularios y consultas). Mi idea seria definir las consultas ahí y poder usarlas "tipo" procedimiento almacenado. Gracias
ResponderEliminarhola Juan
ResponderEliminarvistas y tablas eso no hablia problemas es lo que normalmente se usa en una db
por consultas imagino seria lo mismo que vistas
lo que no estoy seguro es que tan bueno es access con los stored procedure
aqui se trato el tema
http://social.msdn.microsoft.com/Forums/es/vbes/thread/d3167c4d-8ca2-41d4-94a5-6405ea35c104
usarias lo recomendado por Enrique para ejecutarlos
pero me pregunto no te animas a usar sql server express, no es tan complciado y es muchas veces mas robusto que access
saludos
Hola Leandro,
ResponderEliminarAntetodo muchas gracias por el aporte. Lo he realizado con exito.
Quisiera saber si me puedes ayudar en aumentarle alguna funcionalidad como es la modificacion de una factura ya existente.
1. Como hacer para que una SELECT me presente datos de mas de una tabla, esto lo estoy haciendo con el entity de la factura al cual le he añadido campos para que me devuelva un registro de la factura y ademas el apellido del cliente. Al InvoiceEntity.cs le he añadido la propiedad apellido, se que quizas no se debe asi, lo estoy haciendo solo para probar y si me da el resultado que deseo. Esto, con el fin de usarlo en una ventana analoga a la ventana de buscar cliente.
2. Como se haria la programacion para modificar una factura(cabecera y detalle) considerando que cuando extraemos una factura de la base de datos, logicamente en el cliente le podemos añadir, modificar, borrar items en el detalle.
Muchas gracias de antemano por las pistas y ayuda en general a este tema.
Saludos,
Eusebio.
hola eestradaa
ResponderEliminarpero se supone que la factura tiene uan relacion con la tabla clientes, por lo que seria una asociacion con esta entidad, no necesitas de una propiedad apellido porque se supone que debes poner una propiedad que sea
public ClienteEntity Cliente {get; set;}
para asi cargar los datos completos del cliente asociado a la factura
La modificacion no seria muy distinta al alta, solo cambiando los INSERT pot UPDATE, por el unico caso del detalle, porque se supone que puede agregar eliminar o actualizar una linea lo cual implica un MERGE de toda la informacion con resepcto a la que se tiene aun en la tabla de la db, esto puede ser algo complejo, por eso se suele quitar todas las linea para la factura he insertar nuevamente, quizas no sea lo mejor pero hacer una comparacion completa podria ser algo complejo de programar
saludos
En tu comentario: "mas adelante cuando se implementen servicio para distribuir la aplicación cumplirá un papel
ResponderEliminarfundamental para aislar el dominio." podrias hacer o tener un ejemplo para la distribucion de la aplicacion segun la metodologia que haces aqui?
Aunque los Datareader consumen menos recursos que los DAdapter y Dset en un ambiente totalmente desconectado no nos traeria problemas?
hola greg_dorian
ResponderEliminarestoy pensando en armar un ejemplo usando servicios para mostrar como seria la comunicacion
pero queria basarlo en algo mas simple, aun no lo tengo definido
no deberia tener problemas, he usado reader para cargar entidades de clase mucho tiempo y no se han presentado inconvenientes
nunca te has parado a pensar como se carga un dataset? o que crees que usa internamente el DataAdapter cuando haces un Fill(), si usa un datareader para cargar al estructura del datatable
saludos
gracias por todo felicitarle por la enseñanza
ResponderEliminarHola Leandro, tus post son muy instructivos, quisiera que libro me puedes recomendar para aprender todo sobre Desarrollo en Capas o algunas pagina en especial.
ResponderEliminarSaludos
Fritz
Buenas Leandro, como todos los demás te felicito por lo buenos de tus post y lo profesional de tu manera de programar. Te quiero hacer una pregunta breve y otra mas extensa pero que tienen relación entre si.
ResponderEliminarPrimero: ¿porque no se usa en este post la comunicación entre formularios por medio de una interface como lo haces en tu post http://ltuttini.blogspot.com.ar/2010/01/c-datagridview-parte-3-pasaje-de.html?, el mismo me parece muy bueno y me brindo mucha ayuda.
Por otro lado, y también relacionado a lo mismo, tengo 3 formularios, Principal, Clientes y Ciudades. Ciudades es llamado tanto desde Principal como desde Clientes, con la diferencia que al abrir Ciudades desde Clientes necesito que me devuelva el nombre de la ciudad seleccionada en una grilla. Pero cuando se llama desde Principal, no tiene que devolver nada.
No se si me aclaro?, use la interface de tu otro post, pero como la llamada al evento implementado de la interface de comunicación lo hago desde el closing de ciudades, se me ejecuta siempre (sea que a Ciudades lo abrió Principal o lo abrió Clientes).
No quiero liarme con la explicación, si hace falta paso el código para que se entienda.
Muchísimas gracias desde ya.
Hola de nuevo Leandro, creo que voy a abandonar la idea del la interfaz para comunicación entre formularios y adoptar el mismo sistema que haces en este ejemplo. De por si ya estaba trabajando con formularios dialog (o modal, ¿es lo mismo no?), solo tengo que crear en Ciudades la propiedad que voy a pasar al form Clientes. Me inclino por esta solución. Saludos.
ResponderEliminarhola Mariano
ResponderEliminaraqui no se uso ese tipo de comunicacion porque el form de busqueda se abria de forma modal, por lo que se debia cerrar el form hijo para poder continuar con al ejecucion del form padre, esto ahce que la comunicacion entre lso forma peuda realizarse de formas mas directa y simple
Entiendo lo del Ciudades y Clientes pero lo interacion con el Form principal no cierra del todo, porque quierwes invocar un form que muestra ciudades sino va a devolver informacion ?
mas alla de eso podrias simplemente no asignar nada en el owner por lo tanto al usar el "as" para castear comod evolvera null no invocar aningun metodo en el form padre que invoco al hijo
saludos
Hola Leandro, gracias por responder,Listo, ahora entiendo mejor porque no se uso ese tipo de comunicación.
ResponderEliminarCon respecto a lo de llamar a Ciudades y no devolver informaciòn es porque en mi proyecto uso el mismo formulario Ciudades tanto para elegir una ciudad y devolverla a Clientes como para ABM de ciudades. Todo en el mismo formulario. Por eso si el usuario desea solo hacer ABM de ciudades accede desde el formulario principal.
Cito: "mas alla de eso podrias simplemente no asignar nada en el owner por lo tanto al usar el "as" para castear comod evolvera null no invocar aningun metodo en el form padre que invoco al hijo"
Si no entiendo mal me decís que en lugar de hacer esto:
Dim FormCiudades As New frmABMCiudades
FormCiudades.ShowDialog(Me)
Haga esto:
Dim FormCiudades As New frmABMCiudades
FormCiudades.ShowDialog
Si es así, yo lo intenté, pero en el evento closing de frmABMCiudades estoy haciendo esto
If (Me.Owner.GetType Is GetType(frmABMClientes)) Then 'Han llamado desde el Formulario de tipo frmABMClientes
'Paso a travez de la interface la ciudad seleccionada
Dim Ciudades As New ClientesBO
Dim _formInterface As ComunicacionI = CType(Me.Owner, ComunicacionI)
_formInterface.ComunicaString(dgvCiudades.Item(1, dgvCiudades.CurrentRow.Index).Value())
End If
Yo necesito, y lo hago en la linea del If, consultar si o si quien es el owner de la instancia de frmCiudades, porque así como la invoca Clientes, lo podía hacer frmProveedores y también lo hace frmPrincipal. A veces devolviendo y a veces no un resultado.
Esto tiene el problema que me dice que si no tiene owner me tira error, ¿puedo consultar si el owner es null? o ¿no existe?.
Esta consulta ya es mas por curiosidad que otra cosa, ya que evaluando un poco el articulo actual me incline por crear propiedades en la clase frmCiudades que modifiquen su comportamiento, es decir que establezcan si se tiene o no que devolver un valor, luego, como en el articulo presente en el padre consulto por el resultado de dialog.
hola
ResponderEliminarno necesitas hacer esto
If (Me.Owner.GetType Is GetType(frmABMClientes)) Then
podrias usar
Dim _formInterface As ComunicacionI = TryCast(Me.Owner, ComunicacionI)
If _formInterface IsNot Nothing Then
_formInterface.ComunicaString(dgvCiudades.Item(1, dgvCiudades.CurrentRow.Index).Value())
End If
como veras con el trycast puedes evaluar si convierte al tipo que defines
saludos
Si Leandro, acabo de probarlo y me funciona de maravilla el TryCast, no lo conocía. Muchas gracias por la ayuda.
ResponderEliminarhola Fritz
ResponderEliminarse me habia olvidado responderte
Guía Arquitectura N-Capas DDD .NET 4.0
esa guia esta muy buena
saludos
Muchas gracias Leandro! Saludos desde Pucallpa - Perú.
ResponderEliminarHola Leandro muy buenos artículos los 3 sobre el desarrollo en capas.
ResponderEliminarSolo tengo una duda para que recuperas el id a la hora de insertar el registro? yo entiendo que lo haces para poder retornar toda la entidad, pero me gustaría que me expliques cual es la finalidad de capturar el id a la hora de hacer el insert.
hola Cristian
ResponderEliminarlo que sucede es que se usan campo del tipo identity, por lo que solo al insertar es que conoces el id que se genera
la idea es poder devolver la entidad con este dato nuevo para poder ser usado (si se necesita) desde el negocio o la presentacion para informar al uusario que id o codigo se dio de alta
es solo para devolver a modo informativo el id generado en la operacion
saludos
Hola Leandro. He leído brevemente tu aplicación y me surgieron dos preguntas:
ResponderEliminar1) Veo que en tu capa de datos haces una conexión a la base de datos en cada consulta. Quería saber tu opinión de abrir la conexión una sola vez cuando se crea la clase.
2) Tu base de datos y app_config están en la capa de presentación. He visto en otros proyectos que la agregan en la capa de datos. Quería saber que diferencia hay en hacerlo así.
Muchas Gracias!
hola Arévalo
ResponderEliminar1- algo que debes conocer es que ado.net administra internamente un pool de conexiones, al cerrar desde codigo la conexion al salir del using no es que se cierra realmente, ado.net la mantine vigente un tiempo adicional por si se vuelve a invocar, es por eso que no se necesita de ninguna clase que abra globalmente una conexion y se use solo esa, ado.net lo resuleve por nosotos
Agrupación de conexiones en SQL Server (ADO.NET)
2- las aplciaciones web proveen un lugar para alojar la base de datos que se adjuntan dinamicamente al servicio de sql server
otro tipos de desarrollo como ser lo de escritorio no tienen esta capacidad por eso lo dejo en la capa que pertence y con un Build Event se podria crear una copia a el \bin\Debug
o si se deja en el proyecto de UI se copiara solo
saludos
Te felicito con por el artículo.
ResponderEliminarQuería saber si en la capa de negocio está bien incrustar sentencias SQL o todas las sentencias SQL deberían estar definidas en la capa de datos (DAL).
Porque a mi parecer incluir sentencias SQL en la capa de negocio (BL) no es correcto, pero es ahí donde tengo la duda.
Gracias por tu articulo y tu ayuda.
Muy buen articulo, te felicito.
hola Diego
ResponderEliminares tal cual como lo mencionas, en la capa de negocio no puede haber sql, tampoco referencias a ninguna dll referida al accceso a datos
en la capa de negocio no se puede usar clases que tengan que ver con ado.net
saludos
Este comentario ha sido eliminado por el autor.
ResponderEliminarhola Gabriel
ResponderEliminaralgo que no has mencionado es como se relaciona un contrato o la linea del contrato con las otras entidades como ser concepto y recurso
porque en el grid vas a mostrar conceptos y recursos, pero entonces que tienen que ver las listas del contrato ?
las relaciones que comentas estan correctas, pero te encuantras con una limitacion del propio control DataGridView, este no muestra entidades complejas
es por eso que podrias crear una clase que sea solo usada en la vista para convertir tu entidad de negocio en una de presentacion, o sea definir una clase que solo tenga propiedades simples para convertir la entidad compleja que recuperas del negocio y la conviertas en la simple para poder asignar al control grid
saludos
Este comentario ha sido eliminado por el autor.
ResponderEliminarhola Gabriel
ResponderEliminarentiendo la estructura que tienen las entidades, pero no me queda claro el problema que planteas y lo que mencionas de la reutilizacion
lo que mencionaba es que para mostrar info en el grid debes aplanar la informacion ya que algo jerarquico no puedes asignarlo
pero la nueva clase que definas seguro duplica datos porque un concepto tendra varios recursos
quizas sea un tema para tratar en el foro, porque puede que sea algo extendo y necesites poner codigo para analizar el problema
foro c#
saludos
Muy bueno Leandro, yo lo actualice al vb.net 2010 y agregue el modelo.emdx en la capa de negocio. me interesa lo de repositorio. ya que tengo un desarrollo en DDD usando dominio.
ResponderEliminarhola gabymonte
ResponderEliminarpero en el caso del articulo no se usan repositorios
pero me parecio raro lo que comentas del edmx para el negocio, no seria mas que nada para la persistencia?
saludos
Hola Leandro, te hago una consulta.
ResponderEliminarTengo que implementar el patrón singleton y estoy haciendo un ejercicio basado en tu ejemplo. Las únicas instancias que veo en el ejemplo son hacías la capa Entities.. es ahí donde debo implementar el patrón singleton?
hola
ResponderEliminarno para anda, las entidades no pueden aplicar el patron singleton
quizs podrias aplciarlo en la persistencia o en el negocio, pero si vas a hacer esto es mejor definirlo como static
no veo que singleton aplique en un modelo en capas
saludos
Hola, Excelente Blog.
ResponderEliminarQuisiera que me ayudaras con unas dudas:
Estoy por desarrollar un sistema de Solicitud de adelanto de gastos, aprobación de viajes, requisiciones (consumibles y activos fijos)
e hice la base de datos que son como 14 tablas (que se interrelaciona en tre si) y quiero desarrollarlo en capas, pero tengo la duda
de si la cantidad de DB es la equivalente para las clase?
La capa de presentacion puede ser hecha con form? Y como seria para las capas de negocio y de acceso de dato?
hola Weimen
ResponderEliminarcomo es eso de la cantidad de DB, sera la cantidad de tablas
si bien hay una correspondencia entre una tabla y uan clase, no es una regla, puede haber clases que mapean a mas de una tabla, esto se da sobre todo si implementas herencia
la capa de presentacion es la que interactua con el usuario, seguro sera un form
como sera el negocio o datos, la verdad no se, eso depende mucho del dominio de tu aplicacion
saludos
Hola, gracias por responder.
ResponderEliminarY de casualidad no tendras algun ejemplo de: clases que mapean a mas de una tabla implementando herencia
hola
ResponderEliminarquizas este articulo ayude un poco
n-Layer - SchoolManager - Herencia ...
alli explico como definir herencia y persistirla
saludos
Hola Leandro, me gustaría saber porque en las capas de datos, entidades y negocios le asignas una clase a cada "ítem" de tu negocio. Es una forma de organizarte mejor? o estas aplicando una técnica? Gracias..
ResponderEliminarhola Si Señor
ResponderEliminaren realidad la asignacion no siempre es directa, por ejemplo analiza la entidad InvoiceLinesEntity, veras que para esta entidad no hay capa de negocio, ya que es la factura quien administra las lineas
por lo general por cada entidad tendras su negocio y persistencia, pero no es una regla exacta
alli estoy modelando algo basico, el negocio expone funcionalidad que le UI requiere puede coincidir o no con las otras capas
saludos
Hola Leandro.
ResponderEliminarTengo una web en la que obtengo los datos de un Datos.xsd y luego trabajo con ellos en cada uno de los WebForms, no trabajo ni con entidades, esto es realmente un CAOS
Estoy trabajando con WebForm, supongo que es totalmente válido también este modelo para poder aplicarlo en mi proyecto ¿no?, ya que prefiero adaptarlo de esta manera que tener que usar(aprender,aplicar) MVC ASP.NET.
Dejando mi proyecto web tal y como está (UI) y creándome las bibliotecas de clases de Lógica de Negocio, Acceso a Datos y Entidades y realizando las referencias que indicas ¿no?
Y una segunda duda.
He visto que algunos ejemplos usas una excepción propia BusinessException, ¿ésta sería una manera correcta de crearla?
http://www.codeproject.com/Tips/90646/Custom-exceptions-in-C-NET
¿Sería mejor crear una excepción por cada BO?
Me están sirviendo de gran ayuda tus ejemplos, muchas gracias por todo el aporte que proporcionas.
UN SALUDO!
hola misretoques
ResponderEliminarel modelo que aqui explico es completamente valido para aplicarlo en un desarrollo web
solo cambiarias la UI, el resto de las capas se conservan identicas
Es correcto crear un exception como la del articulo, pero solo crea una sola, despues por cada error que se genere le cambias el mensaje que devuelve
saludos
Hola, muy claro el ejemplo, la verdad que ayuda mucho a entender la interaccion entre las capas. Simplemente tengo una duda con respecto a este tema. Si se tuviera que validar que el usuario no ingrese cantidades con valores negativos o cantidades mayores al stock actual de productos. Donde deberia ubicarse esa validacion? En que capa corresponderia? Sería en la de presentación? Gracias de antemano por la ayuda. Saludos!
ResponderEliminarhola Tomas
ResponderEliminarlo que planteas veo dos validaciones, un stock negativo y otroa que no supere el stock existente
la primera se podria realizar en la presentacion verificando que no se ingrese un valor menor a cero, pero tambien deberias realizando nuevamente en la capa de negocio
la del stock superior al existente eso claramente va en la capa de negocio, ya que deberias recuperar el producto para conocer su stock y luego validar
en resumen todas las validaciones van en la capa de negocio, pero aquellas simple que puedes realizar tambien en la presentacion, en esta se ponen solo validaciones simples como ser tipos de datos, validacion por rango, etc, pero todo despues se valida en el negocio nuevamente
saludos
Hola Leandro.
ResponderEliminarMe ha surgido otra duda. Quiero capturar las excepciones que puedan surgir en la capa de Acceso a Datos. Y también por ejemplo las que puedan surgir en la Lógica de Negocio.
Mi duda es, lo habitual que sería, ¿crear una excepción propia para DAL y otra para BO?, las excepciones que debería crear ExceptionBO en la biblioteca de clases BO y ExceptionDAL en la de DAL?
¿Cómo se suele realizar el tratamiento de errores para N-Tier?
Un saludo y gracias.
hola misretoques
ResponderEliminarno creo que haga falta diferenciar con custom exception los errores de DAL o BO, podrias simplemente definir excepciones tecnicas y de negocio, entonces las de acceso a datos podrian ser consideradas tecnica
http://social.msdn.microsoft.com/Forums/en-US/vcses/thread/31410a75-d0a3-4264-b910-324fc8a576d6
saludos
Hola, tu ejemplo dice N-Tier - Desarrollo en capas, pero N-Tier con capas no son lo mismo, de hecho podrían trabajar juntos, tu desarrollo es N-Layer (N-Capas). N-Layer o N-Niveles se refiere a la separación física de tu lógica de aplicación, esto se podría hacer, por ejemplo, con WCF, teniendo tu presentación (cliente) consumiendo tus servicios que se encuentran en otro servidor físicamente separado.
ResponderEliminarSaludos cordiales
Hola Leandro.
ResponderEliminarTengo una pregunta, si yo quisiera implementar un boton Eliminar Detalle, por ejemplo agrego un producto a la grilla detalle pero me equivoque y quiero eliminar esa linea de factura como lo haría por que cada ves que quiero eliminar me aparece un error que dice "el indice esta afuera del intervalo, tiene que ser un valor no negativo e inferior al tamaño de la coleccion", intente de varias formas pero siempre me sale el mismo error.
hola Sergio
ResponderEliminarrecuerda que debes enviar a la capa de negocio el id de la entidad que estas mostrando en el grid, y no el index de la row del control grid
tambien valida que se este seleccionando un row del grid cuando presionas el boton de eliminar
si detectas que
if(datagridview1.CurrentRow == null){
//aqui informas que no hay una fila seleccionada
return;
}
o sea informas y no continuas
saludos
Hola Leandro.
ResponderEliminarEstoy trabajando con Web Form (vs 2010, c# asp.net 4.0) y el llamado código spaguetti. Me he propuesto separar mi código en capas para que sea más legible y mejor escalable..
En mi código tengo un DataSet.xsd en el cual tengo las tablas con DataTable y luego cada una de ellas tiene sus TableAdapater que llaman a procedimientos almacenados de mi BD.
Tenía un fichero Logica.cs donde tenía los métodos a los que llamaba desde las distintas páginas.
Ahora he separado como has indicado
Entity -> donde he metido mi DataSet1.xsd
DAL -> donde uso los DataTable y DataAdapters de la capa Entity para acceder a la BD
BL -> donde llamo a los métodos de DAL
Web -> donde llamo a los métodos BL
Mis dudas son:
1. ¿Está bien separado el Entity poniendo el DataSet1.xsd ahí?
2. En el DAL estoy llamando a los DataAdapters, ¿sería mejor quitarlos del Entity y en la capa de acceso a datos llamar así a los métodos?:
DataSet1.partidosDataTable dt = new DataSet1.partidosDataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("partidosSelectProximos", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dt);
}
}
return dt;
O mejor seguir usándolo:
partidosDataTable adaptador = new partidosDataTable();
DataSet1.partidosDataTable datos = new DataSet1.partidosDataTable();
adaptador.FillProximos(datos);
return datos;
Muchas gracias y un saludo.
hola misretoques
ResponderEliminarla verdad recomendaria que en lugar de dataset tipados uses entidades con clases
aunque podrias analizar la parte 2 del articulo, alli uso dataset en capas
en realidad usarias los dataset tipados para definir las entidades pero estas no tendrias logica de persistencia, o sea no se usarias los tableadapter porque no se puede separar uno de otro
saludos
Hola Leandro.
ResponderEliminarDe momento me gustaría realizarlo poco a poco, aunque eso me lleve el doble de trabajo.
Por lo que mi intención es usar DataSets. Para saber si te entendido bien, lo que me comentas es que 'sí' que debo separar la lógica de persistencia (TableAdapters) de los DataSet (Entities), ya que si dejo los TableAdapters en Entities se podría acceder a la lógica de persistencia desde la WEB al realizar una instancia de un DataTable. ¿no?
Por lo que me debería quedar así:
Entity -> DataSet1.xsd (Sin TableAdapaters)
DAL -> Métodos estáticos donde llamo a los procedimientos almacenados de la BD de esta manera:
DataSet1.partidosDataTable dt = new DataSet1.partidosDataTable();
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = new SqlCommand("partidosSelectProximos", conn))
{
cmd.CommandType = CommandType.StoredProcedure;
conn.Open();
SqlDataAdapter da = new SqlDataAdapter(cmd);
da.Fill(dt);
}
}
return dt;
BL -> donde llamo a los métodos de DAL
Web -> donde llamo a los métodos BL
Un saludo y muchas gracias por la información compartida. GRACIAS.
hola Unknown
ResponderEliminarexacto ese es el problema, los datatable y tableadapter que estan en un dataset tipado no se pueden separar, por lo tanto si defines un dataset tipado como modelo de entidades y este lo referencias en la UI habilitarias a que desde alli se usa el tableadapter directo, lo cual rompe con al separacion en capas
el resto del codigo es correcto, defines dataset sin tableadapter, de esta forma puede separar las responsabilidades y crear tu propia persistencia
saludos
Hola Leandro.
ResponderEliminarEn el DataAccess estás usando:
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
¿Se podría hacer de la siguiente manera?
public static class ClassDAL{
private static string connectionString = ConfigurationManager.ConnectionStrings["default"].ToString();
...
public void MetodoDAL(){
using (SqlConnection conn = new SqlConnection(connectionString))){...}
}
}
¿Puede ser static el string de la conexión?, ¿aconsejas ponerle o no al string static?
Muchas gracias por el artículo me ha servido mucho de ayuda.
hola Unknown
ResponderEliminarsi se podria hacer lo que planteas
aunque la asignacion seria mejor si la realizas usando
Constructores estáticos (Guía de programación de C#)
o sea defines la variable static pero la asignacion del dato desde el ConfigurationManager la realizas en el constructor static
saludos
Hola tuttini gracias por tu aporte, pero hay manera que hagan un tutorial parecido pero donde empieces de una vez en capas. La verdad es que así no entendí nada. no se si usas algunas referencias que no dices pq no me corrió el programa.pero igual se me hace complicado como lo haces todo revuelto, luego sacas una capa y después la otra. de llegar a entenderlo me tocaría hacerlo igual que tu tuto, hacer la api completa como siempre y luego ir sacando capas. La idea seria aprender a hacer un desarrollo en capas así sea básico pero desde cero. Te agradezco si lo tienes en cuenta, ando muy interesado en aprender pero ese método que usas no lo entiendo. no he podido avbanzar
ResponderEliminarhola CRISTIAN
ResponderEliminarpero este articulo si aplcia capas desde el inicio, los otros dos son opcionales para poder ver la transformacion, peor los anteriores no son necesarios analizalros sino lo necesitas
veo que comentas que el programa no corre, estas obteniendo algun mensaje de error en concreto ?
a que llamas api completas ?
puedes definir algun parte en concreto que no se entendendio? es la persistencia, o quizas la comunicacion entre las capas?
o sea hay algun punto en concreto que causa duda he impide entender
saludos
Es como inicias, en este articulo inicias con este codigo if (cliente == null)
ResponderEliminarcliente = new CustomerEntity();
cliente.FirstName = txtNombre.Text;
cliente.LastName = txtApellido.Text;
cliente.Company = txtCompañia.Text;
cliente.Address = txtDireccion.Text;
cliente.Email = txtEmail.Text;
cliente = CustomerBO.Save(cliente);
Pero no tengo idea donde se pone para poder empezar. Otra duda que tengo es como empiezas? creas una solucion vacia y vas agregando proyectos o crear lo normal la aplicacion en winforms y agregas los proyectos de librerias de clases?? hay alguna diferencia?. Otra cosa, en lo del ejemplo me refiero con que solo pongas un pequeño ejemplo donde se inserte un cliente en la base de datos y luego se consulte y en capas ya podria entender. Se me hace muy complejo el articulo.
En este articulo explican la programacion en capas de un modo en que lo entendi http://dotnetfyi.wordpress.com/2009/08/14/desarrollo-de-aplicacion-en-capas-con-visual-studio-2008/ explica desde lo mas basico asi pude arrancar pero me genera un error con el datetime, hay quede parado, me indicaste que esta mal la parte de insertar datos pq no usa parametros pero para entender lo de capas me sirvio mucho. Te agradesco si me puedes colaborar para aprender el desarrollo en capas que es muy importante
hola CRISTIAN
ResponderEliminarpuedes empezar analizando desde uno de los extremos, alli veo que muestras codigo de la presentacion, por lo que si vas navegando la funcionalidad podrias ir de alli al Save() de la capa de negocio y luego de alli a el codigo de la persistencia, es una secuencia de llamadas que pasa de una capa a otra y cada una agrega su funcionalidad, no se si has estudiado redes, es igual al Modelo OSI
puedes empezar de ambas formas, con una solucion vacia y vas a agregando proyecto o empezar creando el proyecto de presentacion y a este le agregas las dos o 3 proyectos Class library para el resto de las capas, ambos caminos son validos
si el ejemplo de ese articulo resulto util toma los conceptos de alli y solo cambia la parte donde define la query usando parametros y solo esto
por lo que vi no difiere mucho a lo que planteo solo que en mi ejemplo es algo mas grande, pero creo que si emepiezas en una capa y los vas navegando por los metodos deberias poder entenderlo
saludos
Hola, me fue util pero tengo un error, dice que el formato de la fecha no es valido, si me puedes colaborar te lo agradesco.
ResponderEliminarVolviendo al tema de crear el proyecto, Cual de las 2 formas es la mejor?? la recomendada?? Creando el proyecto vacio y agregar los proyectos o crear el proyecto de interfaz y agregar los proyectos?? Me gustaria aprender de la forma correcta, por eso estoy tratando de entender tu tutorial pero me ha resultado dificil. Igual ya lo he leido varias veces y no parare hasta entenderlo ya que es muy completo tu tutorial
Hola Leandro. primero quiero dar gracias por tus grandes aportes me han servido mucho. Me he basado en esta arquitectura de capas para generar un proyecto para Winform. Ahora debo hacer otro en ASP MVC basandome en las mismas capas. La duda que tengo es al crear el modelo de una pagina para mostrar por ejemplo los datos de un Proveedor. Veo que en el modelo debiera usar la misma estructura que he creado en Entity (capa). Como se debiera manejar esto, o algún enlace donde encontrar mas informacion.. Muchas gracias!
ResponderEliminarhola Marcos
ResponderEliminarbueno en este punto no hay una ciencia exacta, hay quienes te diran que uses la clase que defines en entity como modelo de la view (esto lo veras mucho en ejemplos con entity framework) y hay quienes te diran que debes crear una clase Model que hagas mapear contra la que defines en el dominio
ambas opciones son validas aunque personalmente prefiero definir un Model separado de la entidad y usar automapper para convertir de uno a otro en la capa de servicio
entonces podrias tener entidades que definen tu dominio y entidades de presentacion que usas en la view
la capa de aplicacion o servicio separa ambos mundos, usando automapper para convertir de uno a otro
saludos
Hola Leandro.
ResponderEliminarUna duda respecto a las N-Capas.
Esas n-capas entiendo que cada una puede ir alojada cada una de ellas en un servidor distinto ¿no?
Mi pregunta sería:
¿Estaría bien crear un WebService, por ejemplo para llamar a métodos de la BL, y otro WebService en DAL que sólo lo pueda llamar la BL, y la parte de presentación ya podría ser dispositivos Móviles, App escritorio, Web... que llamen al WebService de la BL.?
¿Qué opinas?
Un saludo, gracias por el tuto.
hola Unknown
ResponderEliminaryo aconsejaria que con solo hacer remota la fachada de negocio alcanza y sobra
no veo que sea necesiria otro servicio para los datos, eso estaria de mas
con solo exponer mediente servicio el negocio alcanza
si te animas para los servicio utiliza WCF o Web API
saludos
hola leadro muchas gracias por el codigo . pero me ah salido un error como hago si tengo un campo con valor null . ya no me quiere cargar la grilla estoy trabajando en vb.net
ResponderEliminarHola Leandro, estoy siguiendo tu post de N-Tier, y ya tengo montado el proyecto, pero me ha surgido una duda.
ResponderEliminarTengo varias entidades relacionadas entre ellas y lo he montado siguiendo N-Tiers como indicas en el post
Tengo:
PedidoEntity -> Que tiene List()
DetallesEntity -> Que contiene ArticuloEntity
ArticuloEntity -> Que tiene List()
El ejemplo también es bastante simple, pero ésto puede crecer todo lo que se quiera.
Cuando obtengo un PedidoEntity tengo un PedidoDAL.GetAll() éste recupera PedidoEntity sin relaciones, he pensado en crear un PedidoDAL.GetAllWithRelationship() que obtenga las relaciones que tiene con las otras entidades, pero éstos a su vez traeran sus relaciones...y en el caso que sean muchos datos puede tardar mucho en cargar datos.
¿Cómo podría resolver éste problema?, he leídojavascript:void(0) que en ASP.NET 4.0 trae la clase System.Lazy, ¿ésto me podría ser de ayuda?, en el caso que sea sí, ¿Dónde debería poner el Lazy en DAL o en el Entities?
Muchas gracias Leandro.
hola luis
ResponderEliminarpero ese campo null es un valor que recuperas o lo envias para filtrar los datos como parametro de una query ?
sino quieres filtrar simplemente podrias validar si ese valor llega en null y no haces nada
ahora si es un valor que recuperas podrias tambien verificarlo usando
If reader("nombrecampo") IsNot DBNull.Value Then
'aqui accedes al campo
End If
saludos
hola Unknown
ResponderEliminarno evaluaste usar un ORM, como ser Entity Framework ?
con este temas de lazy load son muy simple de implementar
hacerlo a mano lo que planteas no hay muchas alternativas mas que la que comentas de crear un metodo Getall() que retorne la entidad simple y otro que retorne la entidad y sus relaciones
aunque si podrias analizar
n-Layer - SchoolManager - Herencia y navegación de entidades relacionadas (2/2)
con linq esto podria ser mas simple
saludos
Hola Leandro.
ResponderEliminarEstoy Aplicando tu Ejemplo con una base de datos MySQL con la conexion no hay ningun problema y extraccion de datos tampoco, el problema esta al momento de mostralos en un datagridview tengo una tabal Area de 2 campos el id y el Area propiamente dicha, pero en la grilla no se muestra el id, ya puse las propiedades de bindeo pero nada
que podria estar fallando gracias por la respuesta y el tiempo.
hola Sr. Hueveras
ResponderEliminarentiendo que las columnas las defines en tiempo de diseño y especificas el DataPropertyName de cada columna
defines en la del area el nombre de la propiedad, pero ojo tiene que se de tipo simple, ya sea int, string, etc si defines la propeidad de un tipo complejo no vas a verla correctamente
sino podrias dejar que las columnas se creren de forma dinamica para ver que aparece, solo no definas el AutoGenerateColumns = false, sino lo pones entonces se crearan las columnas para ver que nombres se crean
saludos
Hola Leandro, he separado mi proyecto como indicas en tu ejemplo.
ResponderEliminarEstoy intentando usar TransactionScope, lo que quiero hacer que cuando alguien vaya a empezar un pedido, inicializar un pedido y asignárselo a una cesta nueva. Lo que quiero que suceda es que si por cualquier cosa al insertar la cesta falla haga un rollback y el pedido no se inserte.
¿Ésto podría hacerse?
using(TransactionScope scope = TransactionScope())
{
PedidoEntity pedido = new Pedido(){...};
int pedidoId = PedidoDAL.Save(pedido);
cesta = new CestaEntity(){
PedidoId = pedidoId,
UsuarioId = -1 //intento generar un error
}
CestaDAL.Save(cesta);
scope.Complete();
}
Si ejecuto esto en la capa de negocio, me inserta el pedido, y cuando va a insertar la cesta falla, y se queda insertado sólo el pedido.
¿Ésto podría controlarlo?
Indicar que en el DAL, estoy llamando a Procedimientos Almacenados de mi BD.
Un saludo y gracias
hola misretoques
ResponderEliminarpero si genero un error se supone que no deberia pasar por el scope.Complete()
dentro del Save() de la DAL defiens algun try..catch ? porque recuerda que deberias lanzar ese error hacia fuera para que salte el complete
saludos
Hola Leandro ya no está el código para descargar, donde lo puedo conseguir.
ResponderEliminarhola buenas noches queria descargar el codigo en c# [csharp]Chinook3Capas.zip
ResponderEliminarpero no esta el link disponible excelente aporte genio...
hola Juan
ResponderEliminarya estan los links actualizado
salydos
Una pregunta leandro,
ResponderEliminaren el caso de usar un data binding como hiria la distribucion en el procedimiento n-tier
y otra cosa mas si ya al realiza la aplicacion me marca errores de dependendia de la aplicacion los dlls que te genera donde hirian instalados ya que la aplicacion estaria redidente en un servidor
Dale Gracias buenísimo sos un capo
ResponderEliminarJustamente estoy buscando algun software o Hanger factory que sea util en mi negocio
ResponderEliminarhola Maru
ResponderEliminarno entendi la consulta
saludos
Leandro como estas, agradeciendote de nuevo, queria molestarte con algo que me preocupa para una aplicacion que estoy desarrollando y es la inyeccion de codigo sql, ya valide caracteres especiales pero pues viendo bien una inyeccion podria ser "select ciudad from pais" como podria implementar la restriccion para inyeccion de codigo te agradezco de antemano
ResponderEliminarhola ksoto
ResponderEliminarno entendi, a que llamas inyeccion de sql ? definir un SELECT completo no es inyeccion
imagino apuntas a la vulnerabilidad de Sql injection pero esta ahce referencia al uso de parametros en las queries a sql
saludos
Hola, me parece excelente tu trabajo Leandro.
ResponderEliminarQuisiera saber como sería este ejemplo si dejáramos solo con tres capas, sin entidades(presentación, negocio y datos). Se podría colocar en vez de entidades, propiedades publicas dentro de datos?. Cómo seria el ejemplo completo.
De antemano Gracias.
hola Capitan
ResponderEliminaro sea quieres aplicar el patron Active Record ?
Active Record pattern
con este podria hacer que la entidad tenga las operaciones de acceso a datos
saludos
Leandro excelente Blog, Tengo una pregunta si la aplicación a desarrollar(software administrativo) es demasiado grande, Que sería más conveniente ? 3 proyectos(las 3 capas) POR cada modulo(Bancos, Clientes, Proveedores, Produccion, etc.) ya que pienso que si hago 1 sólo proyecto de capa de presentación para todo el sistema el ejecutable sería demasiado grande y no creo que sea muy conveniente para hacer actualizaciones, o que me puede recomedar al respecto.
ResponderEliminarGracias de antemano.
hola Ignacio
ResponderEliminarcuando creas proyectos para cada modulo por lo general se piensa en la capa de negocio, datos, entidades, no tanto en la de UI
quizas lo mas simple seria separa los modulos con carpeta en el proyecto de presentacion
hace mucho se usaba algo como esto
Smart Client Software Factory 2010
pero creo que quedo algo obsoleto
ya que actualmente la tendencia es usar WPF con algun framework como ser Prism
saludos
Buenos Días:
ResponderEliminarMuchas gracias por el Articulo, de mucha orientación y gran ayuda; una consulta quise adjuntar la base de datos a mi servidor, para poder realizar las pruebas, pero no me deja. Que versión es o si se tiene el BAK de dicha Base de datos, en mi caso estoy usando el SQL Server 2012.
Gracias por el apoyo..
Atte
Jorge Diaz
hola leandro, con esta metodologia que recomientas, una aplicación con este estilo llamando a base de datos o usando un ORM, bien sea nhibernate o EF, y es que estoy haciendo algo para un consultorio odontologico, y no se si utilizando EntityFramework con MVC 5 con repository o facade y en VS2013 sea muy robusto, yo catalogo un proyecto mediano que podria expandirse. que recomendarias?
ResponderEliminarhola
EliminarPorque dices que usar EF con respository en mvc 5 no sea robusto ? lo que describes es una buena combinacion
usar un ORM con mvc 5
lo que planteas es recomendable, por supuesto si puedes ademas unir un IoC como ser unity, ninject, etc para poder inyectar al controller el servicio y los respositorios seria aun mejor, de esta forma podrias implementar test en el codigo
saludos
hola leandro!! bueno leandro a lo que me referia era que SI usar Esta metodologia Sería buena recomendación, para el proyecto que te mencione! y digo que si esta metodología es MEnos robusto que un ORM o en que se diferencia con Usar un ORM?
Eliminarhola
EliminarEl tema es que no se si robusto seria la palabra que se deba usar, como en todo los casos hay pros y contras aplicando una tecnica u otra
Con un orm la generacion de consultas es mas simple y puedes aplicar POO facilmente definiendo entidades, por supuesto pierdes algo del control de la db pero en casos puntuales podrias mapear storedprocedure
Creadno el codigo a msno seguro sera mas trabajoso, y escribiras mucho mas codigo, pero bueno ganas en control ya que todo lo has escritu tu mismo
Particularmente me inclino por usar un orm, hace la vida mucho ams simple.
saludos
Muchas Gracias. Me sirvió muchísimo. Sos un ejemplo a seguir.
ResponderEliminarSaludos.
hola Leandro
ResponderEliminarMuy buenos sus tutoriales, quisiera que me ayudara, tengo un error de compilación cuando se llama la cadena de conexión desde otra de las capas. Estoy trabajando con Access y la línea de código de error es:
using (OleDbConnection cnx = new OleDbConnection(ConfigurationManager.ConnectionStrings["cnxString"].ToString()))
me puedes ayudar que es lo que pasa ?
hola
EliminarQue dice el mensaje de error ?
Defines en el app.config el connection string con la key cnxString ?
Puede que falle porque no esta pudiendo encontrar la key de configuracion en el .config
saludos
Buenas Leandro.
ResponderEliminarImplementé en mi proyecto con la estructura N-Tier, funciona correctamente, pero me surge una duda. Estoy implementando un apartado para estadísticas, y me gustaría obtener los datos vía JSON para usarlos con una api de gráficos. ¿Cómo debería realizar con la estructura de n-tier para obtener datos vía JSON?
Un saludo y muchísimas gracias por la información que proporcionas, me eres de gran ayuda.
hola
Eliminarsi usas json entiendo que estas desarrollando una aplicacion web, lo que no mencioans es si se trata de asp.net o asp.net mvc.
El json seguro lo necesitas en la UI para poder aplicar al control de estadistica, en ese caso si tiene una clase o coleccion podrias serializarla a json usando la libreria json.net
saludos
Si, estoy desarrollando una aplicación web (WebForms Framework 4.5.2), con las capas Entities, BLL y DAL y WEB, cuando necesito datos en la UI que no hacen referencia a una entidad, ya que son un conjunto de datos con importes totales y valores que no reflejan una entidad de mi sistema. Donde debería crear la llamada para devolver JSON?
EliminarPor ejemplo, en una página quiero mostrar el numero de usuarios que entraron por móvil, tablet o pc
{"info": {
"pc":"120",
"tablet":"20".
"movil":"100"
}
}
Eso es lo que yo quiero recibir en mi UI, pero quien es el que me devuelve el JSON? el BLL, el DAL?, dónde debo hacerl el serializado de json?
Gracias.
Buenas Leandro, en el ejemplo de las N-Tier que has redactado, la devolución de JSON ¿cual es la capa encargada de devolver esos datos, la BLL?, la capa DAL me trae los datos y los almacena en un Entitie y los pasa a la capa lógica(negocio) y ésta debería ser la encargada de Serializarlo a JSON/XML/.. y mandarlo a la UI?
EliminarSi no estoy en lo cierto corrigeme, no tengo claro bien este concepto.
Un saludo y muchas gracias
hola
Eliminarel json lo devuelve siempre la capa de servicio, en tu caso seria la web. Las capas BLL y DAL solo conoces de entidades y objetos, es en la web que conviertes estos para adaptarlos al transporte ya que puede que quein los consume necesite que sean json,xml o algun otro formato
Si usarias WebApi esto seria un concpeto transparente, desde asp.net clasico desde algun WebMethod definido en el aspx podrias devolver ese json como respuesta al cliente para consumirlo con ajax con jquery
saludos
Este comentario ha sido eliminado por el autor.
ResponderEliminarBUENOS NOCHES FORO .. AYUDA NECESITO IMPRIMIR FACTURA DESDE C# 2015 SIN CRISTAL REPORT... YA QUE HE TRATADO POR VIA DATASET Y DATATABLE LUEGO DE INSTAL EL SAP PARA VS2015 .. CUALQUIE AYUDA GRACIAS ... SI ES CASO ME BAJO LA VERSION A 2013???
ResponderEliminarHola Leandro.
ResponderEliminarMi proyecto está integrado con N-TIER, en la capa lógica de negocio (BLL) tengo métodos que lo usa tango una aplicación web admin como la aplicación web publica, hay alguna manera de separar los métodos para una y otra y a ser posible no tener que reescribir los que ya existen? o en este caso debería crear otra capa que fuese BLLPrivate y BLLPublic.., o algúna interfaz, alguna orientación? Gracias.
Excelente informacion Leandro, muchas gracias !!!
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarHola. Muy buena la explicación pero no está disponible el link de esta parte ni de la anterior. Se me complica seguirlo. Gracias.
ResponderEliminarsaludos me gustaria porque me da error aqui: using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
ResponderEliminar{
conn.Open();
string sql = @"SELECT Count(*)
FROM Customer
WHERE CustomerId = @customerId";
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.AddWithValue("customerId", id);
o si hay que hacer una base de datos o que? por favor
Asi es, necesitas crear la Base de Datos con las tablas y campos correspondientes para que el código te funcione.Saludos.
ResponderEliminarUna aclaración no confundir n - layer con n - tier(niveles) debido a que layer(capas) hacer referencia en la forma logica que se encuentra distribuido el sistema las clases con sus metodos etc. y en cambio tier esta relacionado en la distribucion fisica del sistema donde reside ej la cada de logica de negocio puedo entar en el servidor 1- aplicación y la de persistencia en el servidor 2-base de datos, y el cliente seria otro nivel. disculpen mi redacción no tengo mucho tiempo. Muy buena documentación.
ResponderEliminarQuiero compartir un testimonio sobre cómo el servicio de financiación Le_Meridian me ayudó con un préstamo de 2,000,000.00 USD para financiar mi proyecto de cultivo de marihuana, estoy muy agradecido y prometí compartir esta compañía de financiación legítima a cualquiera que esté buscando la manera de expandir su negocio. project.the company es una empresa de financiación del Reino Unido / EE. UU. Cualquier persona que busque apoyo financiero debe contactarlos en lfdsloans@outlook.com o lfdsloans@lemeridianfds.com. El Sr. Benjamin también está en whatsapp 1-989-394-3740 para facilitar las cosas a cualquier solicitante.
ResponderEliminar