abril 17, 2007

Ejemplo de Aplicación ASP.NET 2.0 en capas con DataSet Tipado

Para el desarrollo de esta aplicación utilizamos Visual Basic .NET con Visual Studio .NET 2005.

Estrucutura del proyecto ASP.NET
- Una carpeta App_Code con:

  • Una clase Area. Que hace uso de los métodos de la Clase del tipo Business Entities representada por un TableAdapter de un DataSet.

  • Una clase DataSet. Contiene un DataSet tipado el cual contiene los TableAdapter que representan a cada uno de los objetos Entitie con sus respectivos métodos de Acceso a Datos.


- La forma Web “Area.aspx” representa a la capa GUI.

Las 3 capas

En una arquitectura en 3 capas tenemos la capa de presentación o GUI, en nuestro ejemplo son los archivos aspx. La capa de Negocio que en nuestro ejemplo la representan nuestras clases Entity ubicados en el DataSet Tipado y para cada objeto entidad existe un TableAdapter con los métodos necesarios para el Acceso a Datos. Y la clase Area que representa como una clase Control que se comunica con las clases Entity y sus metodos de acceso a datos para luego hacer DataBinding con controles en la GUI.

Detalles de la Implementación

Tenemos una tabla llamada Area para la cual queremos implementar una forma de mantenimiento. La pantalla a la que queremos dar funcionalidad con la arquitectura en 3 capas es:



Esta es la estructura de la tabla de la base de datos correspondiente al Area y los datos que regresa el store procedure que estaremos ejecutando.



Implementación del DataSet que contiene a los TableAdapters que representan a nuestros objetos Business Entities.

Algunas de las formas de representar a los Business Entities en la apicación son:
· XML
· DataSet Genericos
· DataSet Tipados
· Componentes Business Entities personaizados
· Componentes Business Entities con comportamiento CRUD

Para nuestro ejemplo utiizaremos el caso de DataSet Tipados. En la siguiente imagen podemos observar como se representa nuestro Business Entiy Area y en la parte baja observamos los métodos de acceso a datos que nos permiten poblar al Businness Entity representado como un DataTable.




Implementación del Objeto Area.

Este objeto es como un objeto control que invoca a los métodos de acceso a datos definidos para cada TableAdapter

Imports Microsoft.VisualBasic

Public Class Area

Public Function ObtenerAreas() As DataSet1.AREADataTable
Dim AreasAdapter As New DataSet1TableAdapters.AREATableAdapter()
Dim Areas As DataSet1.AREADataTable = AreasAdapter.GetData
Return Areas
End Function

End Class

Podemos observar que esta clase crea una instacia del adaptador correspondiente al objeto Entity que deseamos trabajar en este caso: “AreaTableAdapter” luego invocamos su método de acceso a datos llamado GetData que nos regresa un DataTable con cada una de las áreas y que retornamos a la capa GUI para hacer DataBinding con un GridView.

Implementación de la capa de Presentación GUI

En esta capa es en donde vamos a poblar los controles visuales. El GridView se carga durante el evento Load de la página aquí crea una instancia del objeto Control Area y éste expone un método público que obtiene las Area que nos regresa en un objeto DataTable con el cual hacemos DataBinding al GridView.


Partial Class Default2
Inherits System.Web.UI.Page

Dim objArea As New Area

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then

Dim objArea As New Area
Me.GridView1.DataSource = objArea.ObtenerAreas
Me.GridView1.DataBind()

End If
End Sub

'Implementación del Pagineo la Grid
Protected Sub GridView1_PageIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewPageEventArgs) Handles GridView1.PageIndexChanging
Me.GridView1.PageIndex = e.NewPageIndex
Me.GridView1.DataSource = objArea.ObtenerAreas()
Me.GridView1.DataBind()
End Sub

'Borrar el registro seleccionado en el Grid
Protected Sub GridView1_RowDeleting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewDeleteEventArgs) Handles GridView1.RowDeleting
'Para detectar el segundo postback generado por el ButtonField del tipo Image se verifican sus coordenadas x y.
If (Me.Request("x") Is Nothing) AndAlso (Me.Request("y") Is Nothing) Then

Dim iAffect As Int16
objArea = New Area
iAffect = objArea.DeleteArea(Me.GridView1.Rows(e.RowIndex).Cells.Item(0).Text)
Else
Me.GridView1.DataSource = objArea.ObtenerAreas()
Me.GridView1.DataBind()
End If
End Sub

'Al seleccionar un área se muestra en los controles independientes.
Protected Sub GridView1_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles GridView1.SelectedIndexChanged
Me.LabelIdArea.Text = Me.GridView1.Rows(Me.GridView1.SelectedIndex).Cells.Item(0).Text
Me.TextBox2.Text = Me.GridView1.Rows(Me.GridView1.SelectedIndex).Cells.Item(1).Text
Me.TextBox1.Text = Me.GridView1.Rows(Me.GridView1.SelectedIndex).Cells.Item(2).Text
Me.CheckBox1.Text = Me.GridView1.Rows(Me.GridView1.SelectedIndex).Cells.Item(3).Text
End Sub


End Class

Conclusión: Ésta es una de las formas propuestas por Microsoft.

Ejemplo de una Aplicación ASP.NET 2.0 en 3 capas con Colecciones de Objetos Personalizados

Para el desarrollo de esta aplicación utilizamos Visual Basic .NET con Visual Studio .NET 2005.
La arquitectura es en 3 capas con uso del patrón Mapper.

Estrucutura del proyecto ASP.NET
- Una carpeta App_Code con 3 carpetas.

  • La Carpeta Entities. Esta carpeta contiene a todos las clases que corresponden a los objetos de negocio del tipo entity, es decir objetos persistentes en la Base de Datos

  • La Carpeta ColeccionesObj. Esta carpeta contiene a las clases que operan como colecciones de objetos de los objetos entity, es decir existe un objeto collecion por cada objeto Entity.

  • La Carpeta Mappers. Esta carpeta contiene a las clases encargadas de crear objetos entity uno o más con los registros que obtiene de la base de datos y los objetos creados son consumidos por la capa de presentación. Los objetos mappers contienen los métodos necesarios para obtener y persistir los objetos del tipo Entity. Para cada objeto entity existe un objeto Mapper, pero cada uno de estos tiene el patrón Singleton para asegurar que exista una única instancia en toda la aplicación. Podriamos decir que este objeto es el encargado del acceso a datos y hace uso las objetos entity que devueve a la capa de presentación.









-La forma Web “Area.aspx” representa a la capa GUI

Las 3 capas

En una arquitectura en 3 capas tenemos la capa de presentación o GUI, en nuestro ejemplo son los archivos aspx. La capa de Negocio que en nuestro ejemplo la representan nuestras clases Entity ubicados en la carpeta Entites y la capa de Acceso a Datos representada por los objetos Mappers en nuestra carpeta Mappers y la Capa de datos son los objetos de la Base de Datos en nuestro ejemplo se representa con store procedures. En este caso los objetos Mappers son los encargados de comunicar las 3 capas y pasar datos del mundo relacional al de objetos.


















Detalles de la Implementación

Tenemos una tabla llamada Area para la cual queremos implementar una forma de mantenimiento. La pantalla a la que queremos dar funcionalidad con la arquitectura en 3 capas es:






















Esta es la estructura de a tabla de la base de dato correspondiente al Area y los datos que regresa el store procedure que estaremos ejecutando.























Implementación de la clase Entity (Objeto de Capa de Negocio)

Algunas de las formas de representar a los Business Entities en la aplicación son:
· XML
· DataSet Genericos
· DataSet Tipados
· Componentes Business Entities personaizados
· Componentes Business Entities con comportamiento CRUD



Para nuestro ejemplo utilizaremos el caso de ”Componentes Business Entities Personalizados”.

La clase Entity representa al objeto de negocio Area. Para ello agregamos una Clase en la carpeta Entities con el nombre AreaBLL.

Imports Microsoft.VisualBasic

Public Class AreaBLL

Private _idArea As Integer
Private _nombreArea As String
Private _obsArea As String
Private _esActivaArea As Boolean
Private _fechaModificArea As Date
Private _usrModificArea As String
Private _idAreaPadre As Integer
Private _AreaPadre As String

Public Property IdArea() As Integer
Get
Return Me._idArea
End Get
Set(ByVal value As Integer)
Me._idArea = value
End Set
End Property

Public Property NombreArea() As String
Get
Return Me._nombreArea
End Get
If value.Trim.Length > 0 Then
Me._nombreArea = value
Else
Throw New Exception("Debe proporcionar el nombre del área")
End If
End Property

Public Property ObsArea() As String
Get
Return Me._obsArea
End Get
Set(ByVal value As String)
Me._obsArea = value
End Set
End Property

Public Property EsActivaArea() As Boolean
Get
Return Me._esActivaArea
End Get
Set(ByVal value As Boolean)
Me._esActivaArea = value
End Set
End Property

Public Property FechaModificArea() As Date
Get
Return Me._fechaModificArea
End Get
Set(ByVal value As Date)
Me._fechaModificArea = value
End Set
End Property

Public Property UsrModificArea() As String
Get
Return Me._usrModificArea
End Get
Set(ByVal value As String)
Me._usrModificArea = value
End Set
End Property

Public Property IdAreaPadre() As Integer
Get
Return Me._idAreaPadre
End Get
Set(ByVal value As Integer)
Me._idAreaPadre = value
End Set
End Property

Public Property AreaPadre() As String
Get
Return Me._AreaPadre
End Get
Set(ByVal value As String)
Me._AreaPadre = value
End Set
End Property

End Class

Podemos observar que se compone de propiedades privadas que representan a cada una de las columnas de la tabla. Para que dichas propiedades sean accesibles creamos los métodos públicos que leen o escriben en las propiedades. (Con esto implementamos el concepto de Encapsulamiento de la teoría de la Programación Orientada a Objetos).

Implementación de la clase colección para cada objeto Entity

Ahora requerimos construir una clase que nos permita contener una colección de objetos entidad. Si observamos la pantalla que deseamos dar funcionalidad tenemos una rejilla que debe poblarse con objetos entidad del tipo AreaBLL. Agregamos una clase que llamaremos Area_Coleccion y que debe heredar de la clase CollectionBase.

Imports Microsoft.VisualBasic

Public Class Area_Coleccion
Inherits CollectionBase
Default Public Property Item(ByVal index As Integer) As AreaBLL
Get
Return CType(List(index), AreaBLL)
End Get
Set(ByVal value As AreaBLL)
List(index) = value
End Set
End Property

Public Function Add(ByVal value As AreaBLL) As Integer
Return (List.Add(value))
End Function

Public Function IndexOf(ByVal value As AreaBLL) As Integer
Return (List.IndexOf(value))
End Function

Public Sub Insert(ByVal index As Integer, ByVal value As AreaBLL)
List.Insert(index, value)
End Sub

Public Sub Remove(ByVal value As AreaBLL)
List.Remove(value)
End Sub

Public Function Contains(ByVal value As AreaBLL) As Boolean
Return (List.Contains(value))
End Function
End Class

Podemos observar que los métodos de esta clase reciben objetos del tipo Area. Es así que para cada objeto Entity que tengamos debemos crear un objeto Colección, esto si es que requerimos devolver una colección de objetos a la capa de datos. En nuestro ejemplo si requerimos poblar un GridView que muestra a un conjunto de objetos Area.

Implementación de la clase Mapper para cada objeto Entity

Esta es la clase que se encarga de regresar un objeto Entity a la capa de presentación o una colección de objetos de acuerdo al método invocado.

Como esta clase es la que accede a la base de datos utilizamos el DataBlock de Microsoft. Accedemos a los datos a través de store procedures. La cadena de conexión se encuentra en el archivo web.config


Si observamos el método “GetAllArea” retorna una colección de objetos del tipo Area_Colección. Se extraen los registros de base de datos a través de un SqlDataReader y luego se recorre a cada uno y con cada renglon se pobla un objeto Entity AreaBLL, luego el objeto poblado se agrega a la Colección de objetos Area y así sucesivamente para cada objeto creado de acuerdo a los registros de base de datos obtenidos en el SqlDataReader.

Imports Microsoft.VisualBasic
Imports Microsoft.ApplicationBlocks.Data
Imports System.Data.SqlClient
Imports System.Reflection
Imports System.Data

Public Structure AreaMapper
Private _SQLCon As String

'Propiedades necesarias para implementar la clase como Singleton.
Private Shared mInstance As AreaMapper
Private Shared mMutex As New System.Threading.Mutex()

Public ReadOnly Property SQLCon() As Integer
Get
Return Me._SQLCon
End Get
End Property

'Implementando la clase como Singleton.
Public Shared Function GetInstance() As AreaMapper
mMutex.WaitOne()
If mInstance Is Nothing Then
mInstance = New AreaMapper()
End If
mMutex.ReleaseMutex()
Return mInstance
End Function

Public Sub New()
_SQLCon = ConfigurationManager.ConnectionStrings("TimonConnectionString").ToString()
End Sub

'Iniciar con la cadena de conexión
Sub New(ByVal pSQLCon As String)
_SQLCon = pSQLCon
End Sub

Public Function GetAllArea() As Area_Coleccion
Dim dr As SqlDataReader = Nothing
Try
dr = SqlHelper.ExecuteReader(_SQLCon, Data.CommandType.StoredProcedure, "spSelectArea")
Dim ListaAreas As New Area_Coleccion
While dr.Read()
ListaAreas.Add(PopulateArea(dr))
End While
Return ListaAreas

Catch ex As SqlException
Throw New Exception(ex.Message & " En la rutina: " & MethodInfo.GetCurrentMethod.Name)
Catch ex As Exception
Throw New Exception(ex.Message & " En la rutina: " & MethodInfo.GetCurrentMethod.Name)
Finally
If Not dr Is Nothing AndAlso Not dr.IsClosed Then
dr.Close()
End If
End Try
End Function

Private Function PopulateArea(ByVal dr As SqlDataReader) As AreaBLL
Dim ObjArea As New AreaBLL
Try

ObjArea.IdArea = Convert.ToInt32(dr(0)) 'IdArea
ObjArea.NombreArea = Convert.ToString(dr(1)) 'NombreArea

'ObsArea
If Not dr(2) Is DBNull.Value Then
ObjArea.ObsArea = Convert.ToString(dr(2))
End If

'EsActivaArea
ObjArea.EsActivaArea = Convert.ToByte(dr(3))
'AreaPadre
ObjArea.AreaPadre = Convert.ToString(dr(4))

Return ObjArea

Catch ex As SqlException
Throw New Exception(ex.Message & "En la rutina: " & MethodInfo.GetCurrentMethod.Name)
Catch ex As Exception
Throw New Exception(ex.Message & "En la rutina: " & MethodInfo.GetCurrentMethod.Name)
End Try
End Function


End Structure



Implementación de la capa de Presentación GUI

En esta capa es en donde vamos a poblar los controles visuales con objetos Entity o listas de objetos Entity. Nosotros queremos que la cargarse la forma web se poble el GridView con los datos de todas las áreas que se encuentran en la tabla Area de la base de datos. Hacemos esto en el evento Load. Podemos observar como creamos un objeto de la clase AreaMapper y que para poblar el GridView ejecutamos su método GetAllArea y que lo establecemos como DataSource al GridView a esto se lo conoce como DataBinding con objetos de negocio.

Partial Class _Default
Inherits System.Web.UI.Page

Private ObjAreaMapper As AreaMapper

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not Page.IsPostBack Then
ObjAreaMapper = AreaMapper.GetInstance()
Call poblarDBGrid()
Call poblarListaAreasPadre()

End If
End Sub

Private Sub poblarDBGrid()
Try
Me.GridView1.DataSource = ObjAreaMapper.GetAllArea
Me.GridView1.DataBind()
Me.LabelTotalRegGrid.Text = "Total de Registros: " & Me.GridView1.Rows.Count
Catch ex As Exception
Me.LabelInfo.Text = ex.Message
End Try
End Sub

Private Sub poblarListaAreasPadre()
Try

Me.DropDownList1.DataSource = ObjAreaMapper.SelectAreas
Me.DropDownList1.DataValueField = "IdArea"
Me.DropDownList1.DataTextField = "NombreArea"
Me.DropDownList1.DataBind()

Catch ex As Exception
Me.LabelInfo.Text = ex.Message
End Try
End Sub
End Class


Este es un diagrama que expone de manera general la arquitectura de la aplicación desarrollada






























Referencias
http://msdn2.microsoft.com/en-us/library/ms978496.aspx

http://builder.com.com/5100-6387-1049997.html



Excelente artículo DateSet vs. Colecciones de Objetos

Este es el link a un excelente articulo que expresa ventajas y desventajas de usar DataSets y Colecciones de Objetos, cuando usar unos y otros. Muchas veces como desarrolladores utilizamos lo que nos dicen los manuales de microsoft o los libros, pero no sabemos tecnicamente que es mejor, tomamos lo que mejor entendemos o lo que nos parece más fácil, pero creo que esto no es del todo correcto.

http://msdn.microsoft.com/msdnmag/issues/05/08/CuttingEdge/

Algo de lo que dice este articulo con respecto a los DataSets es que estos implementan la serialización de manera automática, pero ésta tiene un costo muy considerable cuando se hace para miles de registros. Soportan la concurrencia optimista, el databinding , permiten la implementación de links lógicos entre tablas cuyo fin es hacer árboles hijos de datos, pero esto último comunmente no es utilizado.

Con respecto a las colecciones de objetos cuya implementación requiere heredar de la clase
CollectionBase son considerablemente más rápidos en la recuperación de datos entre las capas. Requieren un mayor trabajo de codificación.

En conclusión el artículo propone pensar muy bien en usar DataSet ya que al parecer estos presentan más desventajas , claro esto depende de la funcionalidad que se requiera ya que los DataSet soportan funcionalidad que no soportan las colecciones de objetos aunque estos últimos presentan un mayor desempeño.