A CLASSE DAL GENÉRICA
Agora iremos codificar a última classe do projeto, que é a mais importante. Esta classe fará o uso efetivos de tudo o que construimos até agora, ou seja, ela fará a leitura dos nossos atributos, utilizará as classes que geram e gerenciam as conexões com os Bancos de Dados etc.
Vale lembrar que, denovo o grande segredo aqui é o uso de reflection para ler os metadados de nossas classes Model que iremos contruir. Iremos ver mais uma vez como se le esses metadados em tempo de execução utilizando reflection.
Vamos começar a implementar nossa classe. Para iniciarmos é preciso fazer as seguintes referências:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Data;
using GenDAL.Library.DataBaseLib.DataBaseManager;
using GenDAL.Library.Interfaces;
using GenDAL.Library.Attributes;
namespace GenDAL.Library.DataBaseLib.GenericDAL
{
}
Os blocos “using” começam referenciando Namespaces nativos do .Net. A Namespace GenDAL.Library.DataBaseLib.DataBaseManager contém a classe que utilizaremos para controlar os objetos de conexão com o Banco de Dados, bem como as transactions e DataAdapters.
A Namespace GenDAL.Library.Attributes contém a nossa interface “IBaseModel”, a qual será utilizada para restringirmos o uso da classe DAL genérica, ou seja, a classe business que consumir a classe DAL, deverá utilizar um Type Parameter relativo à uma classe model, e essa classe model deverá herdar a interface IBaseModel.
A Namespace GenDAL.Library.DataBaseLib.DataBaseManager contém todos os atributos que criamos, os quais serão lidos em tempo de execução pela classe DAL genérica.
Com nossas referências prontas vamos declarar nossa classe:
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Data;
using GenDAL.Library.DataBaseLib.DataBaseManager;
using GenDAL.Library.Interfaces;
using GenDAL.Library.Attributes;
namespace GenDAL.Library.DataBaseLib.GenericDAL
{
public class GenericDAL where T : IBaseModel, new()
{
}
}
Vamos analisar a declaração de nossa classe.
Aqui
dizemos que ela é publica, e declaramos que todas as instâncias desta classe
deverá declarar um Type Parameter como está explícito no comando
Caso deixássemos a declaração da seguinte maneira -
public class GenericDAL
{
}
A classe que a consumisse poderia declarar qualquer tipo, complexo ou não como Type Parameter. Exemplo:
GenericDAL teste = new GenericDAL();
Mas
percebam que ao lado da declaração do TypeParameter utilizamos os comandos: where T : IBaseModel, new()
Esses comandos restrigem o tipo utilizado no TypeParameter. Restringimos dizendo que este tipo deverá ser uma implementação de IBaseModel e deverá possuir um construtor vazio.
Agora vamos construir uma classe especializada para gerenciarmos nossas exceções:
public class GenericDALException : Exception
{
public string ErrorMessage
{
get
{
return base.Message;
}
}
public GenericDALException(string errorMessage) : base(errorMessage)
{
}
public GenericDALException(string errorMessage, Exception innerException)
: base(errorMessage, innerException)
{
}
}
Aqui
temos uma classe que herda de System.Exception, utilizando algumas de suas
propriedades e construtores.
Continuando,
nossa classe terá apenas um campo privado do tipo DBManager. Este campo irá controlar os
objetos de conexão, dataadapters e transactions e não será acessível através
propriedades get/set comuns. Teremos duas propriedades que servirão apenas para
inicilizar o campo e forçar a utilização de transactions em todo tipo de acesso a dados.
#region Fields
private DBManager _dbManager;
#endregion
#region TransactionScope
protected DBManager TransactionScope()
{
DBManager man = new DBManager();
this._dbManager = man;
return man;
}
protected DBManager TransactionScope(string currentConn)
{
DBManager man = new DBManager(currentConn);
this._dbManager = man;
return man;
}
#endregion
Utilizamos aqui o conceito que todo acesso a dados deverá estar dentro de um escopo de transação. Em outras palavras, a classe que consumir nossa classe genérica, para utilizar os métodos de acesso a dados deverá antes declarar o seguinte bloco de comando para operações no banco de dados principal da aplicação:
using (base.TransactionScope())
{
//Operaçao de Acesso a dados 1
//Operaçao de Acesso a dados 2
//Operaçao de Acesso a dados 3
}
Ou se for utilizar uma outra conexão que não seja a conexão principal da aplicação:
using (base.TransactionScope("conexão"))
{
//Operaçao de Acesso a dados 1
//Operaçao de Acesso a dados 2
//Operaçao de Acesso a dados 3
}
Agora vamos codificar os métodos da classe. Vamos começar pelos métodos privados:
#region Private Auxs Methods
private string GetFieldName(PropertyInfo piInfo)
{
string retorno = "";
foreach (object attributo in piInfo.GetCustomAttributes(false))
{
if (attributo is AttFieldDB)
{
retorno = ((AttFieldDB)attributo).FieldName;
break;
}
}
return retorno;
}
private void SetPropertyValue(DataRow row, PropertyInfo piInfo, T obj)
{
foreach (DataColumn col in row.Table.Columns)
{
if (col.ColumnName.ToLower().Equals(this.GetFieldName(piInfo).ToLower()))
{
if (piInfo.PropertyType.Module.ScopeName.ToLower().Equals("commonlanguageruntimelibrary"))
{
if (row[col] != DBNull.Value)
{
piInfo.SetValue(obj, Convert.ChangeType(row[col], piInfo.PropertyType), null);
break;
}
}
}
}
}
private List DataSetToList(DataSet ds)
{
T obj = new T();
List retorno = null;
try
{
if (ds != null && ds.Tables[0].Rows.Count > 0)
{
retorno = new List();
foreach (DataRow dr in ds.Tables[0].Rows)
{
obj = new T();
foreach (PropertyInfo piAux in obj.GetType().GetProperties())
{
SetPropertyValue(dr, piAux, obj);
}
retorno.Add(obj);
}
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
obj = default(T);
}
return retorno;
}
#endregion
O método “GetFieldName” recebe um PropertyInfo, que nos dá acesso aos metadados de uma propriedade da classe refletida, percorre os atributos customizados para esta propriedade em busca de um atributo do tipo AttFieldDB. Uma vez que foi detectado o atributo, o método retorna o valor de FieldName que é uma propriedade do atributo AttFieldDB.
O método “SetPropertyValue” Faz o parse do
valor contido no resultset para o objeto refletido por piInfo. Este método
recebe um DataRow, o qual será uma representação de uma linha de um DataSet. O
método percorre todas as colunas do DataRow e compara se o nome da coluna é
igual à propriedade 'FieldName' do atributo 'AttFieldDB' da propriedade
corrente. Este atributo deverá estar sendo usado na classe model para mapear
suas propriedades com os campos de uma tabela do BD. Uma vez que o mapeamento é
detectado, o método verifica se o tipo da propriedade refletida é um tipo
nativo do .Net, para garantirmos que no caso da propriedade refletida seja de
um tipo complexo - como uma classe, o
parse não seja feito. Com essa consistência o método pega o valor contido na
coluna do DataRow e joga na propriedade refletida.
O método “DataSetToList” é utilizado dentro dos métodos que realizam selects no banco. Ele cria uma lista genérica de acordo com o dataset. Para isso é necessário qua a classe model esteja devidamenten mapeada com a tabela do BD, utilizando o atributo AttFieldDB.
Vamos ao próximo método:
#region Call IBaseModel Method
private void ValidateUsageCustomAttributes()
{
T obj = default(T);
string methodName = "ValidateUsageCustomAttr";
try
{
obj = new T();
MethodInfo minfo = obj.GetType().GetMethod(methodName);
if (minfo != null)
{
minfo.Invoke(obj, null);
}
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
Este
método valida o uso dos atributos customizados que criamos no projeto
'Attributes'. Ele utiliza reflection para invocar dinamicamente o método
'ValidateUsageCustomAttr', que está contido na classe 'BaseModelClass' do
projeto 'BaseClasses'. Esta classe herda da interface 'IBaseModel'e a
implementa. Todas as classes contidas na camada 'Model'deverão herdar desta
classe, pois desta maneira todas as classes terão o método
'ValidateUsageCustomAttr', para podermos chamar via reflection na nossa classe
DAL.
Aqui utilizamos o método GetType().GetMethod(string) que retorna um objeto MethodInfo. Com o MethodInfo nós podemos ter acesso aos metadados de um método. Podemos também invocar este método, ou seja, executar o método sem ter que instanciar um objeto.
Terminamos essa parte por aqui, na próxima parte do artigo, daremos continuidade.