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.