Simplificando o uso de tipos nulos em entidades em projetos OO – Parte 03

Lendo entidades utilizando Reflection

Escrever códigos para ler entidades diretas é uma tarefa muito cansativa. Para apressar o desenvolvimento e aliviar o aborrecimento, você pode combinar as classes Generics, Reflection e atributos customizados, e escrever um construtor universal de entidade e de lista de entidade.

A Listagem 8 contém uma classe de acesso a dados responsável apenas por construir entidades e listas de entidades, mas ela poderá construirá qualquer entidade. Se você externar a string de conexão e utilizar as interfaces de System.Data ADO.NET, este mesmo código construirá entidades e lista de entidades para praticamente qualquer banco de dados e qualquer tabela.

 

Listagem 8. Uma classe DataAccess genérica que utiliza Generics, atributos customizados e Reflection para ler quase todas as entidades

Public Class DataAccess

   Private Const connectionString As String = _

      "Data Source=localhost;Initial Catalog=Northwind;" + _

      "Integrated Security=True"

 

   Public Shared Function GetList(Of T As New, _

      R As New)(ByVal tablename As String) As T

 

      Using connection As SqlConnection = _

         New SqlConnection(connectionString)

         Dim command As SqlCommand = _

            New SqlCommand("SELECT * FROM " + tablename, connection)

         connection.Open()

         Dim reader As SqlDataReader = command.ExecuteReader

         Return Create(Of T, R)(reader)

      End Using

 

   End Function

   Public Shared Function GetEmployeeList() As EmployeeList

      Return GetList(Of EmployeeList, Employee)("Employees")

   End Function

 

   Private Shared Function Create(Of T As New, U As New)( _

      ByVal reader As SqlDataReader) As T

 

      Dim list As IList(Of U)

      Dim gt As T = New T()

      list = gt

 

      While (reader.Read())

         list.Add(Create(Of U)(reader))

      End While

 

      Return list

   End Function

 

   Private Shared Function Create(Of U As New)( _

      ByVal reader As SqlDataReader) As U

 

      Dim o As U = New U()

 

      ' get the attributes and use them to read

      Dim type As Type = GetType(U)

      Dim properties() As PropertyInfo = type.GetProperties()

 

      ' for each field if it as a field descriptor we can read it

      For Each p As PropertyInfo In properties

 

         Dim attributes() As Object = _

            p.GetCustomAttributes(GetType(FieldDescriptorAttribute), _

               False)

 

         For Each attr As Object In attributes

 

            If (TypeOf attr Is FieldDescriptorAttribute) Then

               Dim descriptor As FieldDescriptorAttribute = _

                  CType(attr, FieldDescriptorAttribute)

 

               Dim method As MethodInfo = _

                  GetType(DataAccess).GetMethod("SafeRead")

               method = method.MakeGenericMethod(descriptor.FieldType)

               Dim val As Object = method.Invoke(Nothing, _

                  New Object() _

                  {descriptor.FieldName, reader, _

                   descriptor.DefaultValue})

               p.SetValue(o, val, Nothing)

               Exit For

 

            End If

         Next

      Next

 

      Return o

   End Function

 

   Public Shared Function SafeRead(Of T)(ByVal fieldname As String, _

      ByVal defaultValue As T) As T

 

      Dim v As Object = reader(fieldname)

      If (v Is Nothing Or v Is System.DBNull.Value) Then

         Return defaultValue

      Else

         Return Convert.ChangeType(v, GetType(T))

      End If

   End Function

 

End Class

 

A primeira função, GetList, requer dois argumentos como parâmetros, T e R, que executam um construtor em branco, Sub Novo. T é o tipo de lista da entidade, tal como EmployeeList; U é o tipo de entidade, tal como Empregado. A função compartilhada GetList requer o nome da tabela para que faça a leitura.

O segundo método compartilhado, Create (Criar), organiza a construção da lista, iterando sobre o leitor e construindo as entidades que formam a lista. O terceiro método faz todo o trabalho pesado: ele cria uma instância de uma classe da entidade indicada pelo parâmetro U. Em seguida, você obtém todas as propriedades para a entidade e solicita o FieldDescriptorAttribute para elas. Para cada propriedade você utiliza o FieldDescriptorAttribute e constrói uma instância do método genérico SafeRead, utilizando o conhecimento do FieldDescriptorAttribute sobre cada campo para inicializá-lo.

Alguém vai escrever e falar sobre o desempenho com todo este uso de Reflection. Correto, o desempenho pode ser ligeiramente mais lento, mas hardware é barato, já o trabalho, não. Se você achar que o desempenho não é aquele que necessitaria ser, você sempre pode executar o código que não usa Reflection, mas certifique-se que o desempenho realmente importa tanto e se o uso de Reflection é realmente mais lento. Para testar o código, você pode usar a sub rotina Main, mostrada na Listagem 9.

 

Listagem 9. Um aplicativo simples de console para testar o código

Imports System.Data

Imports System.Data.SqlClient

Imports System.Threading

Imports System.Reflection

 

Module Module1

 

   Sub Main()

 

      Dim emp As Employee = New Employee

      emp.BirthDate = Nothing

 

      Dim list As EmployeeList = DataAccess.GetEmployeeList

      For Each e As Employee In list

         Console.WriteLine(e)

      Next

 

      Console.ReadLine()

 

      For Each  c As Customer In DataAccess.GetList( _

         Of CustomerList, Customer)("Customers")

         Console.WriteLine(c)

      Next

 

      Console.ReadLine()

 

   End Sub

      ... ' elided

 

Herdando a partir de List(Of T)

Não é necessário criar uma CustomerList (ListagemDeClientes) que herde a partir de List(Of Customer) ou criar nenhum subtipo. Uma razão para fazer isto é apoiar a adição de funcionalidades extras à nova classe List. Na programação real, você necessitará de métodos auxiliares, e estes podem ser adicionados somente às classes que você criar (ver Nota 3).

 

Nota 3. Métodos de Extensão no Orcas

Com o Orcas (a próxima versão do .NET), métodos de extensão são suportados. Os métodos de extensão permitem adicionar potencialidades a um tipo existente. Para esta finalidade, você pode ser capaz de utilizar List(of Customer), por exemplo, sem herança e adicionar novas potencialidades utilizando métodos de extensão.


Conclusão

Há muitos trechos importantes neste artigo. Você pode usar tipos nulos para fazer campos e fazer com que as propriedades aceitem o valor nulo.

É importante reconhecer que muitas pessoas utilizam ADO.NET e DataSets. Esta abordagem pode funcionar, mas eu a utilizo raramente. Prefiro o controle fornecido por classes customizadas.