Simplificando o uso de tipos nulos em entidades em projetos OO – Parte 02
Implementando um método SafeRead(Of T)
As classes e os métodos genéricos (generics) são utilizados em todos os lugares em .NET. Classes Generics são muito úteis. A qualquer momento que você encontrar um algoritmo genérico, que trabalhe com uma variedade de possibilidades, utilize a classe Generics.
Em meu trabalho diário, rotineiramente utilizo macros, snippets, CodeRush, e geradores de CodeDOM. Estabelecendo um padrão para completar uma tarefa, torna-se mais fácil de automatizá-la ou, pelo menos, fazê-la muito rapidamente. Por esta razão, desenvolvi pessoalmente o método SafeRead (De T).
SafeRead é implementado para aceitar um tipo de dados, algum tipo do container ADO.NET tal como um DataRow, um nome de campo e um valor padrão. Então, SafeRead tenta ler o campo, contanto que este não seja nulo. Se o campo tiver um valor nulo, o valor padrão será usado. Apesar de um código parecido com o descrito na Listagem 4 ser repetido várias vezes, o código verificador é convertido em um único método e a carga de trabalho é bastante reduzida. A Listagem 5 contém a implementação de SafeRead (Of T).
Listagem 4. Exemplo de código que verifica se o valor de uma variável é nulo.
Dim v As Object = reader("EmployeeID")
Dim employeeID As Integer
If (v Is Nothing Or v Is System.DBNull.Value) Then
employeeID = Convert.ToInt32(v)
Else
employeeID = -1 ' default value
End If
Listagem 5. Um método genérico denominado SafeRead que retira o trabalho bruto da leitura de valores de campos e da verificação por nulo.
Public Shared Function SafeRead(Of T)(ByVal fieldname As String, _
ByVal reader As SqlDataReader, 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
A primeira coisa a ser feita é ler o campo. No exemplo da Listagem 5, a partir de um leitor de dados em SQL (SqlDataReader). Você pode ainda estender o método SafeRead para aceitar um leitor do tipo IDataReader. Em seguida, você verifica se há valores nulos. Se o campo for alguma das variações do valor nulo, o valor padrão que você forneceu será retornado. Se as informações forem válidas, você utiliza Convert.ChangeType e o tipo T, passado como parâmetro, para converter a informação ao tipo de dados correto.
Vale a pena notar que o VB é menos rígido em relação aos tipos de dados, e assim você provavelmente pode escapar da necessidade de atribuir o tipo do Objeto de um SqlDataReader indexado para um tipo específico. Entretanto, é interessante notar que ser específico é uma maneira mais portátil e mais confiável de escrever o código. Para utilizar o SafeRead, chame-o com parâmetros que se assemelham ao código abaixo.
Dim employeeID As Integer = _
SafeRead(Of Integer)("EmployeeID", reader, -1)
Criando um atributo descritor de campos
Suponha que você queira eliminar mais alguns overheads para a construção de tipos de entidade. Por exemplo, você poderia utilizar meta-dados para fornecer o nome do campo, o tipo do campo e o valor padrão. Então, o campo de cada entidade pode carregar com ele a informação que necessita para usar o método SafeRead. A Listagem 6 tem uma execução de um atributo customizado, denominado FieldDescriptorAttribute (AtributoDescritorDeCampo).
Listagem 6. Um atributo customizado que pode ser utilizado para auxiliar com a leitura de campos de entidades
<AttributeUsage(AttributeTargets.Property)> _
Public Class FieldDescriptorAttribute
Inherits Attribute
''' <summary>
''' Initializes a new instance of the FieldDescriptorAttribute
''' class.
''' </summary>
''' <param name="fieldName"></param>
''' <param name="fieldType"></param>
Public Sub New(ByVal fieldName As String, _
ByVal fieldType As Type, ByVal defaultValue As Object)
FFieldName = fieldName
FFieldType = fieldType
If (FFieldType Is GetType(DateTime)) Then
FDefaultValue = DateTime.MinValue
Else
FDefaultValue = defaultValue
End If
End Sub
Private FFieldName As String
Public Property FieldName() As String
Get
Return FFieldName
End Get
Set(ByVal Value As String)
FFieldName = Value
End Set
End Property
Private FFieldType As Type
Public Property FieldType() As Type
Get
Return FFieldType
End Get
Set(ByVal Value As Type)
FFieldType = Value
End Set
End Property
Private FDefaultValue As Object
Public Property DefaultValue() As Object
Get
Return FDefaultValue
End Get
Set(ByVal Value As Object)
FDefaultValue = Value
End Set
End Property
End Class
A classe do atributo customizado FieldDescriptorAttribute é simplesmente um recipiente para os argumentos a serem usados no método SafeRead. O único aspecto a ser notado é que eu verifico para o tipo DateTime e configuro o valor padrão no atributo customizado. Definido o FieldDescriptorAttribute, você pode determinar uma classe de entidade customizada (baseada em qualquer tabela, neste caso, Employee foi usada) e alinhar os campos de propriedade/entidade com o atributo (veja a Listagem 7).
Listagem 7. A classe Employee que utiliza o FieldDescriptorAttribute, que pode ser utilizada por SafeRead com Reflection
Public Class Employee
Private FEmployeeID As Nullable(Of Integer)
<FieldDescriptor("EmployeeID", GetType(Integer), -1)> _
Public Property EmployeeID() As Nullable(Of Integer)
Get
Return FEmployeeID
End Get
Set(ByVal Value As Nullable(Of Integer))
If (Value.HasValue = False) Then
Console.WriteLine("Employee ID is null")
End If
FEmployeeID = Value
End Set
End Property
Private FLastName As String
<FieldDescriptor("LastName", GetType(String), "")> _
Public Property lastName() As String
Get
Return FLastName
End Get
Set(ByVal Value As String)
FLastName = Value
End Set
End Property
Private FFirstName As String
<FieldDescriptor("FirstName", GetType(String), "")> _
Public Property FirstName() As String
Get
Return FFirstName
End Get
Set(ByVal Value As String)
FFirstName = Value
End Set
End Property
Private FBirthDate As Nullable(Of DateTime)
<FieldDescriptor("BirthDate", GetType(DateTime), Nothing)> _
Public Property BirthDate() As Nullable(Of DateTime)
Get
Return FBirthDate
End Get
Set(ByVal Value As Nullable(Of DateTime))
FBirthDate = Value
End Set
End Property
Private FPhoto As Byte()
<FieldDescriptor("Photo", GetType(Byte()), New Byte() {0})> _
Public Property Photo() As Byte()
Get
Return FPhoto
End Get
Set(ByVal Value As Byte())
FPhoto = Value
End Set
End Property
Public Overrides Function ToString() As String
Return String.Format("Employee: {0} {1}, {2} is in {3}", _
FEmployeeID, FLastName, FFirstName, FRegion)
End Function
Private FRegion As String
<FieldDescriptor("Region", GetType(String), "(unk)")> _
Public Property Region() As String
Get
Return FRegion
End Get
Set(ByVal Value As String)
FRegion = Value
End Set
End Property
End Class