keep-calm-im-c-programmer-btch

Trabalho com ASP.NET desde sua primeira versão, em 2003, ainda no modelo WebForms e arquitetura distribuído em camadas estruturado escrito com projetos de acesso à dados. E, claro, sempre achei que era uma boa ideia usar DataTable como forma de retornar minhas consultas garantindo um ambiente desconectado e “protegendo” meu banco de dados. Porém, mesmo depois de muitas e muitas vezes me pegar suspirando, irritado, estafado, na hora de popular ou ler linhas de uma DataTable, eu não enxergava que aquela não era a melhor maneira de atender a necessidade.

Por isso, neste post vou escrever várias características desse incrível objeto e, pelo fato dele ser tão completo, porquê devemos evita-lo o máximo possível, salvo quando ele for realmente aplicável.

A classe DataTable

Representa uma tabela de dados na Memória. – MSDN

Essa é a descrição da MSDN, simples pequena, mas esconde as verdadeiras característica dessa classe.

  • Propriedades
    • TableName: Nome da tabela (dã)
    • Case Sensitive: Diferencia a maiúsculas e minúsculas
    • HasErrors: Se houve erro em alguma das linhas
    • IsInitialized: Se o DataTable já está inicializado (possui um metadata tão complexo que vale a pena testar isso)
    • Locale: Informações de localidade (para mudar validação de datas, números decimais, moedas, etc)
    • MinimumCapacity: Tamanho mínimo em linhas que vai alocar em memoria ao inicializar. O padrão é 50 (vai vendo!)
    • Prefix: Prefixo de representação XML (mesmo que não use o XML)
  • Colunas (DataColumn)
    • AllowDBNull: Se permite valor nulo
    • AutoIncremente: Se uma coluna de auto incremento
    • ColumnName: Nome da coluna
    • DataType: Tipo de dados que será armazenado nas células dessa coluna
    • DefaultValue: Valor padrão a ser inserido na célula no momento de criação de uma linha
    • Expression: Para criar colunas com campos calculados
    • MaxLength: Tamanho máximo a ser inserido
    • ReadOnly: Se é um campo de somente leitura (isso é curioso, não? já explico)
    • Unique: Se deve ter apenas valores únicos/inéditos
  • Linhas (DataRow)
    • HasErros: Se a linha possui algum dado inválido
    • RowState: O estado atual da linha em relação à DataRowCollection (alerta! alerta!)
  • Relacionamentos
    • PrimaryKey: Especifica qual/quais colunas fazem parte da chave da tabela
    • ParentRelations:  Obtém coleção de relações de pai da tabela (chaves-estrangeiras)
    • ChildRelations: Obtém coleção de relações de filha da tabela (tabelas filhas)

UFA! Isso tudo sem contar outras propriedades e as dezenas de métodos e eventos que esse objeto dispara em consumir qualquer um desses recursos. Enquanto você for lendo a lista, tente imaginar a cascata de classes, coleções, dependências, recursos externos, linhas e mais linhas de c# que são executadas para realizar cada uma das tarefas.

  • Métodos
    • AcceptChanges: (Leia com atenção!) Confirma todas as alterações feitas nesta tabela desde a última vez que o método foi chamado (Agora pare para refletir, por que tem esse método, hum… )
    • BeginLoadData: Desativa notificações, manutenção de índices e restrições ao carregar dados (então, se fosse uma classe simples, não precisaria de um método para desativar recursos, certo?)
    • EndLoadData: Reativa tudo que o BeginLoadData desativou
    • CreateTableReader: Retornar um DbDataReader (um objeto conectado, pensa na complexidade!) baseado nos dados da tabelas, somente leitura e só encaminhamento (ou seja, só .Read())
    • Dispose: Desaloja da memória e marca a classe para ser coletada pelo CG (isso sempre é um sinal que esse objeto tem a necessidade de ser finalizado, sempre!)
    • GetChanges: Obtém uma cópia da DataTable que contém todas as alterações feitas desde que foi carregada, ou desde que o AcceptChanges foi chamado (pensa! com força!)
    • ReadXml: Instancia uma tabela inteira a partir de um arquivo físico de XML
    • WriteXml: Cria um arquivo XML com toda estrutura e dados da tabela
    • Select: Realiza consultas nos dados da tabela
  • Eventos (só vou listar, pois são intuitivos)
    • Initialized
    • ColumnChanging, ColumnChanged
    • RowChanging, RowChanged
    • RowDeleting, RowDeleted
    • TableClearing, TableCleared
    • TableNewRow

E aí, foi imaginando em tudo que eu disse? Alguns desses recursos que listei te chamaram atenção? Espero que sim, mesmo, pois agora vou destacar algumas características de uma DataTable que muitos não conhecem.

DataTable: Os Perigos Ocultos

O fato da classe System.Data.DataTable ser simples de usar não é motivo de sair usando a torto e direita, em qualquer situação onde seja necessário trafegar Lista de dados. Veja algums dos “segredos” dessa classe que, provavelmente, você não conhece, mas você aloca tudo na memória, consome processamento, sem saber.

DataTableRow controla transação, e possui em 5 estados

A DataTable é um classe poderosa, criada para simular um tabela de banco de dados real, mas em memória. Um DataSet (coleção de DataTables) simula todo o banco, com todos os recurso que puder imaginar, que sejam característica de um banco de dados relacional.

Lembram da propriedade DataTable.RowState? Ele retorna o atual estado de uma linha, para cada estado. São eles:

  • RowState.Added: Linha adicionada e AcceptChanges não foi chamado
  • RowState.Deleted: Linha excluída usando Delete()
  • RowState.Detached: Linha criada com NewRow(), mas não foi adicionada à tabela com DataTable.Rows.Add()
  • RowState.Modified: Linha modificada e AcceptChanges não foi chamado
  • RowState.Unchanged: A linha não foi alterada desde que AcceptChanges foi chamado pela ultima vez

Muitos estados para apenas uma linha, e para uma classe “simples” de tabela de dados. Controles de transação são custosos para o processamento. Mesmo que não utilize os métodos AcceptChanges, o controle transacional está lá, trabalhando, guardando valores, lockando dados, tudo como manda o figurino.

DataTable mantém até 4 versões dos dados

Esse é um dos mais impactantes motivos com o qual se deve tomar muito cuidado ao implementar uma DataTable. Assim como um banco de dados tradicional, onde é normal se fazer BeginTransaction, Commit, RollBack, a DataTable também trabalha com transações afim de proteger seus dados em memória. Assim você pode trabalhar os dados com segurança, ou então deixar sua aplicação mais lenta e custosa, se mal utilizada. As quatro versões que a classe DataTable mantém são:

  • DataRowVersion.Current: Valores atuais
  • DataRowVersion.Original: Valores originais, no ato da criação da linha
  • DataRowVersion.Proposed: Valores propostos, quando são alterados, mas não chamou AcceptChanges
  • DataRowVersion.Default: Valor padrão, retorna um dos valores acima, dependendo do estado da linha

Veja aqui a documentação do DataRowVersion.

E então, ainda pensando que a DataTable é um objeto simples, apenas para trafegar coleções de dados?

DataTable consome outras classes muito complexas

Continuando a massagear o ego da DataTable, é realmente um classe muito poderosa. Mas esse poder não de graça, ele realmente depende e consome recursos de outras classes para que a própria DataTable tenha seu valor aumentado. Algum exemplos de recursos que só são possível existir se consumirem outras classes com metadados extremamente complexos são:

  • Cria e interpreta XML: Através dos métodos ReadXml() e WriteXml() ela facilmente trabalha com esse tipo de arquivo de dados, mas para isso depende da namespace System.IO, e esse namespace sim aninham classes super complexas, que implementam desde acesso à discos até redes, memória, streaming, buffering, etc.
  • Manipula IDbDataReader: Mesmo que seja um reader que tenha origem um objeto desconectado, deve-se ter os mesmo cuidados que se deve ter com qualquer outro objeto de mantém um canal aberto, pois se não forem explicitamente encerrados, o GC não o coleta.
  • Localização: Essa implementa a System.Globalization.CultureInfo, para pode fazer saída de dados formatados, e validar entrada de dados de acordo com a localização do sistema. Validações essas que são requisitadas a cada inserção e consulta de dados.

E como esses recursos são alocado por injeção de dependência, sempre irá existir uma instancia de cada um, ainda não populados, mas os recursos já foram alocados, apenas aguardando seu uso.

Nossa! Mais alguma desvantagem? SIM!

Eu realmente estou convencido a te fazer para de usar a DataTable. Não permanentemente, mas para todos os caso no qual a DataTable não foi feita para. Esta ultima é minha “desvantagem predileta”, pois foi nela que comecei a refletir e a concluir que eu estava fazendo algo que definitivamente não era a melhor maneira.

DataTable não valida seu uso em tempo de desenvolvimento

E isso é péssimo! Digitar o trecho de código abaixo é extremamente cansativo, contra-produtivo, e o pior, não tem validação em tempo de desenvolvimento. Eu só vou descobrir que o dado que estou atribuindo a uma linha não é valida em tempo de execução. Ou então, só perceber que causei um tipo de erro de digitação também em tempo de execução.

var table = new DataTable("MinhaTabela");
table.Columns.Add(new DataColumn("Id", typeof(int)));
table.Columns.Add(new DataColumn("Nome", typeof(string)));
table.Columns.Add(new DataColumn(“DataNascimento”, typeof(DateTime)));

var row = table.NewRow();
row["Id"] = 1;
row["N0me"] = "Thiago Lunardi";
row["DataNascimento"] = new DateTime(1981, 5, 13);
table.Rows.Add(row);

Realmente não é agradável fazer esse trecho de código. E se você está criando um código e esta sentindo que deve ter alguma maneira melhor de fazer aquilo, é por existe sim. Então pare com que está fazendo e vá estudar para saber como faz da melhor maneira!

PS: Caso não tenha notado, o código acima vai dar exception. 🙁 Mas eu só iremos descobrir quando executarmos o programa.

E qual a solução? Listas!

De nada adiantaria escrever mais de 1.400 palavras para tentar te convencer a não usar mais a DataTable para este fim, e não dar uma segunda opção. Então lá vai: Listas!

Essas mesmas que derivam da namespace System.Collections.Generic.List, são elas que irão resolver sua necessidade. E trazem várias vantagens:

  • São realmente simples, leves e baixa complexidade
  • Aloca espaço em memória apenas do que realmente for usar
  • Não precisam ser marcada para coleta com Dispose()
  • É possível usar o Intellisence do Visual Studio para aumentar produtividade
  • Valida seu código em tempo de programação
  • Não inflam sua memória tentando tratar transações ou versionamento
  • Tornam o mundo um lugar mais feliz para o desenvolvedor 🙂

Veja abaixo, um código que faz a mesma função do código acima:

var list = new List<MinhaClasse>();
var item = new MinhaClasse { Id = 0, Nome = "Thiago Lunardi", DataNascimento = new DateTime(1981, 5, 13)};
list.Add(item);

E ainda é possível fazer em apenas duas linhas, vejam:

var list = new List<MinhaClasse>();
list.Add(new MinhaClasse { Id = 0, Nome = “Thiago Lunardi”, DataNascimento = new DateTime(1981, 5, 13)});

Hum, em uma linha só, que tal?

var list = new[] {new MinhaClasse { Id = 0, Nome = “Thiago Lunardi”, DataNascimento = new DateTime(1981, 5, 13)}}; // #ChupaDataTable

Claro que, para a lista funciona da maneira acima, é necessário primeiro criar a classe MinhaClasse. E é exatamente isso que ajuda agregar valor às listas, pois isso é quem irá ativar o Intellisence do Visual Studio e permitir as validações em tempo de programação.

public class MinhaClasse
{
    public int Id {get;set;}
    public string Nome {get;set;}
    public DateTime DataNascimento {get;set;}
}

E como provar tudo isso?

Abaixo um print de um script escrito com ScriptCS. Para detalhes, vejam esse artigo bacana do Alberto Monteiro.

ScriptCS - List vs DataTable

O método Tamanho() retorna o tamanho do objeto alocado em memória. Valores em amarelo.

Mas, para que server a DataTable então?

É claro que a DataTable possui o seu lugar ao Sol. E, agora que ressaltamos todas as características dela, é fácil enxergar seu propósito: Use DataTable, ou DataSet, quando a necessidade for de se ter uma modelagem de banco de dados em memória.

Um exemplo legal é se for necessário criar uma aplicação mobile, mas com função de trabalhar desconectado, mas depende de informações de um banco de dados real. Como no caso dos agentes que trabalham com SENSO. Eles carregam várias informações no seus aparelhos móveis, então persistem essa informações no aparelho em forma de arquivo, Xml talvez. Então a aplicação embarcada apenas lê esse XML e sobe um banco de dados em memória para a aplicação poder trabalhar. No fim do dia, o agente conecta seu aparelho em uma rede WiFi e transmite esses dados para o sistema principal.

Neste cenário sim, o uso do DataTable é bem aplicado. Mas usar um “tanque de guerra” como uma DataTable para transportar um “chiuaua” como o resultado de uma consulta, é puro desperdício de recurso.

Finalizações

Eu repeti essa frase várias vezes, e reafirmo: DataTable é uma classe muito poderosa, por isso deve ser usada com cautela. Mas sendo bem aplicada, te ajuda a criar uma aplicação de valor imensurável.

Lista é a opção mais simples, barata e recomendada para simples tráfego de dados entre suas consultas e sua aplicação.

E se depois de tudo isso eu ainda não ter convenci, bem… leia tudo de novo, que nas segunda pega! 😀

Referencias


4 Comments

Leave a Reply

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