Construindo Aplicações C# – Uso de Classes e Métodos Genéricos
Classes e Métodos Genéricos permitem que não sejam especificados os tipos até que sejam declarados e instanciados pelo código. Os genéricos são usados com mais frequência com coleções e com os métodos que operam nelas e que podem ser usados para desenvolvimento de classes que pegam dados do banco de dados e jogam em classes que servem como Camada Modelo dentro de um modelo MVC.
Exemplo de Classe Genérica:
public class ListaGenerica<T>
{
public void Add(T input) { }
}
//Eu posso posteriormente instanciar e inicializar como
// Inteiro
ListaGenerica<int> Lista1 = new ListaGenerica<int>();
Lista1.Add(1);
// String
ListaGenerica<string> Lista2 = new ListaGenerica<string>();
Lista2.Add(“Teste”);
// Outra classe qualquer
ListaGenerica<QualquerClass> Lista3 = new ListaGenerica< QualquerClass >();
Lista3.Add(new QualquerClass);
Agora vamos aplicar esta funcionalidade num modelo pratico onde crio um método que pego um Select consulto no banco de dados, jogando num DataReader, deste DataReader iremos jogar nas classes que fazem papel de Model no modelo MVC.
Sem usar Genéricos para cada tabela você repetiria muitos dos métodos, tornando complicado e mais demorado para dar manutenção, maiores chances de erros, etc.
Se utilizando de Heranças, Generics, classes abstratas, podemos criar Métodos de busca de dados no banco que podem ser usadas para qualquer classe que faça papel de Model, variando praticamente somente a parte de pegar do datareader para a classe modelo, por obviamente cada tabela do banco e classe Model que a representa ter campos diferentes. Vamos considerar o cenário no banco de dados:
Usuarios | Produtos | |||||
Campo | Tipo | Tamanho | Campo | Tipo | Tamanho | |
Chave | INT | Chave | INT | |||
Usuario | Varchar | 50 | Codigo | Varchar | 30 | |
Senha | Varchar | 50 | Descricao | Varchar | 100 | |
Estoque | Float | |||||
Preco | Float |
Logo temos a representação dos modelos das duas tabelas acima da seguinte forma e vamos conforme post anterior ja se aproveitar do conceito de Herança onde herda de BaseModel que tem o atributo Chave que será comum em todas tabelas do banco de dados:
public class UsuarioModel : BaseModel
{
[DisplayName("Usuário do Sistema")]
public string Usuario { get; set; }
[DisplayName("Senha")]
public string Senha { get; set; }
}
public class ProdutoModel : BaseModel
{
[DisplayName("Código do Produto")]
public string Codigo{ get; set; }
[DisplayName("Descrição")]
public string Descricao { get; set; }
[DisplayName("Estoque Total")]
public double Estoque { get; set; }
[DisplayName("Preço em R$")]
public double Preco { get; set; }
}
Acima temos as duas classes que tem atributos diferentes tanto em quantidade de atributos como em tipos diferentes. Então vamos agora construir as classes de acesso a banco de dados de forma genérica e abstrata (classes que não são instanciáveis e servem de base para outros objetos) para que faça a coleta dos dados no banco de dados, jogue pra um dataReader e este jogue para a classe de usuarios e produtos.
Primeiro vamos criar uma classe que retire os dados de um DBDataReader e jogue em uma classe ConversorDataReader dentro de um array de string com as colunas que será usado posteriormente
public sealed class ConversorDataReader
{
#region Atributos
private readonly DbDataReader dbDataReader;
private readonly string[] colunas;
#endregion
#region Construtores
public ConversorDataReader (DbDataReader dbDataReader)
{
this.dbDataReader = dbDataReader ?? throw new ArgumentNullException(nameof(dbDataReader));
this.colunas = new string[dbDataReader.FieldCount];
for (int i = 0; i < dbDataReader.FieldCount; i++)
this.colunas[i] = dbDataReader.GetName(i);
}
#endregion
// Método para retornar uma string do dbDataReader
public string ConverterString(string nomeColuna)
{
return this.dbDataReader[nomeColuna].ToString();
}
// Método para retornar uma Inteiro dbDataReader
public int? ConverterInt32(string nomeColuna)
{
return Convert.ToInt32(this.dbDataReader[nomeColuna]);
}
}
Criaremos a classe AcessoBaseDados que recebe uma classe Generica T, que posteriormente iremos usar passando a classe Modelo desejada, neste caso usamos a classe System.Data.SqlCLient (SqlServer)
public abstract class AcessoBaseDados<T>
{
// classe abstrata que devera ser implementada quando instanciar classe herdada de AcessoBaseDados
protected abstract T CriaRegistro(ConversorDataReader conversorDataReader);
// classe que irá executar o comando sql passado por parametro e retornar uma classe que instanciar T
protected ObservableCollection<T> BuscarRegistros(string commandText)
{
// Coleção de T genericos que irá retornar ao fim do metodo
ObservableCollection<T> registros = new ObservableCollection<T>();
// configuração de conexão
SqlConnection conexao = new SqlConnection();
conexao.ConnectionString = <connectionString a ser usada>;
try
{
// criação do comando que será executado
SqlCommand comando= new SqlCommand
{
CommandText = commandText,
};
// abrindo conexão
commando.Connection = conexao ;
commando.Connection.Open();
// Criando dataReader com o resutlado do ExecuteReader
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
// para cada registro retorndo do bando e dentro do dataReader, será cridoa dum registro do tipo T e adicionado na lista de
registros
var registro = this.CriaRegistro(new ConversorDataReader (sqlDataReader));
registros.Add(registro);
}
sqlDataReader.Close();
}
}
catch (SqlException)
{
throw;
}
finally
{
if (commando!= null)
commando.Dispose();
if (conexao!= null)
conexao.Dispose();
}
// retornando a lista de registros
return registros;
}
}
Acima temos alguns pontos importantes que destacamos
Criamos a classe abstrata CriaRegistro que ela somente serve como Modelo para quando for instanciado esta classe esta sim terá para cada classe modelo sua implementacao diferente, para seus atributos diferentes. Ela retorna um tipo Genérico T que será as classes modelos
Criamos a classe BuscaRegistros que retorna uma ObservableCollection Genérica (que poderia ser qualquer tipo de lista desejada como List por exemplo e dentro dela fazemos a conexão com banco de dados e usando o parametro de entrada commandText que será o select a ser executado, irá gerar uma lista com os registros que resultarem deste select.. Veja que ele executa o select (executeReader) e pra cada registro passara no metodo CriaRegistro que retornará o registro no Tipo T Generico e ser adicionado na lista “registros” que é uma coleçao de objeto do Tipo T Genérico.
Desta forma, até o momento não nos importamos com os atributos que serão tratados dentro da classe, podendo ser usada para qualquer modelo então a partir de agora sempre que se conectar no banco pode ser usado este Método Genérico já trazendo os dados para a classe modelo desejada
Vamos agora implementar o uso destas , mas agora sim com as classes Model inicialmente criadas, onde tomaremos por exemplo a classe UsuarioViewModel que vai herdar de AcessoBasedados e onde estava o T Genérico agora terá a classe desejada
public class UsuarioViewModel : AcessoBaseDados<UsuarioModel>
{
// Construtor
public UsuarioViewModel ()
{
}
public void RetornaLista()
{
// defino o select a ser usado
string comando = "SELECT CHAVE, USUARIO, SENHA FROM USUARIOS"
// crio uma lista que recebera os dados do select
ObservableCollection<UsuarioModel> ListaUsuarios = new ObservableCollection<UsuarioModel>;
// faço chamada do Buscar Registros
ListaUsuarios = BuscarRegistros(comando);
}
// Override (sobrescrita) da classe abstrata CriaRegistro mas retornando agora no modelo desejado
protected override UsuarioModel CriaRegistro(ConversorDataReader conversorDataReader )
{
UsuarioModel registro = new UsuarioModel ();
registro.Chave = conversorDataReader.ConverterInt32("CHAVE");
registro.Usuario= conversorDataReader.ConverterString("USUARIO");
registro.Senha= conversorDataReader.ConverterString("SENHA");
return registro;
}
}
No código acima podemos verificar que retornar registros de usuarios (UsuarioModel) ou qualquer outra classe (poderia ser por exemplo a ProdutoModel é bem simples basta seguir os seguintes passos:
- Criar a classe herdando da AcessoBaseDados e passando que classe Model voce deseja usar;
- Criar um método qualquer onde voce criará uma Lista da Classe Model desejada (deve ser a mesma da criação da classe) ;
- Dentro deste método basta passar o comando sql para o metodo que vem herdado de AcessoBaseDados que ele irá executar e retornar no tipo de Dados (Classe Model) que voce criou o objeto;
- Fazer o Override da Classe CriaRegistro onde voce faz o de-para do datareader para a classe desejada;
Toda questão de conexão com banco de dados, (abrir , fechar conexão, montar o command, criar dataReader, ler cada registro do datareader) está pronto para ser usado para qualquer tipo de classe desejada e não precisa ser desenvolvido para cada tabela do banco de dados o que geraria muito trabalho para dar manutenção e mais código a ser escrito, ficando somente o que é essencialmente particular para cada tipo de dados para ser programado no local desejado.