Introducción
Cuando se desarrolla aplicaciones muy centrada en los datos suele ocurrir que el ida y vuelta de los datos por las capas resulte muy directo, notando que la capa de negocio prácticamente no aporta un valor relevante.
En la mayoría de los casos cuando se confeccionan ABM de entidades sin mayor complejidad, la capa de negocio suele convertirse en un simple pasamanos de entidades y listas sin aportar mayor utilidad, esto puede ser cierto en algunas situaciones, pero igualmente esta capa es necesaria para seguir con una arquitectura adecuada, ya que uno nunca sabe cuando será necesario adaptar la información para disponerlos en un formato distinto.
En este articulo analizaremos la importante la capa de negocio y como puede ser útil para transformar la estructura de los datos, adecuándola a la necesidad de la presentación.
Ejemplo propuesto
Para realizar la demostración se atacara un punto claramente poco practico a la hora de ser usada la aplicación.
Este tiene que ver con la búsqueda de un determinado tema, en la grilla de compra.
Actualmente se agrega una nueva línea y se dispone un combo en la celda para seleccionar un tema, pero esto es poco practico teniendo en cuenta la cantidad de temas disponibles.
Una solución a este problema podrías ser representar la selección en forma de árbol, ya que los datos que disponemos implican que los temas se asocian a Álbumes y Artistas, otorgando una relación jerárquica.
El problema presente aquí es que los datos, tan cual podrían tomarse de la base de datos, serán devueltos como registros sin estructura que permita una simple conversión para ser representada en el control TreeView en el formulario.
Es aquí donde entra en jugo la capa de negocio como mediador / adaptador de la información, facilitando la integración entre las capas.
La capa de datos devolverá los registros planos sin estructura, pero la presentación necesita de una jerarquía, es aquí donde la capa de negocio realizaría la transformación, con la ayuda de Linq.
Estructura de los datos
En la base de datos se cuenta con la siguiente estructura de tablas:
Recuperar los datos y armar la jerarquía
Al obtener información proveniente de las tablas esta se estructura en un nivel simple de registros, la información viene de forma plana definida en campos, es por eso que en esta solución se han creado entidades adicionales para trabajar con la información en dos estadios distintos.
El primero cuando se recupera la información directa de la consulta realizada, la cual mapeara a la entidad representada por TrackHierarchicalEntity, esta contiene las propiedades que definen la relación entre varias entidades:
public class TrackHierarchicalEntity { public int ArtistId { get; set; } public string ArtistName { get; set; } public int AlbumId { get; set; } public string AlbumTitle { get; set; } public int TrackId { get; set; } public string TrackName { get; set; } }
Por otro la se tendrá una estructura diferente de entidades que conformaran la jerárquica de información, es por ello que estas cuentan con propiedades de lista genérica de su entidad relacionada:
public class ArtistEntity { public int ArtistId { get; set; } public string Name { get; set; } public List<AlbumEntity> Alumns { get; set; } } public class AlbumEntity { public int AlbumnId { get; set; } public string Title { get; set; } public List<TrackEntity> Tracks { get; set; } } public class TrackEntity { public int TrackId { get; set; } public string Name { get; set; } }
Capa de Datos
En la clase TrackDAL, se agrego un nuevos métodos que permitirán obtener la información de forma plana y directa mapeando al entidad uno a uno con la query que se utiliza:
public static List<TrackHierarchicalEntity> GetAllHierarchical() { List<TrackHierarchicalEntity> list = new List<TrackHierarchicalEntity>(); ; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string sql = @"SELECT AR.ArtistId, AR.Name As ArtistName, A.AlbumId, A.Title As AlbumTitle, T.TrackId, T.Name FROM Track T INNER JOIN Album A ON A.AlbumId = T.AlbumId INNER JOIN Artist AR ON AR.ArtistId = A.ArtistId ORDER BY AR.Name, A.Title, T.Name"; SqlCommand cmd = new SqlCommand(sql, conn); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { list.Add(LoadHierarchicalTrack(reader)); } } return list; } private static TrackHierarchicalEntity LoadHierarchicalTrack(IDataReader reader) { TrackHierarchicalEntity track = new TrackHierarchicalEntity(); track.ArtistId = Convert.ToInt32(reader["ArtistId"]); track.ArtistName = Convert.ToString(reader["ArtistName"]); track.AlbumId = Convert.ToInt32(reader["AlbumId"]); track.AlbumTitle = Convert.ToString(reader["AlbumTitle"]); track.TrackId = Convert.ToInt32(reader["TrackId"]); track.TrackName = Convert.ToString(reader["Name"]); return track; }
La consulta utilizada el INNER JOIN para unir la información de las tres tablas definidas en la base de datos, recuperando la información en una sola consulta.
Capa de Negocio
Sera la responsable de adaptar la información plana devuelta por la Capa de Datos, otorgando una jerarquía.
public static List<ArtistEntity> GetAllHierarchical() { List<TrackHierarchicalEntity> tracks = TrackDAL.GetAllHierarchical(); var hierarchicalList = from item in tracks group item by new { item.ArtistId, item.ArtistName } into artist select new ArtistEntity() { ArtistId = artist.Key.ArtistId, Name = artist.Key.ArtistName, Alumns = (from artistitem in artist group artistitem by new { artistitem.AlbumId, artistitem.AlbumTitle } into album select new AlbumEntity() { AlbumnId = album.Key.AlbumId, Title = album.Key.AlbumTitle, Tracks = (from trackitem in album select new TrackEntity() { TrackId = trackitem.TrackId, Name = trackitem.TrackName }).ToList() }).ToList() }; return hierarchicalList.ToList(); }
Linq ayuda mucho en esta operación de transformación, el uso de la sentencia “group by” es muy útil para definir los campos usados en cada nivel.
En este caso, al posee dos propiedades para la cada entidad se hizo uso de un “group by” junto al “new” para definir una entidad anónima, lo cual permitió tomar la información de la propiedades Key he ir armando las nuevas entidades.
Capa de Presentación
El contar con información jerárquicamente adaptada por la capa de negocio facilita enormemente la tarea de creación de los nodos en el TreeView, simplemente se recorre de forma anidada cada nivel y se van creando los nodos.
Como verán es muy simple:
private void CargarTree() { List<ArtistEntity> lista = TrackBO.GetAllHierarchical(); foreach (ArtistEntity artist in lista) { TreeNode nodeArtist = new TreeNode(artist.Name); treeTracks.Nodes.Add(nodeArtist); foreach (AlbumEntity album in artist.Alumns) { TreeNode nodeAlbum = new TreeNode(album.Title); nodeArtist.Nodes.Add(nodeAlbum); foreach (TrackEntity track in album.Tracks) { TreeNode nodeTrack = new TreeNode(track.Name); nodeTrack.Tag = track; nodeAlbum.Nodes.Add(nodeTrack); } } } }
[C#] |
[VB.NET] |
Alternativa en el trabajo de entidades
En el ejemplo planteado hasta el momento la entidad TrackHierarchicalEntity define dos propiedades por cada entidad que involucra en la jerarquía, pero que sucedería si se trata de objetos algo mas complejos.
Esta es justamente la alternativa que se analizaría en esta sección.
La entidad usada para definir la estructura con la cual se trabaja el linq cambiara su aspecto:
public class TrackHierarchicalEntity { public ArtistEntity Artist { get; set; } public AlbumEntity Album { get; set; } public TrackEntity Track { get; set; } }
Esto impactara también en la capa de datos, mas que nada en la funcionalidad que crea la entidad:
public static List<TrackHierarchicalEntity> GetAllHierarchical() { List<TrackHierarchicalEntity> list = new List<TrackHierarchicalEntity>(); ; using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString())) { conn.Open(); string sql = @"SELECT AR.ArtistId, AR.Name As ArtistName, A.AlbumId, A.Title As AlbumTitle, T.TrackId, T.Name FROM Track T INNER JOIN Album A ON A.AlbumId = T.AlbumId INNER JOIN Artist AR ON AR.ArtistId = A.ArtistId ORDER BY AR.Name, A.Title, T.Name"; SqlCommand cmd = new SqlCommand(sql, conn); SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { list.Add(LoadHierarchicalTrack(reader)); } } return list; } private static TrackHierarchicalEntity LoadHierarchicalTrack(IDataReader reader) { TrackHierarchicalEntity track = new TrackHierarchicalEntity(); track.Artist = new ArtistEntity() { ArtistId = Convert.ToInt32(reader["ArtistId"]), Name = Convert.ToString(reader["ArtistName"]) }; track.Album = new AlbumEntity() { AlbumnId = Convert.ToInt32(reader["AlbumId"]), Title = Convert.ToString(reader["AlbumTitle"]) }; track.Track = new TrackEntity() { TrackId = Convert.ToInt32(reader["TrackId"]), Name = Convert.ToString(reader["Name"]) }; return track; }
Si bien el query usado para extraer la información continua sin variante, la forma en como se procesa ha cambiado, teniendo que crear instancias por cada entidad que se necesites utilizar.
En la clase TrackBO el linq usado para armar la estructura no ha sufrido cambios notables en el grueso de la lógica aplicada:
public static List<ArtistEntity> GetAllHierarchical() { List<TrackHierarchicalEntity> tracks = TrackDAL.GetAllHierarchical(); var hierarchicalList = from item in tracks group item by item.Artist into artist select new ArtistEntity() { ArtistId = artist.Key.ArtistId, Name = artist.Key.Name, Alumns = (from artistitem in artist group artistitem by artistitem.Album into album select new AlbumEntity() { AlbumnId = album.Key.AlbumnId, Title = album.Key.Title, Tracks = (from trackitem in album select new TrackEntity() { TrackId = trackitem.Track.TrackId, Name = trackitem.Track.Name }).ToList() }).ToList() }; return hierarchicalList.ToList(); }
Pero si hay un detalle no menos a remarcar, en el linq ahora se hacen uso de las propiedades con entidades en las funciones de agrupación, pero estas trabajan por referencia de los objetos, por lo tanto como sabrá linq que entidades son iguales para agruparlas, es aquí donde se genera el principal cambio con la primer parte del articulo.
Las dos entidades involucradas en la agrupación dentro del linq deberás implementar una interfaz IEquatable<>, la cual permite definir los métodos que permitirán diferenciar una instancia con otra cuando se los necesita comparar.
Tanto el método Equals() como el GetHashCode() serán invocados por linq en su operación de agrupación, dentro de estos métodos se hacen uso de la propiedades para poder determinar si se trata de la misma entidad o no, de esta forma se evita evita el método estándar cuando no se implemento la interfaz, en donde solo se comparan las referencias de los objetos.
public class AlbumEntity : IEquatable<AlbumEntity> { public int AlbumnId { get; set; } public string Title { get; set; } public List<TrackEntity> Tracks { get; set; } #region IEquatable<ArtistEntity> Members public bool Equals(AlbumEntity other) { if (Object.ReferenceEquals(other, null)) return false; if (Object.ReferenceEquals(this, other)) return true; return other.AlbumnId.Equals(this.AlbumnId); } public override int GetHashCode() { return this.AlbumnId.GetHashCode() ^ this.Title.GetHashCode(); } #endregion } public class ArtistEntity : IEquatable<ArtistEntity> { public int ArtistId { get; set; } public string Name { get; set; } public List<AlbumEntity> Alumns { get; set; } #region IEquatable<ArtistEntity> Members public bool Equals(ArtistEntity other) { if (Object.ReferenceEquals(other, null)) return false; if (Object.ReferenceEquals(this, other)) return true; return other.ArtistId.Equals(this.ArtistId); } public override int GetHashCode() { return this.ArtistId.GetHashCode() ^ this.Name.GetHashCode(); } #endregion }
Si bien esta alternativa no parece muy útil al principio, cuando se trabaja con objetos es muy común encontrarse con situaciones como esta, en donde entidades se encuentren relacionados con otras mas complejas, las cuales hay que trabajarlas y conocer las técnicas que permiten su manipulación.
[C#] |
[VB.NET] |
Conclusión
Si bien la capa de negocio puede resultar muy útil como intermediario para coordinar operaciones que requiere complejas actualizaciones de datos a distintas tablas, definiendo a su vez todo en una misma transacción, esta es solo la operación común para la cual se usaría esta capa.
La transformación de información para adaptarla a la presentación es otra de las tantas responsabilidades que puede tener.
Hola Leandro:
ResponderEliminarQuisiera saber si ¿vas a poner el tema "[N-Tier] Desarrollo en capas – Transformación de entidades en la Capa de Negocio – Parte 4" en visual basic.net, ya que estoy empezando y C# casi no lo entiendo?.
Un Saludo.
Juan Carlos.
hola Euroasesor
ResponderEliminarSi la idea es publicarlo tambien en vb.net, pero como me di cuenta que tenia algunos detalles por cambiar, preferi publicar en c# solo y cuando lo revise ahi si pasarlo a vb.net
saludo
Hola Leandro
ResponderEliminar¿Vas a publicar el ejemplo en VB.NET?
GRACIAS
hola Enrique
ResponderEliminarYa esta publicado el ejemplo en vb.net
Como veras el linq resultante para el armado del arbol es algo ditinto al de c#, porque la forma en como agrupa vb.net por multiples campos no resultaba
saludos
Gracias Leandro
ResponderEliminarEstos ejemplos me han abierto la mente hasta tal punto que he llegado a hacer una mini aplicacion para probarlo
Una duda que tengo es, ¿hay que crear una entidad por cada tabla de la base de datos?
Es que de tus ejemplos, derivo eso
Un saludo
hola Enrique
ResponderEliminarDisculpa por la demora en la respuesta
Si bien lo mas comun es mapear una entidad con una tabla, esto no es una regla estricta, puedes mapear aplicando herencia, en dodne una entidad persista en ams de una tabla, o al contrario mas de una tabla formen una entidad compuesta
Veras en el ejemplo en la segunda parte como al entidad jerarquica devuelve propiedades de clases compuestas, o sea esta formada por otras tres clases distintas.
Aunque lo mas comun es relacionar como lo hace el DataTable mapeando la tabla a la entidad, el 90% de los caso sera asi, salvo particularidades del diseño en donde necesites algo mas complejo
Si usarias ORM esto se nota mucho mejor, ya que la creacion de entidades complejas es mas simple
saludos
Estimado Leandro, antes que nada agradezco el articulo publicado. Sirve mucho para introducirse en la materia del desarrollo en N capas.
ResponderEliminarAl respecto quiero aclarar una duda que me surge:
En caso de querer contar con un datagridview que muestre, por ej. Los temas musicales (tracks), todas sus columnas, pero además, en otra columna agregar el nombre del album, y en otra, el artista? Como se reflejaría esto? o mas o menos una idea de como hacerlo...
Agradezco mucho tu respuesta.
hola Emilio
ResponderEliminarEn este caso que planteas vas a encontrar problemas con wel DataGridView, ya que este tiene una limitante para mostrar informacion de objetos complejos, o de propiedades que sean instancia, por eso armar entidades que se relacionen no tendria sentido sabiendo este problema
Lo mas simple seria crear una entidad a tal efecto y definir en esta una propeidad por cada columan del DataGridView, o sea una propiedad NomnreAlbum otra Artista, pero estas serina del tipo string.
Entonces en una queery especial usando INNER JOIN recuperarias esta info volcandola a esta nueva entidad especial para el DataGridView
saludos
Leandro, muchas gracias por tu respuesta.
ResponderEliminarLa situacion planteada creo que es frecuente encontrarla en muchos escenarios: mostrar una tabla en un DGV y algunas propiedades (campos) de otras tablas de referencia. Tal es el caso, por ej. de articulo, y rubro de articulo; pelicula y genero de pelicula; empleado y area donde trabaja; etc. En todos estos casos, veo una relacion de asociacion entre estas clases; y en Modelado Relacional, serían relaciones de 1 a muchos (un rubro-muchos articulos, etc.).
Cuando programamos con ADO.net, es arrastrar y listo...
Pero considerando entidades, capa de negocios, te pido me orientes y corrijas por favor, si no estoy entendiendo bien:
Para esto, y siguiendo el ejemplo de tracks, necesitaria en:
capa de acceso a datos TrackDAL: defino un query con los INNER JOINS a las tablas relacionadas, para armar la consulta plana.
Entidades: TrackEntity: creo una clase especial que tenga las propiedades relacionadas con la query creada en TrackDAL.
Capa de negocios: TrackBO defino el metodo que devuelva un List de entidades creadas especialmente para el DGV, recurriendo al query en TrackDAL.
Capa presentacion: definir los campos del DGV de acuerdo a la entidad definida.
Espero se me entienda
Nuevamente, muchas gracias Leandro!
Leandro, en la entidad Invoice, hay un miembro que es "int CustomerId"; no debería ser esto "CustomerEntity customer"?
ResponderEliminarEstoy intentando entender como se aplicarían estas clases "intermedias" o clases de asociacion, como lo explica UML.
En tal caso, en la entidad CustomerEntity no tendría que haber una List? agradezco tu ayuda.
Saludos
hola Emilio
ResponderEliminarTe comento, en realidad el uso del CustomerId y del CustomerEntity es relativo, que implementes uno u otro son validos todo dependera que tanto es necesario en la navegacion entre las entidades.
Si usas algun ORM ya sea NHibernate o Entity Framework, implementar un CustomerEntity como propiedad de la entidad Invoce es muy simple porque estos frameworks tienen conceptos como ser Lazy Load, lo cual es genial para no penalizar la carga de als entidades en las asociaciones cuando als cargas, en cambio hacerlo manualmente hay que pensarlo un poco mas.
Recuerda que cargar esa asociacion en el DataAccess no es facil, ya que al hacer el load de la factura debes hacer otra query por el id y buscar el Customer, eso para cada invoice, si son 100 las que cargas en la lista realziaras 100 queries para recuperar cada Customer, uno por cada factura, o sea el lazy-load de las propiedades es algo complejo, por eso dependiendo de la situacion es que se define la entidad o se deja el id, al menso con el id si es que se requiere el customer se podra ir al DataAccess y recuperarlo especificamente ese cuando se lo necesite.
En resumen, si vas a un modelado puro tienes razon debveria ser una asociacion a CustomerEntity, pero a veces la implementacion hace que se deban romper estas reglas, si te animas a usar un ORM esto que necesitas si podras implementarlo.
saludos
Estimado Leandro.
ResponderEliminarNuevamente, muchas gracias por tu respuesta. En vista de lo que voy aprendiendo entonces, quisiera que me orientes sobre los conocimientos teóricos y prácticos, y tecnologías que debo aprender para realizar desarrollos de software "decentes" en .NET, por asi decirlo. Por ej. uno sería: separación en capas, como lo enseñas en el blog; otro, sería NHibernate? qué más tendría que saber?
Muchas gracias, nuevamente por tu ayuda.
Hola Leandro,
ResponderEliminarPrimero te felicito por los excelentes ejemplos que preparas.
Sera mucho pedirte que hicieras un pequeño tutorial explicando como crear la capa DAL y/o Entities utilizando CODESMITH?
Seria muy practico para los que ya tenemos algunas aplicaciones hechas con los DataSets...
Gracias como siempre por todas las ayudas que brindas.
Saludos,
Jorge
hola Jorge
ResponderEliminarHe utilizado hace tiempo CodeSmith, es una buena herramienta para generar codigo, pero ultimamente me estoy dedicando a ver algo de T4 que seria el geneerador de codigo que se integra con el propio Visual Studio
Estaba viendo mas que nada de armar algo con este, pero voy un poco lento
Igualmente me extraña que unas CodeSmith con DataSet porque lo ideal es que con el generador de codigo crees tus propias entidades basandte en la estructura de tablas o metadata
saludos
Hola Leandro,
ResponderEliminarTenes razon, pasa que no me exprese bien, lo que quise decir es que seria muy practico para los que estamos acostumbrados a trabajar con DataSets, para justamente empezar a utilizar estas tecnicas.
Si pudieras armar un tutorial de como utilizar alguno de los generadores de codigo (el T4 x ej) con este mismo ejemplo, seria genial.
Saludos,
hola Jorge Conil
ResponderEliminarestoy en eso, pero el tiempo para dedicarle es reducido y voy algo despecio
pero espero dentro de poco publicarlo
saludos
Saludos Leandro,
ResponderEliminarLeandro en algunos de tus post se han eliminado las imágenes y los enlaces para descargar los códigos de ejemplo no están disponibles, en especial el código de este articulo.
Si es posible y puedes resubir el código de C# te lo agradecería mucho.
hola Nelson88
ResponderEliminargracias por el aviso, me puse en contacto con el soporte de hosting donde subo los archivos, no estoy seguro si sera un problema del sitio o del DNS .com.ar
pero bueno ya envie la consulta al soporte a ver que me dicen, por ahi es solo un problema temporal
saludos
Hola Leandro estoy haciendo un aplicativo web C# arquitectura de N capas. No se si podrias hacer un ejemplo completo es decir el insertar-modificar-eliminar-listar en dicho programa con acceso a Base de datos utilizando procedure=?. Por favor gracias
ResponderEliminarEditar
ResponderEliminarEn el Itemplate de gridview al momento de hacer tus propios metodos se utiliza OnCommand="Editar_Command" y se coloca CommandName='<%# Container.DataItem("CODIGO")%>'
y al hacer ese metodo en la parte programacion se realiza de la siguiente forma=?
ResponderEliminar(void Editar_Command(object sender, CommandEventArgs e) {
string str_modo = "E";
//capturamos al cliente al cual se quiere editar
string str_prueba = e.CommandName;
//Mandamos dos parámetros: modo de edición y el cliente a editar
Response.Redirect
"frmPrueba.aspx?modo=" + str_modo + "&prueba=" + str_prueba);
} )
hola kelly
ResponderEliminarpero usar stored procedure o usar una query directa es la misma tecnica que describo en estos articulos
solo cambia que en el Command de ado.net deberas definri el
cmd.CommandType = CommandType.StoredProcedure
solo es cambia el resto es identico
saludos
hola Leandro,
ResponderEliminarVeo que utilizas los DataReader, pero ya venimos acostumbrados con el ambiente Dataset que es desconectado.
no existe la posibilidad que cuando se consulte o se lleve datos a un datagrid el datareader nos ponga problemas???
o seria como cuando trabajabamos con vb6,
Abrir la conexion,
traer datos y cierran la conexion.
abrir la conexion,
enviar transacciones al servidor
y cierran la conexion.
hola greg_dorian
ResponderEliminares que aqui tambien estas practicamente desconectado, se usa el reader proque es un objeto muchas veces mas rapido que el dataset, mas que nada si el acceso es secuancial
entonces como bien comentas abres la conexion, habilitas el reader, cargas la entidad y luego cierras todos los objetos de ado.net que usaste
no deberia traer problemas a menos que uses readers anidados para armar una jerarquia de objetos, igual esto se puede solucionar habilitando MARS
saludos
hola leandro, he visto desarrollos similares donde se aplica EF para crear el modelo conceptual de la bd y luego mapearlo con las entidades, mi idea es implementar procedimientos almacenados para las consultas en vez de linq, me aconsejas utilizar EF o trabajar de esta manera, creando las entidades manualmente?? saludos!!
ResponderEliminarhola matias
ResponderEliminarimagino que sabes que EF es un ORM, por lo tanto la ventaja que aporta es la de generar las queries dinamicamente en base al mapeo que definas
si vas a realizar toda la persistencia mediante stored procedure como que no tendria mucho sentido usar un ORM, ahora si vas a realizar un mix, o sea para la mayoria de las operaciones linq y para algunas en concreto donde requiere performance un procedure en ese caso si es valido EF
saludos
gracias por tu rta! la idea es usar todo SP, estoy terminando de realizar mi primer proyecto en capas realizando toda la persistencia mediante stored procedures como mencionabas, pero no habia experimentado ni visto ningun ORM todavia, ni tampoco usaba entidades..viendo tu ejemplo veo q es mucho mas practico a la hora de pasar la informacion entre capas mediante listas, por lo que quiero mantener los SP que realice y agregar las entidades..
ResponderEliminarHola leandro,en una aplicacion web n capas me surge la necesidad de compartir la capa de negocio para unos formularios windows esto dado a la riqueza de sus controles, es esto posible? gracias.
ResponderEliminarhola ariel
ResponderEliminarclaro, podrias exponer la logica de negocio de forma remota usando WCF, o sino te animas con asmx
los servicio web exponen la funcionalidad para que la aplicacion desktop la consuma
desde la aplicaicon winforms harias un service reference a los servicio para invocarlos, de esa forma la logica de negocio podria estar central en el server web para suarse en la aplciacion aspx y en la desktop al mismo tiempo
saludos
gracias, es lo que estaba pensando. publicaste algun articulo sobre esta temática. Saludos y gracias.
ResponderEliminarHola Leandro buenos dias, estoy k me rompo la cabeza con las listas List<> en c# y esperaba que me pudieses lanzar una cuerda antes que me caiga !!
ResponderEliminarE aqui el ejemplo:
/*==================================*/
Capa Entidad-> ent_empleado
public int Idempleado { get; set; }
public string Nombre { get; set; }
public decimal Sueldo { get; set; }
public bool Activo {get; set; }
public ent_empleado(){}//constructor1
public ent_empleado(int idempleado, string nombre, decimal sueldo)//constructor2
{
this.Idempleado = idempleado;
this.Nombre = nombre;
this.Sueldo = sueldo;
}
Como ves en este constructor2 solo declaro 3 campos(para listar en un datagridview) porque el de [Activo] no lo voy a usar aun.
/*==================================*/
Capa Datos
public static List d_listar_empleado(ent_empleado objEN)
{
List Lista = new List();
using (SqlConnection con = new SqlConnection())
{
con.ConnectionString = ConStr;
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.CommandText = "listar_empleado";
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Lista.Add
(
new ent_empleado
(
(int)dr["Id"],
(string)dr["Nombre"],
(decimal)dr["Sueldo"]
)
);
}
}
}
}
return Lista;
}
/*==================================*/
Capa Presentacion
Datagridview1.Datasource= n_maestros.d_listar_empleado();
Pero en el datagridview me lista 4 campos(incluye el de activo pero todos desactivados), lo que yo deseo saber es como hacer para que el datagridview solo me liste los campos que e declarado en el constructor(3 campos)...Obviamente es solo un ejemplo pues el verdadero ejemplo tiene como 20 campos de los cuales solo algunos me interesa listar, solo uso los demas campos cuando voy a agregar nuevos items para lo cual tengo un tercer constructor; ademas esta con su capa de negocio respetiva.
E escuchado por ahi que tendria que hacer un foreach de la lista pero me pregunto si eso no sera un recargo de recursos innecesario, porque antes yo usaba el datatable y no habian problemas pero me dijeron que el datareader es mas veloz por lo cual decidi empezar a usarlo, otro comentario menciona que haga un Datatable.Load(Datareader) pero creo que es lo mismo que usar el datatable en cuanto a velocidad porque tendria que esperar a que se haya creado el dataset y luego el datatable, mas que todo lo hago por temas de aminorizar recursos y tiempo.
Gracias por la atencion y espero tu ayuda porfavor!!.
hola Franky
ResponderEliminaralgunos puntos que puedo marcar
- no necesitas definir ningun constructor porque podrias asignar los datos directamente usando
Lista.Add( new ent_empleado() {
Id = (int)dr["Id"],
Nombre = (string)dr["Nombre"],
Sueldo = (decimal)dr["Sueldo"]
});
- para solo ver algunos campos en el grid debes definir las columnas en tiempo de diseño, en la primer parte de este articulo lo explico
[DataGridView] – Parte 4 - Uso del DataGridViewComboBoxColumn
es muy importante cuando defines las columnas asignar el DataPropertyName ademas de poner la propiedad AutoGeneratecolumns =false
de esta forma defines que datos quieres ver o no en el grid
saludos
Gracias, sobre todo por el dato del constructor, yo lo usaba asi porque en la mayoria de tutoriales en internet lo hacen de esa forma o por lo menos de los que e visto!!..
ResponderEliminarEn cuanto al DataPropertyName si lo habia tenido en cuenta y tu respuesta me sirvio para re-confirmarlo.
Una pregunta mas por favor: el tiempo y recurso que toma en crearse un DataSet de un DataTable en un Fill son diferentes o son lo mismo por ejemplo:
da.Fill(dt); vs da.Fill(Ds.Tables["algo"]);
Ah y te olvidaste de mencionar si el DataReader es mas veloz que el DataTable..
Por ahi tambien estoy desarrollando un proyecto con clases comunes DBCommand, DbConnection, DbDataAdapter, etc para que se pueda cambiar de base de datos; estas clases ocupan la misma cantidad de recursos que los definidos por cada proveedor o es mayor su consumo por ejemplo:
SqlDataReader vs DBDataReader
Disculpa por hacerte muchas preguntas pero estoy con toda la onda de la investigacion en cuanto a optimizacion y velocidad de las transacciones, gracias por la atencion.
hola Franky
ResponderEliminarusar un dataset o datatable en el Fill deberia consumir los mismos recursos y tiempo, siempre considerando que se carga una unica tabla
Esta claro que el datareader para cargar entidades de forma secuencial es mucho mas performante que un datatable
Si la idea es programar con varias base de datos no aconsejo las clases de ado.net, sino que deberias implementar el patron repository, entonces defines un repository para cada db
si te animas a usar una libreria de IoC (Invertion of Control) mucho mejor
Repository pattern tutorial in C#
saludos
Como siempre, tus respuestas son excelentes.. Investigare (IoC) nunca lo habia escuchado..Bueno pues a buscarse a dicho la informacion !!
ResponderEliminarGracias.
Hola Leandro yo estoy haciendo una modificacion de tu aplicacion en frmCompra CAMBIANDO LA BASE DE DATOS, quisiera preguntarte lo sgte: En este parte del codigo
ResponderEliminaromboCol.ValueMember = "TrackId"
Se salta las restantes lineas y me muestra el formulari (revise los campos que sean de la tabla que yo uso) aparentemente todo esta bien, como puedo revisar que esta mal en mi aplicacion? Gracias, abajo el codigo original
Private Sub frmCompra_Load(sender As Object, e As EventArgs)
'
' Cargo los items del combo
'
Dim comboCol As DataGridViewComboBoxColumn = TryCast(dgvLineaCompra.Columns("Track"), DataGridViewComboBoxColumn)
comboCol.ValueMember = "TrackId"
comboCol.DisplayMember = "Name"
comboCol.DataSource = TrackBO.GetAll()
'
'
'
NuevaLinea()
hola Unknown
ResponderEliminarcomo es eso que se salta las lineas? eso no es un comportamiento correcto,
no deberia saltarse lineas
intentanste realizar un Rebuild de la solucion para ver si quizas no esta tomando el codigo correctamente
prueba definir un bloque try..catch que involucre ese bloque de codigo para ver si es causa de un error que no se este controlando
saludos
Hola Hice el try catch y dice: Referencia de objeto no establecida como instancia de un objeto, osea el problema esta en el comboCol ? si solo modifique la base de datos y los campos, que tengo que revisar, gracias por la guia
ResponderEliminarhola Unknown
ResponderEliminarlo que ese error indica es que no esta pudiendo castear una columna como la que defines aqui
dgvLineaCompra.Columns("Track")
valida que este bien escrita y que sea una columna del tipo DataGridViewComboBoxColumn
porque el TryParse() al no poder castear al tipo que le indicas devuelve null
saludos
Etoy haciendo modificaciones con otra base de datos y en el comobobox en el campo unitpriceno aparece "0" ni se ve el precio , al momento de cargar el formulario que podria haber pasado, que deberia revisar?
ResponderEliminarGracias de antemano
hola Unknown
ResponderEliminaralgo no entiendo, como saltamos del error de la columna al no poder convertirla en DataGridViewComboBoxColumn
a que esta devuelva cero ?
o sea una cosa es que no se pueda tomar la columna del tipo combo para asignarle datos y otra muy diferentes que algo devuelva cero
pero esto es mucho despues, al final la linea
Dim comboCol As DataGridViewComboBoxColumn = TryCast(dgvLineaCompra.Columns("Track"), DataGridViewComboBoxColumn)
pudo pasar correctamente la ejecucion ?
saludos
ok , mira tu repuesta del 24 de Octubre anterior me ayudo a resolver el problema de la columna Track, pero esta ultima pregunta que te hago es en la columna UnitPrice, esa es la que me no me da cero , y si bien es cierto ME SALE BIEN bien el combo de lo que vendria a ser la columna track(recuerda que yo uso Northwind asi qeu la columna es productname),en la columna UnitPrice no sale cero al cargar mi formulario ni permite ver el precio PERO EN TU APLICACION cuando carga el formulario las 2 columnas unitprice y Cantidad estan con 0, y carga el precio
ResponderEliminarhola Unknown
ResponderEliminarvalidaste que el evento que usa la seleccion dentro del grid se este ejecutando ?
es que alli es cuando tomando el productid seleccionado deberias buscar el precio para mostrarlo en la otra celda
lo primero que debes validar es que este evento se este ejecutando y despues ver que se recupere el id del producto seleccionado para poder localizar el precio y cargarlo en la otra celda
saludos
Ya halle la solucion a la hora de ejecutar nuevalinea , sale mal en el la clase habia puesto preciounitario en ves unitprice, igual en la funcion clone .se me hace un mareo, por eso te pregunto cual es la que hace referencia al campo unitprice? .Gracias por la ayuda que me diste
ResponderEliminarhola Unknown
ResponderEliminarno se si entendi la pregunta
el unitprice es la propiedad de la entidad InvoiceLinesEntity, esto no tiene nada que ver con la entidad que utiliza el datagridview para definir el registro, porque es una entidad de cada de presentacion, por supuesto despues se debe transformar para pasar a la otra capa
saludos
el caso es que cuando cambie de preciounitario a unitprice, en la clase y en la función clone, se ejecuto bien,apareciendo el cero y cargando los precios, no se como, si se te ocurre una idea, seria genial pues, no me gusta el no entender bien este programa
ResponderEliminarLuis Martin
hola Unknown
ResponderEliminarlas propiedades que mencionas trabajan en entidades diferentes
una define una entidad del dominio de la aplicacion y la otra es una entidad de la presentacion, pueden tener diferentes nombres peor una mapea con la otra, por lo que deberia convertirse una en otra cuando pases datos de la capa de presentacion a la capa de negocio
saludos
Me sale este error al hacer la adaptacion a la base de datos Northwind
ResponderEliminar"No se puede convertir un DBNUll en otros tipos"
y se para en esta linea mostrando el mensaje de error
xPedidosDetalle.OrderLineID = Convert.ToInt32(cmd.ExecuteScalar());
Aqui las lineas del lugar donde encontre el error
using (SqlCommand cmd = new SqlCommand(sqlPedidoDetalle, Cnx))
{
foreach (PedidosDetalle xPedidosDetalle in xPedido.Detalles)
{
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("@OrderID",xPedido.OrderID);
cmd.Parameters.AddWithValue("@ProductID",xPedidosDetalle.ProductID);
cmd.Parameters.AddWithValue("@UnitPrice",xPedidosDetalle.UnitPrice);
cmd.Parameters.AddWithValue("@Quantity",xPedidosDetalle.Quantity);
//
// Si bien obtenermos el id de linea de factura, este no es usado
// en la aplicacion
// lm no entiendo
xPedidosDetalle.OrderLineID = Convert.ToInt32(cmd.ExecuteScalar());
}
}
Como lo soluciono? Gracias
hola Unknown
ResponderEliminarque query estas ejecutando ? porque si es algo como el MAX() deberias usar
SELECT ISNULL(MAX(campo), 0) FROM Tabla
para asi no tener el problema con el null
saludos
Hola
ResponderEliminarEsto es la instruccion que uso:
string sqlPedidoDetalle = @"INSERT INTO [Order Details] (OrderID,ProductID,UnitPrice,Quantity )
VALUES (@OrderID,@ProductID,@UnitPrice,@Quantity)
SELECT SCOPE_IDENTITY()";
Como lo solucionare en este caso?Gracias
hola Unknown
ResponderEliminarpero defines la key de la tabla como IDENTITY (autonumerico)
porque puede que devuelva null porque el scope identity no tenga nunguna key que generar
imagino tienes una columna de nombre OrderDetailID y esa es la key de la tabla, ese campo lo defines como identity
saludos
Muchas gracias por la ayuda, espero ser algún dia experto como tu tambien, como dicen en Argentina, Aguante Leandro!!
ResponderEliminarhola leandro, deseo indagar bien el patron repositorio, yo trabajo igual en mis proyectos de n-tier o varias capas, pero he querido implementar el patron repositorio para el CRUD, una interfase base y la implementacion, junto a las llamadas a cada entidad me podrias explicar como implementarlo con tu ejemplo?
ResponderEliminarhola greg_dorian
ResponderEliminaren este articulo no estoy implementando al patron repositorio
si implemento una capa de persistencia pero esta se instancia de forma fija
The Repository Pattern
saludos
hola leandro, gracias por tu respuesta, pero se podria implementar el patron repositorio en este ejemplo? y cual parte dices que implementas la persistencia de forma fija??
ResponderEliminarhola greg_dorian
ResponderEliminarcomo poder se podria, pero habria que aplicarle varios cambios
cuando menciono "capa de datos" a esta me refiero y es fija porque veras que los metodos son static
esta el concepto de separacion de responsabilidades ya que esas clases son las unicas que se conectan con ado.net a la db, pero es codigo fijo o sea no uso interfaces o alguna otra tecnica para desacoplar
saludos
Quiero 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