domingo, 29 de agosto de 2010

[N-Tier] Desarrollo en capas – Transformación de entidades en la Capa de Negocio – Parte 4

 

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:

image

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.

56 comentarios:

  1. Hola Leandro:

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

    ResponderEliminar
  2. hola Euroasesor

    Si 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

    ResponderEliminar
  3. Hola Leandro

    ¿Vas a publicar el ejemplo en VB.NET?

    GRACIAS

    ResponderEliminar
  4. hola Enrique

    Ya 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

    ResponderEliminar
  5. Gracias Leandro

    Estos 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

    ResponderEliminar
  6. hola Enrique

    Disculpa 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

    ResponderEliminar
  7. Estimado Leandro, antes que nada agradezco el articulo publicado. Sirve mucho para introducirse en la materia del desarrollo en N capas.
    Al 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.

    ResponderEliminar
  8. hola Emilio

    En 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

    ResponderEliminar
  9. Leandro, muchas gracias por tu respuesta.
    La 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!

    ResponderEliminar
  10. Leandro, en la entidad Invoice, hay un miembro que es "int CustomerId"; no debería ser esto "CustomerEntity customer"?
    Estoy 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

    ResponderEliminar
  11. hola Emilio

    Te 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

    ResponderEliminar
  12. Estimado Leandro.
    Nuevamente, 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.

    ResponderEliminar
  13. Hola Leandro,
    Primero 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

    ResponderEliminar
  14. hola Jorge

    He 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

    ResponderEliminar
  15. Hola Leandro,
    Tenes 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,

    ResponderEliminar
  16. hola Jorge Conil

    estoy en eso, pero el tiempo para dedicarle es reducido y voy algo despecio

    pero espero dentro de poco publicarlo


    saludos

    ResponderEliminar
  17. Saludos Leandro,

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

    ResponderEliminar
  18. hola Nelson88

    gracias 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

    ResponderEliminar
  19. 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

    ResponderEliminar
  20. Editar

    En el Itemplate de gridview al momento de hacer tus propios metodos se utiliza OnCommand="Editar_Command" y se coloca CommandName='<%# Container.DataItem("CODIGO")%>'

    ResponderEliminar
  21. y al hacer ese metodo en la parte programacion se realiza de la siguiente forma=?
    (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);
    } )

    ResponderEliminar
  22. hola kelly

    pero 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

    ResponderEliminar
  23. hola Leandro,
    Veo 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.

    ResponderEliminar
  24. hola greg_dorian

    es 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

    ResponderEliminar
  25. 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!!

    ResponderEliminar
  26. hola matias

    imagino 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

    ResponderEliminar
  27. 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..

    ResponderEliminar
  28. Hola 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.

    ResponderEliminar
  29. hola ariel

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

    ResponderEliminar
  30. gracias, es lo que estaba pensando. publicaste algun articulo sobre esta temática. Saludos y gracias.

    ResponderEliminar
  31. Hola 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 !!

    E 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!!.

    ResponderEliminar
  32. hola Franky

    algunos 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

    ResponderEliminar
  33. 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!!..

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

    ResponderEliminar
  34. hola Franky

    usar 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

    ResponderEliminar
  35. Como siempre, tus respuestas son excelentes.. Investigare (IoC) nunca lo habia escuchado..Bueno pues a buscarse a dicho la informacion !!

    Gracias.

    ResponderEliminar
  36. 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
    omboCol.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()

    ResponderEliminar
  37. hola Unknown

    como 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

    ResponderEliminar
  38. 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

    ResponderEliminar
  39. hola Unknown

    lo 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

    ResponderEliminar
  40. 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?
    Gracias de antemano

    ResponderEliminar
  41. hola Unknown

    algo 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

    ResponderEliminar
  42. 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

    ResponderEliminar
  43. hola Unknown

    validaste 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

    ResponderEliminar
  44. 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

    ResponderEliminar
  45. hola Unknown

    no 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

    ResponderEliminar
  46. 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
    Luis Martin

    ResponderEliminar
  47. hola Unknown

    las 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

    ResponderEliminar
  48. Me sale este error al hacer la adaptacion a la base de datos Northwind

    "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

    ResponderEliminar
  49. hola Unknown

    que 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

    ResponderEliminar
  50. Hola
    Esto 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

    ResponderEliminar
  51. hola Unknown

    pero 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

    ResponderEliminar
  52. Muchas gracias por la ayuda, espero ser algún dia experto como tu tambien, como dicen en Argentina, Aguante Leandro!!

    ResponderEliminar
  53. hola 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?

    ResponderEliminar
  54. hola greg_dorian

    en 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

    ResponderEliminar
  55. 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??

    ResponderEliminar
  56. hola greg_dorian

    como 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

    ResponderEliminar