ABC App – 03 Fazendo um Refactoring na DAL

No post anterior,  iniciamos a contrução da ABC App, construindo uma camada de Domínio e uma camada de DAL. Antes de continuarmos a desenvolver, vamos aplicar um Refactoring?

Eu não gosto de termos em inglês para coisas que podemos falar em português, mas no caso do Refactoring eu abro uma exceção, normalmente o que se fala é Refatoração, mas essa palavra para mim é um termo matemático. O correto, IMHO, seria dizer Re-fabricar, mas fico com o termo em inglês.

E o que podemos fazer?

Vamos seguir usando Baby Steps, daqui por diante pequenos passos, ou seja , faço Refactoring e testo, Refactoring e testo, então não corro o risco de fazer uma grande alteração e me perder no código alterado e o sistema não funcionar. A primeira coisa para alterar é a criação da conexão com o BD, como será recorrente em toda classe DAL, podemos criar um classe base que todas dessa camada herdem e,  assim,  centralizarmos o código. Então,  primeira coisa, crie uma classe BaseDAL, como abaixo:

[code lang=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Data;
using System.Data.Common;
using System.Resources;
using EntLib = Microsoft.Practices.EnterpriseLibrary.Data;

namespace ABCApp.DAL
{

    public abstract class BaseDAL
    {
        public EntLib.Database db { get; set; }

        public BaseDAL()
        {
            db = EntLib.DatabaseFactory.CreateDatabase("Connection String");
        }
    }
}
[/code]

Listagem 01

A classe BaseDAL não poderá ser instanciada, só poderá ser herdada. Com isso, ganhamos um único ponto de conexão com o Banco de Dados, que será muito útil quando quisermos controlar um transação, outro ponto é no reuso de código. Para usarmos essa classe vamos herdar ela na classe Customer da DAL. Não se esqueça de apagar a linha indicada, pois agora usaremos o objeto Database da classe BaseDAL. Lembram-se do código abaixo?

[code lang=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Domain = ABCApp.Domain;

using System.Data;
using System.Data.Common;
using System.Resources;
using Microsoft.Practices.EnterpriseLibrary.Data;

namespace ABCApp.DAL
{
    public class Customer : BaseDAL
    {
        public Domain.Customer GetCustomerById(int customerId)
        {
            //Apague a linha abaixo
//Database db = DatabaseFactory.CreateDatabase("Connection String");

            string sql = "select customerid, firstname, middlename, lastname, companyname, emailaddress, phone, modifieddate from saleslt.customer where customerid = @customerid";

            DbCommand cmd = db.GetSqlStringCommand(sql);

            db.AddInParameter(cmd, "customerid", DbType.Int32, customerId);

            IDataReader dr = db.ExecuteReader(cmd);

            Domain.Customer c = new Domain.Customer();

            while (dr.Read())
            {
                c.CompanyName = dr["companyname"].ToString();
                c.CustomerId = Convert.ToInt32(dr["customerid"]);
                c.EmailAddress = dr["emailaddress"].ToString();
                c.FirstName = dr["firstname"].ToString();
                c.LastName = dr["lastname"].ToString();
                c.MiddleName = dr["middlename"].ToString();
                c.ModifiedDate = Convert.ToDateTime(dr["modifieddate"]);
                c.Phone = dr["phone"].ToString();
            }

            if (!dr.IsClosed)
                dr.Close();

            return c;
        }
    }
}
[/code]

Listagem 02

Se já tivéssemos escrito mais classes DAL, o impacto dessa alteração seria muito maior. Então , seguindo nossso mantra de pequenos passos, vamos executar o código e saber se ele continua funcionando como o esperado.
Sendo o retorno igual ao que você tinha antes de modificar o código, então passamos neste Refactoring!
Vamos continuar implementando outros métodos na nossa classe DAL, o próximo interessante é retornar uma coleção de clientes. Podemos retornar todos os clientes e basicamente o método seria o mesmo do anterior mas sem o parâmetro de customerId e sem a linha de parâmetro no Command, vou criar então o método GetCustomers e que irá me retornar uma lista tipada de objetos, segue:

[code lang=”csharp”]
public IList<Domain.Customer> GetCustomers()
        {
            List<Domain.Customer> lstCustomers ;

            string sql = "select customerid, firstname, middlename, lastname, companyname, emailaddress, phone, modifieddate from saleslt.customer";

            DbCommand cmd = db.GetSqlStringCommand(sql);

            IDataReader dr = db.ExecuteReader(cmd);

            lstCustomers = new List<Domain.Customer>();

            while (dr.Read())
            {
                Domain.Customer c = new Domain.Customer();

                c.CompanyName = dr["companyname"].ToString();
                c.CustomerId = Convert.ToInt32(dr["customerid"]);
                c.EmailAddress = dr["emailaddress"].ToString();
                c.FirstName = dr["firstname"].ToString();
                c.LastName = dr["lastname"].ToString();
                c.MiddleName = dr["middlename"].ToString();
                c.ModifiedDate = Convert.ToDateTime(dr["modifieddate"]);
                c.Phone = dr["phone"].ToString();

                lstCustomers.Add(c);
            }

            if (!dr.IsClosed)
                dr.Close();

            return lstCustomers;
        }
[/code]

Listagem 03

E para testar o código acima vamos escrever as seguintes linhas no nosso método Main no Projeto Console, logo abaixo da última linha já existente do nosso teste anterior:

[code lang=”csharp”]
            IList<Domain.Customer> lstCustomer = dalCustomer.GetCustomers();

            foreach (Domain.Customer customer in lstCustomer)
            {
                System.Console.WriteLine(customer.CustomerId.ToString() + " – " + customer.FirstName.ToString() + " " + customer.LastName.ToString());
            }

            System.Console.ReadKey();
[/code]

Listagem 04

O código acima chama o método da DAL e retorna uma coleção de Clientes. Se aparecerem  várias centenas de Clientes,  está certo!
O código escrito até agora. Será que é possível aplicar um Refactoring para melhorar algo? Bom, tem algo bem evidente, uma duplicação de código, os dois métodos fazem a leitura do DataReader e populam um objeto Customer, e um deles ele adiciona a uma lista de objetos já que o retorno são vários. Podemos centralizar esse código para que quando for preciso adicionar uma nova Propriedade a classe Customer seja preciso alterar somente em um lugar, então vou criar o método AdaptToList que irá receber um DataReader e retornar uma lista tipada de Customer:

[code lang=”csharp”]
public IList<Domain.Customer> AdaptToList(IDataReader dr)
        {

        }
[/code]

Listagem 05

O código da listagem 05 está praticamente pronto. Mova o código que se encontra na listagem 03, da linha 11 até 27 para dentro do método AdaptToList, e adicione a última linha, conforme a listagem 06! E repare que na inicialização da coleção Customer a declaração estava em outra linha,  então acrescente o tipo antes.

[code lang=”csharp”]
        public IList<Domain.Customer> AdaptToList(IDataReader dr)
        {
            List<Domain.Customer> lstCustomers = new List<Domain.Customer>();

            while (dr.Read())
            {
                Domain.Customer c = new Domain.Customer();

                c.CompanyName = dr["companyname"].ToString();
                c.CustomerId = Convert.ToInt32(dr["customerid"]);
                c.EmailAddress = dr["emailaddress"].ToString();
                c.FirstName = dr["firstname"].ToString();
                c.LastName = dr["lastname"].ToString();
                c.MiddleName = dr["middlename"].ToString();
                c.ModifiedDate = Convert.ToDateTime(dr["modifieddate"]);
                c.Phone = dr["phone"].ToString();

                lstCustomers.Add(c);
            }

            return lstCustomers;
        }
[/code]

Listagem 06

Para funcionar só é preciso chamar o método AdaptToList no método GetCustomers,  também alterei para que a lstCustomer fosse baseada em uma IList<T>, conforme a listagem abaixo:

[code lang=”csharp”]
        public IList&lt;Domain.Customer&gt; GetCustomers()
        {
            IList&lt;Domain.Customer&gt; lstCustomers = null; 

            string sql = &quot;select customerid, firstname, middlename, lastname, companyname, emailaddress, phone, modifieddate from saleslt.customer&quot;;

            DbCommand cmd = db.GetSqlStringCommand(sql);

            IDataReader dr = db.ExecuteReader(cmd);

            lstCustomers = AdaptToList(dr);

            if (!dr.IsClosed)
                dr.Close();

            return lstCustomers;
        }
[/code]

Listagem 07

Mais um pequeno passo, e mais um teste! Se rodou e continuou funcionando é por que o seu Refactoring foi bem feito! O que falta? Bem, se AdaptToList foi contruído para economizar código devemos usá-lo também no método GetCustomerById, mas nesse último método ele retorna apenas um Customer e não uma lista. Podemos então usar um artifício: pegar somente o primeiro item da lista para retornar, conforme listagem abaixo:

[code lang=”csharp”]
public Domain.Customer GetCustomerById(int customerId)
        {
            IList&lt;Domain.Customer&gt; lstCustomers = null;

            string sql = &quot;select customerid, firstname, middlename, lastname, companyname, emailaddress, phone, modifieddate from saleslt.customer where customerid = @customerid&quot;;

            DbCommand cmd = db.GetSqlStringCommand(sql);

            db.AddInParameter(cmd, &quot;customerid&quot;, DbType.Int32, customerId);

            IDataReader dr = db.ExecuteReader(cmd);

            lstCustomers = AdaptToList(dr);

            if (!dr.IsClosed)
                dr.Close();

          �
return ((lstCustomers != null) || (lstCustomers[0] != null)) ? lstCustomers[0] : null;
        }
[/code]

Listagem 08

Na listagem 08 está o método GetCustomerById como deve ficar, a mudança do List<Domain.Customer> para IList<Domain.Customer>, a chamada do método AdaptToList e o pulo do gato que é o uso do operador ternário para retornar somente o primeiro item da coleção, ou seja um objeto Customer, ou um null, não vamos discutir agora se retornar nulo é uma boa opção ou não!

Vou finalizar o post por aqui, para não ficar muito grande, resumindo o que fizemos aqui: Refactoring!
Melhoramos o nosso código, parece que não é necessário fazer isso agora, mas sempre que possível é interessante a fazer, no caso criamos uma classe para cuidar inicialmente da conexão com o banco de dados e como criamos outro método que populava a classe Customer criamos um método para cuidar especificamente disso.

O código deste post encontra-se no Change Set 35524, dúvidas e sugestões por favor comentem!

Referências:

Wikipedia Refatoração

Wikipedia Refactoring

Wikipedia Code Refactoring

3 Replies to “ABC App – 03 Fazendo um Refactoring na DAL”

  1. Memory Leak a vista!

    Usar DataReader sem try-finally e suicidio. Se no meio da leitura do seu DR ocorrer uma exception vc vai ter uma connection hanging na sua app. Nao esqueca tambem de setar o CloseConnectionBehavior na hora de abrir o DR.

    try { //usa dr } finally { dr.Close() }

    1. Aê, Tucaz, valeu por ler e comentar!
      Sim, eu sei disso… Eu ia falar sobre Try-Catch-Finally em um próximo post! 🙂
      Estou deixando o código bem enxuto para não distrair e também para fazer a coisa incremental, assim posso ir explicando os conceitos em cima da aplicação ABC App!
      Mas continua dando os pitacos aê, por que eu sempre posso esquecer de algo!
      []’s

  2. Como você faria se tivesse que popular uma combo (ex: id e nome) com todos os customers?
    Utilizaria o método “GetCustomers” trazendo assim informações a mais ou criaria um novo método retornando apenas as informações necessárias…?

    Abraço.

Leave a Reply

Your email address will not be published. Required fields are marked *