Software y Aplicaciones Web

Blog de desarrollo de software y aplicaciones web

Comentarios Recientes

Comment RSS

MSDN Home Page (Argentina)


C# Corner


AspAlliance.com

Declaración

Las opiniones en este blog se proporcionan "TAL CUAL", sin garantías,  no confieren derechos y no reflejan, necesariamente, la opinión de quienes me contratan.
Algunas cuestiones que se comentan en el blog no son reales, cualquier similitud con alguna persona viva o muerta no es más que una coincidencia, tampoco significa que necesite terapia, soy asi.

© Copyright 2007-2010

Propaganda

Este sitio implementa publicidad basada en intereses
Oct
12.
2009

  ABM con ADO.NET

Con esta publicación vamos a realizar lo que la mayoría de los desarrolladores denominan un ABM (Altas, Bajas y Modificaciones) que también se conoce como CRUD (Create, Retrieve, Update and Delete).

En esencia se trata de las operaciones básicas que pueden realizarse sobre una tabla de información de alguna base de datos.

Como mostré en publicaciones anteriores, el asistente que facilita Visual Studio permite generar un formulario o página web que lo hace todo, todas estas operaciones se pueden realizar con un GridView que "viene" con todo el comportamiento listo para utilizar. Sin embargo la implementación de todo el código en un único archivo (en este caso el .aspx) está lejos de ser un desarrollo en capas; por otro lado la o las aplicaciones pueden requerir de una lógica de negocios compleja que debe mantenerse en clases separadas de la interfaz de usuario.

Consecuentemente vamos a realizar el ejercicio tratando de separar un poco las responsabilidades, por un lado vamos a contar con una interfaz de usuario que nos permita mostrar la información, y otro componente que nos permita editar la información ya sea de un nuevo registro o de uno existente en la tabla. Obviamente vamos a implementar un mecanismo de acceso a datos en una clase que para este ejercicio será la misma entidad del ABM o CRUD.

 

En el ejercicio vamos a necesitar de una tabla, que podemos crear en cualquier base de datos. El siguiente script crea una tabla llamada Personas (justamente para almacenar información de personas) que cuenta con los siguientes atributos: Nombre, Apellido, Mail y Comentarios. Para poder manipular eficientemente la tabla vamos a incorporar un identificador único de cada registro (Id) que será clave primaria y dejaremos que el motor de la base de datos lo auto incremente (IDENTITY), por otro lado vamos a implementar un borrado lógico de modo que hace falta un indicador para saber si el registro fue "borrado".

El script es el siguiente:

/****** Object:  Table [dbo].[Personas]    Script Date: 10/11/2009 09:57:16 ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Personas]') AND type in (N'U'))
DROP TABLE [dbo].[Personas]
GO
/****** Object: Table [dbo].[Personas] Script Date: 10/11/2009 09:57:16 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Personas]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Personas](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Nombre] [nchar](100) NULL,
[Apellido] [nchar](100) NULL,
[Mail] [nchar](100) NULL,
[Comentarios] [nvarchar](max) NULL,
[IsDeleted] [bit] NOT NULL,
CONSTRAINT [PK_Personas] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
END
GO
IF NOT EXISTS (SELECT * FROM dbo.sysobjects WHERE id = OBJECT_ID(N'[DF_Personas_IsDeleted]') AND type = 'D')
BEGIN
ALTER TABLE [dbo].[Personas] ADD CONSTRAINT [DF_Personas_IsDeleted] DEFAULT ((0)) FOR [IsDeleted]
END

GO

En un proyecto de alguna solución vamos a agregar un elemento (una clase) para implementar justamente la clase Person que nos permitirá acceder a la información de cada una de las personas que almacenemos en la tabla.

La clase Person implementa en su estructura interna todo lo necesario para almacenar la información que se corresponde con cada atributo de la tabla Personas, también implementaremos las propiedades necesarias para acceder a cada uno de esos campos. Siempre hace falta el constructor por defecto de modo que también se implementa inicializando todos los campos de la estructura interna.

El comportamiento que por ahora implementaremos en la clase Person, es el correspondiente a las operaciones:

  • Insert() para agregar una persona en la tabla
  • SelectById(int id) para obtener los datos de la persona cuyo identificador único se pasa como parámetro
  • Update() para actualizar los datos de la persona en la tabla
  • Delete() para borrar (lógicamente) a una persona en la tabla

A continuación tenemos el código completo de Person.cs

using System.Configuration;
using System.Data;
using System.Data.SqlClient;
 
namespace ADO2
{
  public class Person
  {
 
    #region Estructura interna y Propiedades
    /// <summary>
    /// Identificador de la persona
    /// </summary>
    private int _Id;
    /// <summary>
    /// Accede al identificador de la persona
    /// </summary>
    public int Id
    {
      get { return _Id; }
      set { _Id = value; }
    }
    /// <summary>
    /// Nombre de la persona
    /// </summary>
    private string _Nombre;
    /// <summary>
    /// Accede al nombre de la persona
    /// </summary>
    public string Nombre
    {
      get { return _Nombre; }
      set { _Nombre = value; }
    }
    /// <summary>
    /// Apellido de la persona
    /// </summary>
    private string _Apellido;
    /// <summary>
    /// Accede al apellido de la persona
    /// </summary>
    public string Apellido
    {
      get { return _Apellido; }
      set { _Apellido = value; }
    }
    /// <summary>
    /// Mail de la persona
    /// </summary>
    private string _Mail;
    /// <summary>
    /// Accede al mail de la persona
    /// </summary>
    public string Mail
    {
      get { return _Mail; }
      set { _Mail = value; }
    }
    /// <summary>
    /// Comentarios
    /// </summary>
    private string _Comentarios;
    /// <summary>
    /// Accede a los comentarios
    /// </summary>
    public string Comentarios
    {
      get { return _Comentarios; }
      set { _Comentarios = value; }
    }
    /// <summary>
    /// Indicador si está borrado (lógicamente)
    /// </summary>
    private bool _IsDeleted;
    /// <summary>
    /// Accede al indicador de si está borrado
    /// </summary>
    public bool IsDeleted
    {
      get { return _IsDeleted; }
      set { _IsDeleted = value; }
    }
    #endregion
 
    #region Constructores
    /// <summary>
    /// Constructor por defecto
    /// </summary>
    public Person()
    {
      this.Id = 0;
      this.Nombre = string.Empty;
      this.Apellido = string.Empty;
      this.Mail = string.Empty;
      this.Comentarios = string.Empty;
      this.IsDeleted = false;
    }
    #endregion
 
    #region SQL 
    public void Insert()
    {
      SqlConnection mySqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Postal"].ConnectionString);
      SqlCommand mySqlCommand = new SqlCommand();
      mySqlCommand.Connection = mySqlConnection;
      mySqlCommand.CommandText = "INSERT INTO Personas (Nombre, Apellido, Mail, Comentarios, IsDeleted) VALUES (@Nombre, @Apellido, @Mail, @Comentarios, DEFAULT)";
      mySqlCommand.CommandType = CommandType.Text;
      mySqlCommand.Parameters.AddWithValue("@Nombre", this.Nombre);
      mySqlCommand.Parameters.AddWithValue("@Apellido", this.Apellido);
      mySqlCommand.Parameters.AddWithValue("@Mail", this.Mail);
      mySqlCommand.Parameters.AddWithValue("@Comentarios", this.Comentarios);
      mySqlConnection.Open();
      mySqlCommand.ExecuteNonQuery();
      mySqlConnection.Close();
    }
    public void SelectById(int id)
    {
      SqlConnection mySqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Postal"].ConnectionString);
      SqlCommand mySqlCommand = new SqlCommand();
      mySqlCommand.Connection = mySqlConnection;
      mySqlCommand.CommandText = "SELECT Id, Nombre, Apellido, Mail, Comentarios, IsDeleted FROM Personas WHERE Id=@Id";
      mySqlCommand.CommandType = CommandType.Text;
      mySqlCommand.Parameters.AddWithValue("@Id", id);
      mySqlConnection.Open();
      SqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader();
      if (mySqlDataReader.Read())
      {
        this.Id = mySqlDataReader.GetInt32(0);
        if (!mySqlDataReader.IsDBNull(1))
          this.Nombre = mySqlDataReader.GetString(1);
        if (!mySqlDataReader.IsDBNull(2))
          this.Apellido = mySqlDataReader.GetString(2);
        if (!mySqlDataReader.IsDBNull(3))
          this.Mail = mySqlDataReader.GetString(3);
        if (!mySqlDataReader.IsDBNull(4))
          this.Comentarios = mySqlDataReader.GetString(4);
        this.IsDeleted = mySqlDataReader.GetBoolean(5);
      }
      mySqlConnection.Close();
    }
    public void Update()
    {
      SqlConnection mySqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Postal"].ConnectionString);
      SqlCommand mySqlCommand = new SqlCommand();
      mySqlCommand.Connection = mySqlConnection;
      mySqlCommand.CommandText = "UPDATE Personas SET Nombre = @Nombre, Apellido = @Apellido, Mail = @Mail, Comentarios = @Comentarios WHERE Id=@Id";
      mySqlCommand.CommandType = CommandType.Text;
      mySqlCommand.Parameters.AddWithValue("@Id", this.Id);
      mySqlCommand.Parameters.AddWithValue("@Nombre", this.Nombre);
      mySqlCommand.Parameters.AddWithValue("@Apellido", this.Apellido);
      mySqlCommand.Parameters.AddWithValue("@Mail", this.Mail);
      mySqlCommand.Parameters.AddWithValue("@Comentarios", this.Comentarios);
      mySqlConnection.Open();
      mySqlCommand.ExecuteNonQuery();
      mySqlConnection.Close();
    }
    public void Delete()
    {
      Delete(true);
    }
    public void Delete(bool isDeleted)
    {
      this.IsDeleted = isDeleted;
      SqlConnection mySqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Postal"].ConnectionString);
      SqlCommand mySqlCommand = new SqlCommand();
      mySqlCommand.Connection = mySqlConnection;
      mySqlCommand.CommandText = "UPDATE Personas SET IsDeleted = @IsDeleted WHERE Id=@Id";
      mySqlCommand.CommandType = CommandType.Text;
      mySqlCommand.Parameters.AddWithValue("@Id", this.Id);
      mySqlCommand.Parameters.AddWithValue("@IsDeleted", this.IsDeleted);
      mySqlConnection.Open();
      mySqlCommand.ExecuteNonQuery();
      mySqlConnection.Close();
    }
    #endregion
  }
}

Observen que estamos utilizando una cadena de conexión que se encuentra en el web.config, utilicé la misma de los ejercicios de Provincias, Departamentos y Localidades porque en esa base de datos he creado la tabla Personas.

Es interesante ver como se implementa el método Delete() que en su forma simple (sin argumentos) invoca al otro método que permite pasar el valor del indicador, con esto podremos fijar en verdadero o falso el valor del atributo en cuestión.

Es importante destacar que después de cada operación de acceso a los datos se debe cerrar la conexión de manera que el recolector de basura (en algún momento) libere los recursos utilizados para cada objeto SqlConnectio, SqlCommand y los otros. En realidad la codificación de estos métodos debería hacerse con estructuras using( ... ) que garantizan la liberación de los recursos, pero por ahora veamos el ejemplo de CRUD.

 

La interfaz de usuario para realizar el CRUD presenta dos aspectos claramente diferenciados, en uno de ellos el usuario debe poder visualizar los registros de la tabla, lo que se puede lograr con un componente del tipo GridView; el otro aspecto es el formulario web que permite editar los datos de una persona en particular. De manera que estamos en presencia de un mecanismo de mediana complejidad que se puede implementar en el mismo formulario utilizando un componente MultiView que habilita y deshabilita las vistas de acuerdo a cómo el usuario desarrolla su actividad, también podría ser un mecanismo con dos paneles que podemos ocultar o mostrar de acuerdo a lo que el usuario realiza y la que voy a mostrar que es con dos formularios web de manera que desde uno de ellos invocamos al otro y viceversa.

El primer formulario lo vamos a llamar ListPerson.aspx, que nos permitirá mostrar los datos de las personas en la tabla. Básicamente tendrá un GridView cuyo origen de datos (por ahora) lo vamos a desarrollar con el asistente de Visual Studio donde indicaremos que obtenga todos los atributos de la tabla, vamos a ocultar las columnas del identificador único de cada registro (eso no le interesa al usuario) así como el indicador de registro borrado y el atributo de comentarios porque no hace falta en este caso.

Es necesario convertir a TemplateField la columna del identificador de registro, esto nos permitirá averiguar el Id de cada registro, hace falta incorporar un comando para seleccionar un registro en particular para modificarlo o borrarlo y también hace falta un botón que nos permita agregar nuevos registros (personas).

El siguiente código es el que corresponde a ListPerson.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ListPerson.aspx.cs" Inherits="ADO2.ListPerson" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Panel ID="pnlListPerson" runat="server" 
        GroupingText="Consulta de Personas">
        <asp:GridView ID="gvPerson" runat="server" AutoGenerateColumns="False" 
          DataKeyNames="Id" DataSourceID="sdsPerson" 
          onselectedindexchanging="gvPerson_SelectedIndexChanging" >
          <Columns>
            <asp:TemplateField HeaderText="Id" InsertVisible="False" SortExpression="Id" 
              Visible="True">
              <ItemTemplate>
                <asp:Label ID="lblId" runat="server" Text='<%# Bind("Id") %>'></asp:Label>
              </ItemTemplate>
            </asp:TemplateField>
            <asp:BoundField DataField="Nombre" HeaderText="Nombre" 
              SortExpression="Nombre" />
            <asp:BoundField DataField="Apellido" HeaderText="Apellido" 
              SortExpression="Apellido" />
            <asp:BoundField DataField="Mail" HeaderText="Mail" SortExpression="Mail" />
            <asp:BoundField DataField="Comentarios" HeaderText="Comentarios" 
              SortExpression="Comentarios" Visible="False" />
            <asp:CheckBoxField DataField="IsDeleted" HeaderText="IsDeleted" 
              SortExpression="IsDeleted" Visible="False" />
            <asp:CommandField ShowSelectButton="True" />
          </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="sdsPerson" runat="server" 
          ConnectionString="<%$ ConnectionStrings:Postal %>" 
          SelectCommand="SELECT [Id], [Nombre], [Apellido], [Mail], [Comentarios], [IsDeleted] FROM [Personas] WHERE ([IsDeleted] = @IsDeleted)">
          <SelectParameters>
            <asp:Parameter DefaultValue="false" Name="IsDeleted" Type="Boolean" />
          </SelectParameters>
        </asp:SqlDataSource>
        <div style="float:right; padding-top:20px; padding-right:20px;">
          <asp:Button ID="btnAddPerson" runat="server" onclick="btnAddPerson_Click" 
            Text="Agregar Persona" />
        </div>
      </asp:Panel>
    
    </div>
    </form>
</body>
</html>

Observen que se atrapa el evento de intento de selección de un renglón del GridView, porque es en ese momento cuando pasaremos al formulario de edición. Dejo para que Uds., desarrollen el "maquillaje" de la presentación y vean que hacerlo de este modo realmente independiza la realización de la interfaz de usuario (presentación) del proceso mismo del CRUD.

El código asociado a este formulario web (el ListPerson.aspx.cs) es el siguiente:

using System;
using System.Web.UI.WebControls;
 
namespace ADO2
{
  public partial class ListPerson : System.Web.UI.Page
  {
    /// <summary>
    /// Atrapa el evento de cargar la página o formulario web
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void Page_Load(object sender, EventArgs e)
    {
    }
    /// <summary>
    /// Atrapa el evento de pulsar el boton Agregar Persona
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void btnAddPerson_Click(object sender, EventArgs e)
    {
      Response.Redirect("~/EditPerson.aspx");
    }
    /// <summary>
    /// Atrapa el evente de intento de seleccionar un registro
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.GridViewSelectEventArgs"/> instancia con información del evento.</param>
    protected void gvPerson_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
    {
      e.Cancel = true;
      GridViewRow row = gvPerson.Rows[e.NewSelectedIndex];
      Int32 id = Int32.Parse(((Label)row.FindControl("lblId")).Text);
      Response.Redirect("~/EditPerson.aspx?Id=" + id.ToString());
    }
  }
}

No hay mucho para comentar, el método que atrapa el evento de Click sobre el boton "Agregar Persona" invoca otro formulario, que es donde se realizara la edición.

El método que atrapa el intento de cambio de selección es más interesante; lo primero que hace es cancelar lo que el GridView quiere hacer, de ese modo tomamos el control de la ejecución, obtenemos la fila (row) que se pretende seleccionar, buscamos en esa fila el identificador del registro y se lo pasamos mediante el QueryString al formulario de edición.

 

El segundo formulario, el que llamamos EditPerson.aspx es el que nos permite agregar, editar y eventualmente borrar un registro. Lo realizamos mediante una tabla que nos permite organizar los componentes de interfaz de usuario necesarios para mostrar y editar los atributos de un registro, incorporamos tres botones para poder grabar, borrar y cancelar que obviamente realizarán el comportamiento esperado para cada uno de ellos; también incorporamos un rótulo donde podremos mostrar mensajes de error que atraparemos en caso que las validaciones no se cumplan o se produzca un error en la base de datos.

El código de EditPerson.aspx es el siguiente:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="EditPerson.aspx.cs" Inherits="ADO2.EditPerson" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
    <style type="text/css">
      .style1
      {
        width: 100%;
      }
      .style2
      {
        width: 30%;
      }
      .txtLonger
      {
        width: 400px;
      }
      .txtMultiLine
      {
        width: 400px;
        height: 100px;
      }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
      <asp:Panel ID="pnlEditPerson" runat="server" GroupingText="Datos de la Persona">
        <table class="style1">
          <tr>
            <td class="style2">
              <asp:Label ID="lblId" runat="server" Text="Código"></asp:Label>
            </td>
            <td>
              <asp:TextBox ID="txtId" runat="server" ReadOnly="True"></asp:TextBox>
            </td>
          </tr>
          <tr>
            <td class="style2">
              <asp:Label ID="lblNombre" runat="server" Text="Nombre"></asp:Label>
            </td>
            <td>
              <asp:TextBox ID="txtNombre" runat="server" CssClass="txtLonger"></asp:TextBox>
            </td>
          </tr>
          <tr>
            <td class="style2">
              <asp:Label ID="lblApellido" runat="server" Text="Apellido"></asp:Label>
            </td>
            <td>
              <asp:TextBox ID="txtApellido" runat="server" CssClass="txtLonger"></asp:TextBox>
            </td>
          </tr>
          <tr>
            <td class="style2">
              <asp:Label ID="lblMail" runat="server" Text="Mail"></asp:Label>
            </td>
            <td>
              <asp:TextBox ID="txtMail" runat="server" CssClass="txtLonger"></asp:TextBox>
            </td>
          </tr>
          <tr>
            <td class="style2">
              <asp:Label ID="lblComentarios" runat="server" Text="Comentarios"></asp:Label>
            </td>
            <td>
              <asp:TextBox ID="txtComentarios" runat="server" CssClass="txtMultiLine" 
                TextMode="MultiLine"></asp:TextBox>
            </td>
          </tr>
          <tr>
            <td colspan="2">
              <div>
                <asp:Label ID="lblMessage" runat="server" EnableViewState="False"></asp:Label>
              </div>
              <div style="float:right; padding-top:20px; padding-right:20px;">
                <asp:Button ID="btnSave" runat="server" Text="Grabar" onclick="btnSave_Click" />
                &nbsp;&nbsp;&nbsp;&nbsp;
                <asp:Button ID="btnDelete" runat="server" Text="Borrar" onclick="btnDelete_Click" 
                  OnClientClick="javascript:return(window.confirm('Esta Seguro?'))" />
                &nbsp;&nbsp;&nbsp;&nbsp;
                <asp:Button ID="btnCancel" runat="server" Text="Cancelar" 
                  onclick="btnCancel_Click" />
              </div>
            </td>
          </tr>
        </table>
      </asp:Panel>
    
    </div>
    </form>
</body>
</html>

Observen que en el botón de "Borrar" se ha incorporado un poquito de JavaScript que nos permite confirmar la operación.

El código asociado a este formulario debe comenzar analizando el QueryString para saber si se trata de un "Alta" o de una "Modificación", recordemos que desde ListPerson.aspx se invoca a este formulario pasando el Id del registro que se desea "modificar" o "borrar", cuando se trata de un nuevo registro no se pasa ningún identificador, por otro lado es importante indicar que este control solo debe realizarse la primera vez que se carga el formulario.

En EditPerson.aspx.cs se muestra en regiones separadas los diferentes aspectos del código; por un lado están los métodos que atrapan los eventos necesarios y por otro lado los métodos que realizan lo necesario en un ABM sencillo. Es importante destacar esta separación de responsabilidades para mostrar cómo cada parte del código solo realiza un comportamiento en particular.

using System;
 
namespace ADO2
{
  public partial class EditPerson : System.Web.UI.Page
  {
    #region Eventos del Formulario
    /// <summary>
    /// Atrapa el evento de cargar la página o formulario web
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void Page_Load(object sender, EventArgs e)
    {
      if (!this.IsPostBack)
      {
        string args = Request.QueryString["Id"];
        if (args != null)
        {
          btnDelete.Visible = true;
          Edit(Int32.Parse(args));
        }
        else
        {
          btnDelete.Visible = false;
          Edit(0);
        }
      }
    }
    /// <summary>
    /// Atrapa el evento de pulsar el boton Grabar
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void btnSave_Click(object sender, EventArgs e)
    {
      Save();
    }
    /// <summary>
    /// Atrapa el evento de pulsar el boton Borrar
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void btnDelete_Click(object sender, EventArgs e)
    {
      Delete();
    }
    /// <summary>
    /// Atrapa el evento de pulsar el boton Cancelar
    /// </summary>
    /// <param name="sender">Objeto que ejecuta el evento</param>
    /// <param name="e">La <see cref="System.EventArgs"/> instancia con información del evento.</param>
    protected void btnCancel_Click(object sender, EventArgs e)
    {
      Cancel();
    }
    #endregion
 
    #region Comportamiento ABM
    /// <summary>
    /// Habilia la edición de la persona que corresponde al
    /// Id que se pasa como parámetro
    /// </summary>
    /// <param name="id">Identificador de persona</param>
    protected void Edit(int id)
    {
      Person person = new Person();
      person.SelectById(id);
      txtId.Text = person.Id.ToString();
      txtNombre.Text = person.Nombre;
      txtApellido.Text = person.Apellido;
      txtMail.Text = person.Mail;
      txtComentarios.Text = person.Comentarios;
    }
    /// <summary>
    /// Graba los datos editados
    /// </summary>
    protected void Save()
    {
      try
      {
        this.Validate();
        if (!this.IsValid)
        {
          throw new Exception("Error en validaciones...");
        }
 
        Person person = new Person();
        person.Id = Int32.Parse(txtId.Text);
        person.Nombre = txtNombre.Text;
        person.Apellido = txtApellido.Text;
        person.Mail = txtMail.Text;
        person.Comentarios = txtComentarios.Text;
        if (person.Id == 0)
        {
          person.Insert();
        }
        else
        {
          person.Update();
        }
        Return();
      }
      catch (Exception ex)
      {
        lblMessage.Text = ex.Message;
      }
    }
    /// <summary>
    /// Borra los datos de la persona (lógicamente)
    /// </summary>
    protected void Delete()
    {
      Int32 id = Int32.Parse(txtId.Text);
      Person person = new Person();
      person.Id = id;
      person.Delete();
      Return();
    }
    /// <summary>
    /// Cancel la edición
    /// </summary>
    protected void Cancel()
    {
      Return();
    }
    /// <summary>
    /// Regresa al formulario de listado
    /// </summary>
    protected void Return()
    {
      Response.Redirect("~/ListPerson.aspx");
    }
    #endregion
  }
}

El método Edit() se utiliza para los atributos de la persona a los componentes de la interfaz de usuario; el método Save() se utiliza tanto para insertar o modificar los datos de una persona y el método Delete() (que debería tener una estructura try ... catch) obviamente borra (lógicamente) una persona. El resto es un poco más de separación de responsabilidades de manera que el mantenimiento de este código sea sencillo.

 

Para leer fuera de línea: ABM con ADO NET.pdf (104,39 kb)

 







Comments (6) -

rafael Argentina

Thursday, October 22, 2009 11:41 AM

rafael

MUY BUENO PARA UNA INTRODUCCION.. ESPERANDO MAS
GRACIASSSSSSS

wpablow Argentina

Tuesday, January 26, 2010 8:32 PM

wpablow

Hola, te comento que llegue a tu tutorial buscando buenas prácticas de desarrollo en ABM. Y quería preguntarte lo siguiente, en realidad son ideas que tengo y me interesa nuevas opiniones:

-Que opinas del uso del SqlDataSource? yo veo como que rompe el orden del código definiendo consultas sql en una capa que no es la de acceso a datos!! Mi idea de buena práctica sería enlazarla en el code behind.

-Que opinas de FormView? me gusta la idea de bindear los controles, pero no lo veo muy práctico y hasta obliga el uso de SqlDataSource, bueno esto no es tan correcto ya que encontre la forma de enlazarlo en el code behind, pero genera mucho mas código.

Cual sería tu modelo correcto de ABM?? que otros controles ves correcto usar y cuales no?

Desde ya muchas gracias!!!

jtentor Argentina

Wednesday, January 27, 2010 5:22 PM

jtentor

No existe el modelo correcto de ABM.
De lo que sí se puede y debe hablar es del Modelo Apropiado de ABM dependiendo de la escala (tamaño) de la aplicación y su futura expasión.

Por ejemplo, si vamos a realizar una aplicación para guardar información de las flores de mi abuela entonces podemos utilizar los generadores que vienen con el Visual Studio que meten todo en una sola página aspx o formulario (si se trato de winform), obviamente no hay arquitectura en capas y lo mejor es continuar manejando esa porción de código con la misma herramienta que la generó. Por supuesto que la aplicación de mi abuela es pequeñísima, el mantenimiento que requiere es mínimo y no va a crecer (ninguna multinación se va a interesar en esa aplicación). En este caso es apropiado por la relación costo-beneficio utilizar los generadores y tener todo en un solo archivo.

El otro caso, es el que vamos a realizar una aplicación para un hipermercado que tiene 200 sucursales distribuidas en todo el país, mantiene relaciones contractuales con grandes proveedores que facilitan y consumen servicios web, donde el mantenimiento y seguimiento de la aplicación debe realizarse día a día y las expectativas de expansión estan presentes permanentemente dado la capacidad de asociación de esta organización con otras. En este caso, inevitablemente se debe planificar la arquietectura de la aplicación en tantas capas y niveles como sea necesario para que el futuro mantenimiento y expansión sea razonable. La lógica de negocio debe estar totalmente separada de las otras capas especialmente de aquellas que involucran presentación porque la tecnología utilizada para impactar el mercado evoluciona y es lógico pensar que la organización destine presupuesto anual para incorporar esas tecnologías, pero siempre deberá preservarse la lógica de negocio. Ni hablar del acceso a datos, es de suponer que una organización de este tipo tenga base de datos distribuidas de modo que el acceso a ellos puede realizarse mediante servicios web privados a la organización o conecciones via una infraestructura de Active Directory a lo largo de todos los servidores de la organización. Otra cuestión son las relaciones contractuales entre proveedores y clientes de la organización, obviamente requerirán de servicios web que faciliten el acceso y procesamiento de información común a sus contratos. Consecuentemente esta aplicación debe conener mucho pero mucho código y especialmente mucha documentación.

Entre los dos ejemplos hay una buena cantidad de posibilidades que el desarrollador debe considerar antes de lanzarse a escribir código y luego encontrarse con que ha invertido más tiempo del necesario para el producto y no lo cobró, o que se quedó chico en el modelo arquitectónico y los cambios o expansiones resultan más costosos que hacer uno nuevo (es lo que normalmente ocurre).

Cuando digo "cambios" no me refiero a cambiar el código porque está mal; un producto de software no se entrega con fallas. Los cambios a los que me refiero son aquellos que el cliente solicita porque sus necesidades de información y procesamiento van cambiando de acuerdo a cómo cambian las condiciones del mercado en el que se mueve.

Respecto del SqlDataSource, es un objeto interesante para armar prototipos y descubrir rápidamente si lo que estamos haciendo es lo que el cliente quiere; en unas horas se puede tener modelos (prototipos) de como sería el ABM, las consultas y chequearlas con el cliente. Luego si el tamaño de la aplicación es pequeño pueden quedar así o se incia un proceso de reemplazo del origen de datos probablemente por objectos de la capa de lógica de negocios y/o servicios web

Cualquiera sea el componente (objeto) que se utiliza en la capa de presentación (FormView, GridView, etc) necesita obtener los datos de algún lugar. Este lugar puede ser un ??DataSource (sql, object, LINQ, etc) o enlazar a dedo en el codebihind cada dato; nuevamente esto depende del tamaño y futuro de la aplicación que se hace.

Finalmente, lo que puedo decir es que cada modelo de abm requiere una cantidad de horas de trabajo y experiencia de los desarrolladores; el cliente está dispuesto a pagar por ello o se conforma con algo simple y rápido, en este caso nuestra etica profesional nos obliga a decirle que eso funciona pero cuando quiera cambiarlo o expandirlo tendra que comenzar de nuevo ...

Espero que sirva.

wpablow Argentina

Thursday, January 28, 2010 5:35 PM

wpablow

Julio, gracias por contestar, es muy interesante lo que escribís, si seguimos la charla en lo que respecta a sistemas que requieren muchos cambios constantes y de tamaño medio-alto:

Podemos resumir que FormView y GridView conectados por codebehind con DataTables que recupero de la capa de negocios sería la mejor opción?

Cambiando de tema: Que opinas de LinQ ??? como ves la performance, tuviste oportunidad de usarlo en proyectos grandes? que opinas de ORM?? Es una tendencia caprichosa o es que la baja de performance se justifica en lo que es orden del código??

Cuando usarias DataTable y cuando LinQ?? o la lógica sigue siendo LinQ para proyectos chicos y DataTables en proyectos de gran acceso al disco??

Digo LinQ pero me refiero a todos los patrones y tecnologías (LinQ, ActiveRecord, etc...)

jtentor Argentina

Friday, January 29, 2010 10:44 PM

jtentor

Definitivamente, los asistentes son para los pequeños productos de software o para completar un prototipo de exploración. Los grandes proyectos necesitan más, mucho más código y una buena arquietectura de soporte.

LINQ, como lo dice el nombre es un lenguaje de consulta embebido en otro lenguaje (en este caso C# y VB); con lo que están intentando facilitar la vida o la curva de aprendizaje de los desarrolladores, se supone que ahora no tienen que aprender SQL lo peor es que muchos creen que con eso pueden diseñar bases de datos. Particularmente estoy convencido de la necesidad del recurso humano que se especialice en diseño y mantenimiento de bases de datos.

Por otro lado el Visual Studio 2008 al incorporar LINQ también incorpora un ORM (Object Rlational Mapper) que crea una clase (DataContext) y una clase por cada tabla o vista de la base de datos que nosotros le indiquemos. Ahi es donde encuentro algo realmente importante, estas clases publican todas las propiedades correspondientes a los campos de la tabla (vista) y además habilitan (utilizando la potenica de C# 3.0) las extensiones con lo que nosotros podemos codificar en el mismo archivo o en otro (dado que son clases parciales) otros aspectos que hagan a la lógica de negocio o mínimamente todas la validaciones necesarias. Mi único problema es que al ser la misma clase debe estar en el mismo DLL, consecuentemente estamos en la misma capa - nivel de una arquitectura.

El otro avance fenomenal que trae LINQ es que podemos desarrollar una librería de clases con el DataContext y todo lo que nos haga falta para cada clase que represente una entidad de la base de datos y eso si sería un DLL, basicamente un DLL que conforma la capa de acceso a datos que puede utilizarse directamente desde la capa de presentación en un gridview por ejemplo. Definitivamente un gran avance.

Todavía no está resuelta la capa de negocios, muchos creen que la capa de negocios es pasar los valores de un campo de una clase a un campo de otra clase y luego invocar un método que vuelque eso en la base de datos; eso es simplemente acceso a datos, la lógica de negocios es mucho más. Por ejemplo decirle a un cliente que te entregue todas sus órdenes de compra, nosostros sabemos que las ordenes de compra estan en una o más tablas en la base de datos y seguramente tenemos objetos del tipo OrdenDeCompra pero el diálogo se realiza con un objeto del tipo cliente (que seguramente delega en otros objetos la tarea) eso es lógica de negocio.

Respecto de ActiveRecord, creo que es un patron interesante que simplifica el acceso a datos dado que al crear un objeto de algún tipo, automáticamente lo estamos insertando o actualizando en la base de datos. Esto se logra en base a una muy elaborada jerarquía de clases que encapsulan todos los métodos necesarios para hacer esa tarea; finalmente creo que se trata de otra aproximación a la capa de acceso a datos.

Hasta ahora prefiero el CSLA - Component-Based Scalable Logical Architecture ver (http://www.lhotka.net/, http://www.cslanet.org/portalcsla/index.php) esto si es un marco de trabajo con arquitectura en capas muy bien pensado, si se aprende a trabajar con ello (lleva tiempo) la vida es más facil.

wpablow Argentina

Sunday, January 31, 2010 8:35 PM

wpablow

Julio, gracias por tu ayuda, aún no me decido si iniciar mi nuevo proyecto con ORM (ActiveRecord, Linq, etc...) o usar una arquitectura con DataTables y StoredProcedures como venía usando....
Sin embargo he aprendido mucho en tu blog, prometo estudiar un poco CSLA como me lo sugerís.... Saludos.....

Pablo Albella

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading