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 = &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 = new List<Domain.Customer>();

    while (dr.Read())

    {

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

        c.CompanyName = dr[&quot;companyname&quot;].ToString();

        c.CustomerId = Convert.ToInt32(dr[&quot;customerid&quot;]);

        c.EmailAddress = dr[&quot;emailaddress&quot;].ToString();

        c.FirstName = dr[&quot;firstname&quot;].ToString();

        c.LastName = dr[&quot;lastname&quot;].ToString();

        c.MiddleName = dr[&quot;middlename&quot;].ToString();

        c.ModifiedDate = Convert.ToDateTime(dr[&quot;modifieddate&quot;]);

        c.Phone = dr[&quot;phone&quot;].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() + &quot; &#8211; &quot; + customer.FirstName.ToString() + &quot; &quot; + 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[&quot;companyname&quot;].ToString();

        c.CustomerId = Convert.ToInt32(dr[&quot;customerid&quot;]);

        c.EmailAddress = dr[&quot;emailaddress&quot;].ToString();

        c.FirstName = dr[&quot;firstname&quot;].ToString();

        c.LastName = dr[&quot;lastname&quot;].ToString();

        c.MiddleName = dr[&quot;middlename&quot;].ToString();

        c.ModifiedDate = Convert.ToDateTime(dr[&quot;modifieddate&quot;]);

        c.Phone = dr[&quot;phone&quot;].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, conforme a listagem abaixo:

[code lang=”csharp”]

public IList<Domain.Customer> GetCustomers()

{

    IList<Domain.Customer> 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<Domain.Customer> 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

Compartilhar