ASP .NET - Trabalhando com 2 camadas com C# - II


Na primeira parte deste artigo eu apresentei o modelo de desenvolvimento em duas camadas muito usado em aplicações cliente-servidor. Vou continuar a criar os demais métodos da camada de acesso a dados categoriaDAL.

Irei criar os métodos básicos que realizam as operações CRUD(Create, Read, Update , Delete)  conforme definidos a seguir:

Você não notou nada de diferente nas definições do métodos acima ?

Lembra do primeiro método que criamos no artigo anterior , DataTable GetCategorias() ?

Observe que ele retorna um objeto DataTable. Nada de mais , não é mesmo ?

Agora observe os demais métodos que definimos acima. Eles não retornam DataSets ou DataTables mas retornam objetos relacionados com o nosso domínio : categoria e coleção de categorias.

Sim,  resolvi mudar o foco do artigo, e, onde iria apenas criar os demais métodos CRUD, resolvi  mostrar um maneira mais eficiente e próxima da orientação a objetos na maneira como vamos tratar a nossa camada de acesso a dados.

Na verdade vemos muitos artigos publicados na web que usam a linguagem VB .NET ou C# e mostram como realizar as mais variadas tarefas usando a forma procedural de escrever código. No caso da criação da camada de acesso a dados isso é mais relevante pois temos a disposição recursos da linguagem orientada a objetos mas não vemos isso sendo aplicado.

Então a partir desse ponto eu vou definir um desenho para nossa camada de acesso a dados que vai usar os recursos da orientação a objetos com o objetivo de facilitar a reutilização de código e a manutenção.

Desenhando uma arquitetura em camadas com recursos da POO

Ao invés de escrever uma classe DAL diretamente vamos primeiro definir uma classe abstrata que irá definir uma interface publica da classe contendo os métodos para acesso e manutenção de dados. Essa será nossa classe base.

O código de acesso estará em uma classe concreta que irá herdar da nossa classe base e irá fornecer a implementação para os métodos abstratos.

Vamos definir também uma classe abstrata que irá conter os métodos de acesso a dados e uma classe que irá representa os objetos do nosso domínio no caso categorias.

Abaixo temos uma figura que representa a hierarquia da classes do nosso modelo:

A seguir iremos cria um diagrama UML para representar as classes. Antes vamos recordar alguns conceitos:

Na UML, atributos são mostrados com pelo menos seu nome, e podem também mostrar seu tipo, valor inicial e outras propriedades. Atributos podem também ser exibidos com sua visibilidade:

+ indica atributos públicos
# indica atributos protegidos
- indica atributos privados


Operações (métodos) também são exibidos com pelo menos seu nome, e podem também mostrar seus parâmetros e valores de retorno. Operações podem, como os Atributos, mostras sua visibilidade:

+ indica operações públicas
# indica operações protegidas
- indica operações privadas

Obs: A representação de uma classe abstrata em UML é quase igual à representação de uma classe concreta, a única diferença é o estilo da fonte do nome da classe, que, neste caso, está em itálico.

A seguir vemos o diagrama de classe exibindo os atributos e métodos e a associação entre as classes. A associação é do tipo Generalização que representa o conceito de herança.

- Nossa classe abstrata que contém os métodos para acesso a dados será chamada de : DataAccess.cs
-
A classe abstrata que contém os métodos para acesso e manutenção será chamada de : CategoriaProvider.cs
-
A classe concreta que irá implementar os métodos abstratos será chamada : CategoriaDAL.cs
-
A classe que irá representar nosso objeto de domínio será chamada de : CategoriaDetails.cs

Todos estes arquivos foram criados na pasta App_Code através do menu principal na opção WebSite -> Add New Item , selecionando o template Class e informando o nome correspondente e a linguagem C#.
 
Vemos ao lado na janela Solution Explorer a estrutura da solução e os arquivos criados na pasta App_Code.

Vejamos a seguir o código de cada uma delas:

1-) Código da classe abstrata DataAccess.cs que irá conter somente os métodos de acesso a dados. Essa classe é especializada em acesso a dados.

É uma classe abstrata e por isso não pode ser instanciada diretamente e seus métodos são todos protected o que indica que somente pode ser acessados pela classe e por classe que dela herdarem.

using System.Collections.Generic;
using System.Data.Common;

/// <summary>
/// descricao para DataAccess
/// </summary>
public abstract class DataAccess
{
     public DataAccess()
     {
     }
    
    private string _connectionString = "";
    protected string ConnectionString
    {
        get { return _connectionString; }
        set { _connectionString = value; }
    }

    protected int ExecuteNonQuery(DbCommand cmd)
    {
            foreach (DbParameter param in cmd.Parameters)
            {
                if (param.Direction == ParameterDirection.Output ||
                   param.Direction == ParameterDirection.ReturnValue)
                {
                    switch (param.DbType)
                    {
                        case DbType.AnsiString:
                        case DbType.AnsiStringFixedLength:
                        case DbType.String:
                        case DbType.StringFixedLength:
                        case DbType.Xml:
                            param.Value = "";
                            break;
                        case DbType.Boolean:
                            param.Value = false;
                            break;
                        case DbType.Byte:
                            param.Value = byte.MinValue;
                            break;
                        case DbType.Date:
                        case DbType.DateTime:
                            param.Value = DateTime.MinValue;
                            break;
                        case DbType.Currency:
                        case DbType.Decimal:
                            param.Value = decimal.MinValue;
                            break;
                        case DbType.Guid:
                            param.Value = Guid.Empty;
                            break;
                        case DbType.Double:
                        case DbType.Int16:
                        case DbType.Int32:
                        case DbType.Int64:
                            param.Value = 0;
                            break;
                        default:
                            param.Value = null;
                            break;
                    }
                }
            }
           return cmd.ExecuteNonQuery();
    }

    protected IDataReader ExecuteReader(DbCommand cmd)
    {
        return ExecuteReader(cmd, CommandBehavior.Default);
    }

    protected IDataReader ExecuteReader(DbCommand cmd, CommandBehavior behavior)
    {
        return cmd.ExecuteReader(behavior);
    }

    protected object ExecuteScalar(DbCommand cmd)
    {
        return cmd.ExecuteScalar();
    }
}

2-) A classe CategoriaProvider.cs  possui a assinatura dos seguintes métodos CRUD para acesso e manutenção dos dados:

    public abstract List<CategoriaDetails> GetCategorias();
    public abstract CategoriaDetails GetCategoriaByID(int categoriaID);
    public abstract bool DeletaCategoria(int categoriaID);
    public abstract bool UpdateCategoria(CategoriaDetails categoria);
    public abstract int InsertCategoria(CategoriaDetails categoria);

Também possui um método que retorna detalhes sobre uma categoria: GetCategoriaFromReader e outro que retorna uma  lista de categorias: GetCategoriaCollectionFromReader.

É uma classe abstrata e herda da classe DataAcess tendo desta forma acesso a todos os métodos desta classe.

using System.Xml.Linq;
using System.Collections.Generic;

  /// <summary>
  /// categoriaProvider
  /// </summary>
  public abstract class CategoriaProvider : DataAccess
  {
      public CategoriaProvider()
     {
          this.ConnectionString = ConfigurationManager.ConnectionStrings["MacorattiConnectionString"].ConnectionString);
     }

    public abstract List<CategoriaDetails> GetCategorias();
    public abstract CategoriaDetails GetCategoriaByID(int categoriaID);
    public abstract bool DeletaCategoria(int categoriaID);
    public abstract bool UpdateCategoria(CategoriaDetails categoria);
    public abstract int InsertCategoria(CategoriaDetails categoria);

    /// <summary>
    /// Retorna uma nova instancia de CategoriaDetails
    /// preenchida com os dados atuais do DataReader
    /// </summary>
    protected virtual CategoriaDetails GetCategoriaFromReader(IDataReader reader)
    {
        return new CategoriaDetails(
           (int)reader["categoriaid"],
           reader["nome"].ToString(),
           reader["descricao"].ToString(),
           reader["imagemurl"].ToString());
    }
    /// <summary>
    /// Retorna uma coleção de objetos CategoriaDetails
    /// com os dados lidos a partir do do DataReader
    /// </summary>
    protected virtual List<CategoriaDetails> GetCategoriaCollectionFromReader(IDataReader reader)
    {
        List<CategoriaDetails> categorias = new List<CategoriaDetails>();
        while (reader.Read())
            categorias.Add(GetCategoriaFromReader(reader));
        return categorias;
    }
}

3- O código da classe CategoriaDetails.cs abaixo representa o objeto Categorias e possui dois construtores e 4 propriedades que permitem o acesso aos dados do objeto.

// <summary>
/// Descrição de CategoriaDetails
/// </summary>
public class CategoriaDetails
{
      public CategoriaDetails()
      {}
      public CategoriaDetails(int id, string categoriaNome, string descricao, string imagemUrl)
      {
         this.categoriaid   = id;
         this.nome          = categoriaNome;
         this.descricao     = descricao;
         this.imagemurl     = imagemUrl;
      }

      private int _id = 0;
      public int categoriaid
      {
          get { return _id;}
         set { _id = value;}
      }
      private string  _categoriaNome = "";
      public string  nome
      {
          get { return _categoriaNome; }
          set { _categoriaNome = value; }
      }

      private string _descricao = "";
      public string descricao
      {
          get { return _descricao; }
          set { _descricao = value; }
      }

      private string _imagemUrl = "";
      public string imagemurl
      {
         get { return _imageUrl; }
         set { _imageUrl = value; }
      }
}

4-) A classe CategoriaDAL é nossa classe concreta que implementa os métodos e herda da classe CategoriaProvider. Por isso tem que implementar os métodos

    public abstract List<CategoriaDetails> GetCategorias();
    public abstract CategoriaDetails GetCategoriaByID(int categoriaID);
    public abstract bool DeletaCategoria(int categoriaID);
    public abstract bool UpdateCategoria(CategoriaDetails categoria);
    public abstract int InsertCategoria(CategoriaDetails categoria);

Definidos na classe CategoriaProvider.

using System.Xml.Linq;
using System.Data.SqlClient;

/// <summary>
/// classe para acesso a dados para a tabela Categorias
/// </summary>
public class categoriaDAL : CategoriaProvider
{
     public categoriaDAL()
     {}
    //este método foi criado na primeira parte do artigo e não será usada
    //pois foi substituida pelo método de mesma assinatura que retorna uma lista de categorias
    // o nome do método foi alterado para GetCategoriasDataTable()
    public static DataTable GetCategoriasDataTable()
    {
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MacorattiConnectionString"].ConnectionString))
        {
            try
            {
                conn.Open();

                SqlCommand cmd = new SqlCommand("SELECT * FROM Categorias", conn);
                DataTable tbl = new DataTable();
                tbl.Load(cmd.ExecuteReader(CommandBehavior.CloseConnection));
                return tbl;
            }
            catch (SqlException ex)
            {
                throw new Exception ("Erro ao acessar Categorias" + ex.Message);
            }
        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>
    /// Retorna uma coleção com todas as categorias
    /// </summary>
    public override List<CategoriaDetails> GetCategorias()
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("SELECT * FROM Categorias", cn);
            cmd.CommandType = CommandType.Text;
            cn.Open();
            return GetCategoriaCollectionFromReader(ExecuteReader(cmd));
        }
    }

    /// <summary>
    /// Retorna uma categoria existente com o ID especificado
    /// </summary>
    public override CategoriaDetails GetCategoriaByID(int categoryID)
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("tb_Categorias_GetCategoryByID", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@CategoryID", SqlDbType.Int).Value = categoriaid;
            cn.Open();
            IDataReader reader = ExecuteReader(cmd, CommandBehavior.SingleRow);
            if (reader.Read())
                return GetCategoriaFromReader(reader);
            else
                return null;
        }
    }

    /// <summary>
    /// Deleta uma categoria
    /// </summary>
    public override bool DeleteCategoria(int categoriaID)
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("tb_Categorias_DeleteCategory", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@CategoriaID", SqlDbType.Int).Value = categoriaID;
            cn.Open();
            int ret = ExecuteNonQuery(cmd);
            return (ret == 1);
        }
    }

    /// <summary>
    /// Atualiza uma categoria
    /// </summary>
    public override bool UpdateCategoria(CategoriaDetails categoria)
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("tb_Categorias_UpdateCategory", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.Add("@CategoriaID", SqlDbType.Int).Value = categoria.categoriaid;
            cmd.Parameters.Add("@Nome", SqlDbType.NVarChar).Value = categoria.nome;
            cmd.Parameters.Add("@ImagemUrl", SqlDbType.NVarChar).Value = categoria.imagemurl;
            cmd.Parameters.Add("@Descricao", SqlDbType.NVarChar).Value = categoria.descricao;
            cn.Open();
            int ret = ExecuteNonQuery(cmd);
            return (ret == 1);
        }
    }

    /// <summary>
    /// Cria um nova categoria
    /// </summary>
    public override int InsertCategoria(CategoriaDetails categoria)
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("tb_Categorias_InsertCategory", cn);
            cmd.CommandType = CommandType.StoredProcedure;
       //cmd.Parameters.Add("@CategoriaID", SqlDbType.Int).Value = categoria.categoriaid;
            cmd.Parameters.Add("@Nome", SqlDbType.NVarChar).Value = categoria.nome;
            cmd.Parameters.Add("@ImagemUrl", SqlDbType.NVarChar).Value = categoria.imagemurl;
            cmd.Parameters.Add("@Descricao", SqlDbType.NVarChar).Value = categoria.descricao;
            cmd.Parameters.Add("@CategoriaID", SqlDbType.Int).Direction = ParameterDirection.Output;
            cn.Open();
            int ret = ExecuteNonQuery(cmd);
            return (int)cmd.Parameters["@CategoriaID"].Value;
        }
    }
}

 

Note que os métodos implementados na classe concreta usam stored procedures com exceção do método GetCategorias() que utiliza um comando SQL em texto: SELECT * FROM Categorias;

  public override List<CategoriaDetails> GetCategorias()
    {
        using (SqlConnection cn = new SqlConnection(this.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("SELECT * FROM Categorias", cn);
            cmd.CommandType = CommandType.Text;
            cn.Open();
            return GetCategoriaCollectionFromReader(ExecuteReader(cmd));
        }
    }

Os métodos GetCategoriaCollectionFromReader() é herdado da classe CategoriaProvider e o método ExecuteReader() é herdado da classe DataAccess.

Para testar o nosso modelo vamos usar o método GetCategorias() para exibir as categorias cadastradas no GridView na página Default.aspx. Para isso vamos alterar o código do evento Load da página Default.aspx.cs conforme abaixo

    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            CategoriaDAL cat = new CategoriaDAL();
            gdvCategorias.DataSource = cat.GetCategorias();
            gdvCategorias.DataBind();
        }
        catch (Exception ex)
        {
            lblErro.Text = "Erro ao acessar dados : " + ex.Message;
        }
    }

Tivemos que criar uma instância da classe concreta CategoriaDAL visto que o método GetCategorias não é estático para em seguida usar o método GetCategorias() que retorna uma lista de objetos categorias.

Executando a página web iremos obter:

Com isso criamos uma camada de acesso a dados usando os recursos da orientação a objeto onde temos realmente a reutilização de código e a especialização das classes.

Eu sei é apenas ASP .NET, mas eu gosto...

Referências:


José Carlos Macoratti