.NET - O acesso a dados usando um ORM - Entity Framework - I


Quando o foco principal é o acesso a dados a maneira como você se comunica com o banco de dados e a maneira como você representa os dados na sua aplicação pode determinar o sucesso ou o fracasso do seu projeto.

Nesse assunto temos muitas  opções, a começar com a utilização de objetos ADO .NET como: connections, adapters, readers e datasets. Essa abordagem é simples e fácil de ser entendida e permite que você tenha um desenvolvimento bem rápido.

Outra opção é utilizar classes ADO .NET para interagir com o banco de dados e então criar suas próprias classes (modelo de objetos) para representar os dados na sua aplicação. A curva inicial com tal modelo é maior se comparada com a primeira, mas a longo prazo esta abordagem garante uma alta manutenabilidade.

Outra opção a considerar é utilizar uma ferramenta ORM que esconde a complexidade de usar as classes ADO .NET e permite que você trabalhe somente com objetos em sua aplicação. Um ferramenta ORM inclui o melhor das duas opções citadas acima porque ela oferece uma produtividade imediata.

Mapeamento objeto-relacional (ou ORM, do inglês: Object-relational mapping) é uma técnica de desenvolvimento utilizada para reduzir a impedância da programação orientada aos objetos utilizando bancos de dados relacionais. As tabelas do banco de dados são representadas através de classes e os registros de cada tabela são representados como instâncias das classes correspondentes.

Considerando a plataforma .NET a Microsoft criou a ferramenta ORM conhecida como Entity Framework adotando-a como a melhor prática para o acesso a dados. Dessa forma vamos considerar apenas o Entity Framework que atualmente esta na versão 4.1 para utilização em projeto com acesso a dados.

Com isso em mente temos que compreender que acesso a dados usando o Entity Framework é de vital importância pois é partir de sua utilização que iremos nos apoiar para desenvolver nossas aplicações com acesso a dados.

Projetando uma aplicação com acesso a dados

Como exemplo vamos criar uma aplicação que trata os pedidos para o banco de dados Northwind onde temos dados sobre clientes e produtos armazenados nas tabelas: Orders, Customers, Order Details e Products. Abaixo uma figura mostrando essas tabelas no banco de dados:

Para começar precisamos criar o nosso modelo de objetos que são classes que tratam os dados e que são preenchidas com as informações a partir do banco de dados. Essas classes também precisam tratar os dados que atualizam o banco de dados.

Para o nosso exemplo vamos criar as classes Order, Customer, Order_Detail e Product que conterão as propriedades que representam os dados no banco de dados e que serão úteis para o negócio. Essas classes escondem a complexidade da estrutura do banco de dados a partir do código de negócio, também conhecido como camada de negócios ou Bussines Logic Layer (BLL), permitindo que o código se comunique somente com elas e com uma camada específica que será responsável por interagir com o banco de dados (Data Access Layer ou DAL).

A camada de lógica de negócios(BLL) não sabe nada sobre o banco de dados e interage apenas com as quatro classes. A DAL é responsável por se comunicar com o banco de dados. Com isso as classes que criamos se tornam-se a camada de o negócio de banco de dados da camada Lógica.

A BLL usa as classes no modelo e então persiste as modificações via
DAL.

O código da BLL não sabe se conectar com um banco de dados.

A separação do código dentro de camadas isoladas é uma técnica que garante um desenvolvimento mais rápido e, talvez mais importante, uma manutenção mais fácil. Até o momento, já dissemos que o modelo contém classes que por sua vez, contêm dados que são persistido no banco de dados.

Mas como você deve fazer um modelo ? Quais as técnicas que você precisa usar para construí-lo ?

A resposta é: depende.

O que é um modelo de objeto ?

Em muitas aplicações um modelo pode ser um simples conjunto de classes que contêm dados provenientes de um banco de dados e que têm pouco comportamento. Este tipo de modelo é conhecido como um modelo de objetos. Vejamos algumas das características das classes do modelo de objetos:

Conectando as classes

As classes no modelo de objetos não estão isoladas, elas estão conectadas entre si. Por exemplo: a classe Order esta conectada com a classe Customer e a classe Order_Detail esta conectada com a classe Product.

No banco de dados as tabelas são conectadas pelas colunas chave estrangeira. No modelo de objetos podemos referenciar diretamente outra classe usando a propriedade do tipo da classe referenciada. Por exemplo: a classe Order mantém uma referência a classe Customer usando a propriedade Customer (podemos usar o nome do cliente) cujo tipo é Customer.

Se um objeto precisa referenciar múltiplos objetos você pode criar uma propriedade enumeration do tipo List<T> onde T é o tipo de objeto na coleção. Por exemplo, um pedido precisa referenciar uma lista de detalhes (itens) e para fazer isso a classe Order contém a propriedade Order_Details, a qual é do tipo List<Order_Detail>. (Você não precisa necessariamente usar List<T>, pode outras classes do tipo coleção como Colletion<T>, HashSet<T> ou mesmo a classe não genérica ArrayList.)

Quando você completar o processo de desenhar os relacionamentos do modelo de objetos você deverá ter algo parecido com o que mostra a figura abaixo:

  1. A classe Order está ligada à classe Customer pela propriedade Customer.
  2. A Classe Order também contém detalhes através da propriedade Order_Details.
  3. Cada detalhe esta conectado ao produto através da propriedade Product.

O modelo de objetos atende uma grande parte dos cenários encontrados pelas aplicações mas existem casos em que você vai precisar de um maior nível de interação entre o modelo de objetos e o ambiente e para isso o modelo de objetos vai precisar conter comportamentos.

A evolução do modelo de objetos : O modelo de domínio

Um modelo de domínio é um modelo de objetos onde as classes têm mais comportamentos.O comportamento que é adicionado ao modelo de classes de domínio cria uma integração ampla e rica entre as classes e ao meio ambiente.

  • O modelo de domínio abrange o modelo de classes e repositórios
  • O modelo de domínio usa a camada de infra-estrutura para abstrair a
    interação física com o banco de dados

O modelo de domínio não envolve apenas as classes e seus dados ele introduz um novo design para sua aplicação. As classes são integradas a camada de negócios (BLL) e se comunicam com os módulos do repositório que são portas para o o banco de dados. Assim todas essas classes e módulos torna-se uma única camada: o modelo de domínio.

O modelo de domínio não se comunica diretamente com o banco de dados. Uma camada de infra-estrutura que oculta a comunicação de dados e que está por baixo do modelo e faz esse trabalho. Os repositórios lidam com a camada de infra-estrutura que são usadas para enviar os comandos para recuperar ou atualizar dados de um banco de dados.

Nota: Como as classes estão cientes dos repositórios, eles podem recuperar dados do banco de dados e assim oferecem uma nova gama de serviços para os desenvolvedores usá-los. O padrão de projeto modelo de domínio (Domain Model) contém muitas outras características , para mais detalhes consulte o livro de Eric Evans - Domain Driven Design.

Agora você já sabe como definir as camadas de um aplicativo para criar um design melhor e um código mais sustentável. Desenvolver em camadas é o fundamento para uma aplicação bem-sucedida e toda essa discussão leva em conta que você tem que lidar com um banco de dados e com objetos cujos dados devem ser persistentes.

Agora vamos descobrir como você pode fazer isso usando uma ferramenta ORM como o Entity Framework.

Usando um ORM para construir a camada de acesso a dados

Uma ORM é uma framework que lhe permite construir aplicações baseadas em um paradigma que é completamente diferente do que você usa quando você trabalha diretamente com ADO.NET. Usando um ORM você pode apenas trabalhar com objetos e ignorar o banco de dados.

Um ORM permite mapear suas classes e propriedades em tabelas e colunas no banco de dados. Você pode projetar suas classes e tabelas de forma independente e depois deixar o ORM fazer o trabalho sujo de usar as informações de mapeamento para gerar o código SQL para ler os dados a partir de um banco de dados, criar objetos com ela, e persistir os dados nos objetos no banco de dados.(O código SQL é executada através de ADO.NET, que permanece na camada mais baixa, por trás dos panos.)

Dito desta forma você pode ser levado a pensar que um ORM é uma ferramenta que apenas gera código mas ela faz muito mais do que isso.

Um ORM gerencia as alterações feitas nos objetos e garante a integridade de um determinado objeto suportando muitos ajustes de otimização. Além disso um ORM lida com as diferenças entre os banco de dados relacionais e o paradigma OOP.

O modelo dos bancos de dados relacionais e o paradigma OOP são diferentes em termos de granularidade dos dados, a forma como os relacionamentos são definidos, a herança, e os tipos de dados disponíveis.

Antes de dizer-lhe como usar um ORM, vamos examinar os problemas que ele resolve de modo que você possa entender melhor como uma ferramenta ORM pode tornar a sua vida mais fácil.

1- A incompatibilidade da granularidade dos dados

O número de classes nem sempre vai ser igual ao número de tabelas no banco de dados causando uma incompatibilidade conhecida como granularidade dos dados.

Geralmente o endereço residencial de um cliente consiste de rua, cidade, cep, pais, e, com frequência o endereço de entrega para pedidos possui a mesma informação. Quando você define a tabela no banco de dados você cria colunas com essas informações nas tabela Customers e Orders.

Quando você define o modelo de classes você geralmente cria uma classe AddressInfo que contém todas as propriedades relacionadas com o endereço e então você reutiliza esta classe nas classes Customer e Order. Ao final você acaba tendo 2 tabelas e 3 classes conforme a figura abaixo:

As classes Order e Customer possuem a propriedade Adress do tipo AddressInfo que contém a informação do endereço.

No banco de dados as colunas relacionadas com o endereço são repetidas nas tabelas Customers e Orders.

2- A incompatibilidade dos relacionamentos

Outra diferença entre os modelos relacionais e OOP está na forma como eles mantêm os relacionamentos. Pense sobre o relacionamento Order/Customer. Em um banco de dados, você cria uma coluna chave estrangeira CustomerId na tabela Pedidos (tabela filho) que aponta para a coluna chave primária CustomerId da tabela Customers (tabela pai).

No modelo de objetos, você pode referenciar o cliente(Customer) a partir do pedido(Order) usando uma propriedade do tipo Customer(Cliente), por isso não há necessidade de uma propriedade chave estrangeira.

Em um banco de dados as associações são unidirecionais da tabela filho para a tabela pai. No modelo de objetos, as associações podem ser tanto unidirecionais como bidirecionais. Você pode ter a propriedade Customer na classe Order, mas você também pode ter uma propriedade Orders na classe Customer.

Os Pedidos(Orders) estão relacionados aos seus Clientes(Customers)
através de uma chave estrangeira no banco de dados.No modelo OOP
utiliza-se um referência ao objeto.

No caso de um relacionamento muitos-para-muitos, as diferenças entre os bancos de dados relacionais e o paradigma OOP tende a crescer. Imagine o relacionamento entre as tabelas Employees e Territories no banco de dados Northwind. Eles não estão relacionados entre si através de chaves estrangeiras, mas através de uma terceira tabela: EmployeeTerritories.

No modelo de objetos, você deixa as classes referirem-se diretamente entre si, sem recorrer a uma classe intermediária.

3- A incompatibilidade da herança

Suponha que você tenha que tratar em uma aplicação com dois tipos de clientes : pessoa física e pessoa jurídica. Você poderia criar uma classe base para todos os clientes e a seguir criar uma classe para cliente pessoa física e outra classe para pessoa jurídica e depois fazer com que essas duas classes herdem da classe base.

Nos banco de dados relacionais o conceito de herança não existe. O que você pode fazer é criar um artefato para simular a herança. Você pode simular a herança no banco de dados utilizando um dos seguintes 3 métodos:

Independentemente da abordagem que você escolher, você está adaptando o modelo relacional para representar algo para o qual ele não foi projetado.

4- A incompatibilidade dos tipos de dados

Existe também o problema do tipo de dados. Em um banco de dados, você geralmente tem os tipos de dados varchar, int, datetime, e outros tipos de dados que pode ou não ter uma contrapartida na plataforma. NET.

Como exemplo veja o tipo de dados int do SQL Server. Você pode mapeá-lo para uma propriedade Int32 e ele vai funcionar perfeitamente. Agora, pense sobre o tipo de dados varchar, que pode ser facilmente mapeado para uma string. Mas, se a coluna tem um comprimento máximo, você não pode representar o tipo varchar usando o tipo string da plataforma .NET sem ter que escrever código para verificar o seu tamanho. A situação fica mais complicada para tipos de dados como TimeStamp que são representados como um array de bytes.

Ao escrever o seu código usando ADO .NET você tinha que tratar todas essas diferenças. Se você usar uma ferramenta ORM ela trata essas diferenças para você e você pode se concentrar apenas nos objetos que interagem com o banco de dados.

Vimos assim os problemas um ORM resolve e como isso pode tornar a sua vida mais fácil quando você tratar com aplicações com acesso a dados.

A seguir veremos como usar a ferramenta ORM Entity Framework. Acompanhe a segunda parte em: .NET - O acesso a dados usando um ORM - Entity Framework - II

Romanos 2:1 Portanto, és inescusável, ó homem, qualquer que sejas, quando julgas, porque te condenas a ti mesmo naquilo em que julgas a outro; pois tu que julgas, praticas o mesmo.

Referências:


José Carlos Macoratti