DUVIDA OOP CAMADAS

LUIS.HERRERA 20/03/2012 11:43:15
#397633
Bom dia.
Estou com uma grande dúvida conceitual / prática quando ao preenchimento de combobox nos formulários.

Estou trabalhando com 4 camadas: Modelos, Dados, Negócios e Interface Usuário.

Na classe Principal (DAL) para acesso ao banco, tenho todas as classes relacionadas a cada tabela / form. Porém em cada formulário (muitos), tenho de carregar em alguns forms, vários combobox com dados correspondentes a tabela ex: Funcionários, Departamentos; Cursos, etc...

Como eu devo fazer o preenchimento corretamente das combos, seguindo o conceito da OOP?

Dúvidas
1 - Ao carregar cada form, tenho de preencher vários combos, então teria de me conectar uma única vez ao banco, carregar todas as combos e fechar a conexão. Mas como fazer isso com classes genéricas?

2 - Ao carregar uma única combo, já foi um parto. Usei um exemplo com ArrayList para trabalhar com ID e Nome da (suposta tabela) na combo, agora terei de usar o banco mesmo, só que não consegui passar um combobox como argumento para a Classe a fim de preencher o combo. Será que só é possível fazer o preenchimento pelo formulário e não pela classe que está em outra camada?

ex:
Form Load

Chamar conexão
Carregar uma tabela
preencher Combo

Carregar outra tablea
preencher outra combo

etc....

Fechar conexão

Nota: Isso era o que eu fazia no VB6 com funções. Como seria com as classes em camadas?

KERPLUNK 20/03/2012 12:07:49
#397636
1 - Não sei o que você quer dizer com classes genéricas. Mas para ter uma única conexão com o banco valendo para todas as classes, teria que criar uma segunda classe(Singleton) que serviria como conexão, assim, você a usaria em todas as outras classes.
2 - Você não passa a combo como parâmetro. Instancie a classe com os dados que a combo vai ter e preencha.

A abertura do banco, não deve ser feita no form e sim na própria classe. Como já citei antes, pode ter uma classe que faz a abertura do banco de dados.

A classe, deve ser totalmente independente do form, ou seja, abrir a conexão no form e executar o método de classe que traz os dados, configura acoplamento.
AJSO 20/03/2012 14:34:11
#397675
Caro LUIS HERRERA

Não sei como você escreveu sua Classe de Negócio BLL mas carrego todos os objetos que contem ARRAY de Dados provenientes de Banco de dados como internamente por ENUM, LIST e/ou array mas faço um para cada Combo da Classe

private void CarregarTiposEstrutura()
{
cbxNONENONE.DisplayMember = [Ô]CampoDescricao[Ô];
cbxNONENONE.ValueMember = [Ô]CampoId[Ô];
cbxNONENONE.DataSource = ObterListaA(Proveniente de uma procedure do Banco de dados[ô]DAL[ô] );
}

//Estrutura para seleção

private void cbxNONENONE_SelectedValueChanged(object sender, EventArgs e)
{
int aux = 0;
if (cbxNONENONE.Items.Count > 0)
aux = Convert.ToInt32(cbxNONENONE.SelectedValue.ToString());
lblResutado.Text = [Ô]O Value selecionado é: [Ô] + aux.ToString();
}

---------------------------------------------
Isso fica na BLL da Classe que você esta em uso

private List<NONENONE> ObterListaA()
{

List<NONENONE> lista = new List<NONENONE>();

SqlCommand cmd = new SqlCommand(sql, conn);
//Nesse processo pode rodar sua estrutura de INPUT e/ou OUTPUT de dados da procedure do banco
// APenas simplifiquei para entendimento

conn.Open();
SqlDataReader leitor = cmd.ExecuteReader();

if (leitor.HasRows)
{
while (leitor.Read())
{
DadosC c = new DadosC();
c.Id = Convert.ToInt32(leitor[[Ô]Id[Ô]]);
c.Decricao = leitor[[Ô]NomeDesc[Ô]].ToString();
lista.Add(c);
}
}
conn.Close();
return lista;
}

Digamos que você tem 10 tipos de dados tratado por COMBOBOX ou DropDownList nessa Classe Seria apenas 10 diferentes para cada chamada
private List<NONENONE> ObterListaA() {}
private List<NONENONE> ObterListaB() {}
private List<NONENONE> ObterListaC() {}
private List<NONENONE> ObterListaD() {}
private List<NONENONE> ObterListaE() {}
private List<NONENONE> ObterListaF() {}
private List<NONENONE> ObterListaG() {}
private List<NONENONE> ObterListaY() {}
private List<NONENONE> ObterListaX() {}
private List<NONENONE> ObterListaZ() {}

Boa sorte.
LUIS.HERRERA 20/03/2012 16:33:48
#397704
KERPLUNK eu consegui. Segui o que disse e depois de algumas tentativas e pesquisas, cheguei a uma solução que funcionou assim:

//Camada Dados (DAL)
Ativo Classe AbreConexao();
Ativo Classe para Inserir, Alterar, Deletar e Consultar
Ativo Classe FechaConexão();

Alessandro, eu estou fazendo diversas tentativas aqui. Na realidade estou tentando carregar a lista ID e Nome do DB e popular a Combo. Estou tentando ser bem desacoplado, onde um form irá chamar a Classe popular Combo(nome da tabela) e retornará os dados da tabela para preencher a combo. Assim terei uma única Classe e por isso não pensei em ter uma estruturas auxiliares.

Hoje tirei o dia para isso, pois estou aprendendo C#. Se não conseguir, vou tentar sua opção, pois não queria te de fazer várias rotinas [Ô]Listas A, B, C etc..[Ô] como sugeriu, mas sim uma única classe que recebe o nome da tabela e devolve os dados corretos.

Isso era como fazia antes no VB6. Uma função para carregava o Recordset (SQL) e Outra função para Povoar a DBCombo com o recordset, assim era só passar o SQL para uma e o Recordset para outra com o nome da DBCombo que preenchia qualquer combo em qualquer form.

Depois coloco o código que conseguir aqui.
KERPLUNK 20/03/2012 16:39:35
#397705
Resposta escolhida
Se quiser, poste o código aqui e eu dou uma revisada mostrando o que pode melhorar...
LUIS.HERRERA 20/03/2012 17:25:14
#397717
KERPLUNK segue o código que tenho agora. Estou gostando do C#, mas o início tem horas que não dá para entender como coisas tão simples no VB6, ficou difícil no C# com OOP. Mas eu chego lá.

Classe DAL.cs (Acesso a Dados)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Loja.Modelos;
using System.Data;
using System.Data.SqlClient;

namespace Loja.DAL
{
public class ClientesDAL
{
public static SqlConnection cn = new SqlConnection();

public static void AbrirConexao()
{
try
{
cn.ConnectionString = Dados.StringDeConexao; // aqui pego a string de conexão da classe Dados que vou criar uma descriptografia de um arquivo TXT externo gravado pelo TI com a string
cn.Open();
}
catch (SqlException ex)
{
throw new Exception([Ô]Servidor SQL Erro: [Ô] + ex.Number);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}

public static void FecharConexao()
{
if (cn.State == ConnectionState.Open)
{
try
{
cn.Close();
cn.Dispose();
}
catch (SqlException ex)
{
throw new Exception([Ô]Servidor SQL Erro: [Ô] + ex.Number);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
}
}
}

Classe para CarregarCombobox.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Loja.DAL;

namespace Loja.DAL
{
public class ComboCarregaDados
{
public DataTable Listagem(string tabela) //recebe o nome da tabela a pesquisar dados
{
string sSQL = [Ô]SELECT ID, Nome FROM [Ô] + tabela + [Ô] ORDER BY Nome[Ô];

AbrirConexao();
DataTable dt = new DataTable();
SqlCommand cmd = new SqlCommand(sSQL, cn);

try
{
SqlDataReader dr = cmd.ExecuteReader(); //cria objeto só lê dados para frente e executa consulta
dt.Load(dr); //carrega dataTable com dados DataReader

return dt; //Aqui devolvo a DataTable para o formulário a fim de preencher o combobox
}
catch (SqlException ex)
{
throw new Exception([Ô]Servidor SQL Erro: [Ô] + ex.Number);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
FecharConexao();
}
}
}
}


Chamada do Formulário

Identifica item selecionado na Lista

private void cboCidades_SelectedIndexChanged(object sender, EventArgs e)
{
int itemID = 0;
if (int.TryParse(cboCidades.SelectedValue.ToString(), out itemID))
{
MessageBox.Show([Ô]Item ([Ô] + cboCidades.Text + [Ô]) ID ([Ô] + itemID + [Ô])[Ô], [Ô]Aviso do Sistema[Ô]);
}
}

Parte que Chama a Classe e preenche o combo

Nota: Essa parte ainda não fiz pois estou com alguma dificuldade nos comandos e lógica do C#. Não sei se o conceito e estrutura anterior está 100% correto.
Penso que aqui tenho de instanciar a classe ComboCarregaDados só não sei ainda como receber o DataTable da classe para preencher o combo.
KERPLUNK 20/03/2012 17:56:30
#397726
Bem, o que você deveria fazer é o seguinte:
1 - Crie uma classe com propriedades correspondentes à tabela que está querendo pesquisar. Por exemplo, se a tabela tem campos ID(int), Nome(varchar), crie uma classe que tenha as propriedades:
public int ID { get; set; }
public string Nome { get; set; }
E nessa classe, mais nada. Coloque o nome dela com o nome do que ela representa, por exemplo [Ô]Cidade[Ô]. Essa classe é chamada de entidade;

2 - Cria uma outra classe com um método que vai retornar uma lista dessa classe, mais ou menos assim:
public List<Cidade> BuscaTodas()
{
List<Cidade> _return = null;

string sSQL = [Ô]SELECT ID, Nome FROM [Ô] + tabela + [Ô] ORDER BY Nome[Ô];

AbrirConexao();
//DataTable dt = new DataTable();
SqlCommand cmd = new SqlCommand(sSQL, cn);

try
{
SqlDataReader dr = cmd.ExecuteReader(); //cria objeto só lê dados para frente e executa consulta
//dt.Load(dr); //carrega dataTable com dados DataReader
//Você não precisa do datatable

if (dr.HasRows)
{
_return = new List<Cidade>();
while (dr.Read())
{
Cidade cidade = new Cidade();
cidade.Id = dr.GetInt32(dr.GetOrdinal([Ô]ID[Ô]));// ID é o campo na tabela mesmo
cidade.Nome = dr.GetString(dr.GetOrdinal([Ô]Nome[Ô]));

_return.Add(cidade);
}
}
}
catch (SqlException ex)
{
throw new Exception([Ô]Servidor SQL Erro: [Ô] + ex.Number);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
FecharConexao();
}


return _return;
}

3 - Ao preencher a combo você vai fazer assim:
cboCidades.ValueMember = [Ô]ID[Ô];
cboCidades.DisplayMember = [Ô]Nome[Ô];
cboCidades.DataSource = new Loja.DAL.(Classe que tem o método BuscaTodas).BuscaTodas();

Tem vários probleminhas conceituais que você está tendo aí, mas com calma vamos resolvendo...
LUIS.HERRERA 20/03/2012 19:14:10
#397738
KERPLUNK fiz tudo que disse, mas está dando erro em um único ponto, justametne onde associo a Lista ao DataSource do combo. Fiz uns pequenos ajustes no código, pode ser isso que está errado, pois tento passar o nome da tabela, de modo a ficar genérico para todos os combos.

Veja o código final:

Classe Entidade

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Loja.Modelos
{
public class ComboCampos
{
private int _id;
public int ID
{
get{return _id;}
set{_id = value;}
}
private string _nome;
public string Nome
{
get{return _nome;}
set{_nome = value;}
}
}
}

Classe CarregaCombos

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using Loja.DAL;
using Loja.Modelos;
using System.Collections;

namespace Loja.DAL
{
public class ComboCarregaDados
{
public List<ComboCampos> BuscaTodas([txt-color=#e80000]string tabela[/txt-color]) // Aqui tento passar o parâmetro
{
List<ComboCampos> _return = null;

string sSQL = [Ô]SELECT ID, Nome FROM [Ô] + tabela + [Ô] ORDER BY Nome[Ô];

ClientesDAL.AbrirConexao();
SqlCommand cmd = new SqlCommand(sSQL, ClientesDAL.cn);

try
{
SqlDataReader dr = cmd.ExecuteReader(); //cria objeto só lê dados para frente e executa consulta
if (dr.HasRows)
{
_return = new List<ComboCampos>();
while (dr.Read())
{
ComboCampos tabelaCampos = new ComboCampos();
tabelaCampos.ID = dr.GetInt32(dr.GetOrdinal([Ô]ID[Ô]));// ID é o campo na tabela mesmo
tabelaCampos.Nome = dr.GetString(dr.GetOrdinal([Ô]Nome[Ô]));

_return.Add(tabelaCampos);
}
}
}
catch (SqlException ex)
{
throw new Exception([Ô]Servidor SQL Erro: [Ô] + ex.Number);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
ClientesDAL.FecharConexao();
}

return _return;
}
}
}

Rotina do Formulário

private void ClientesForm_Load(object sender, EventArgs e)
{
AtualizaGrid();
nomeTextBox.Focus();

cboCidades.DataSource = new Loja.DAL.ComboCarregaDados.[txt-color=#e80000]BuscaTodas[/txt-color]([Ô]Cidades[Ô]); //Erro está nesse método, mesmo eu tirando a string com nome da tabela fica o erro.
cboCidades.DisplayMember = [Ô]Nome[Ô]; //usar o campo da tabela
cboCidades.ValueMember = [Ô]ID[Ô]; //usar o campo da tabela
cboCidades.AutoCompleteMode = AutoCompleteMode.Suggest;
//cboCidades.Text = [Ô][Ô];

}


[txt-color=#e80000]Nota:[/txt-color]
Agradeço muito pela ajuda, estou apredendo banstante. é muita mudança junta para alguém de 46 anos: C#, OOP, SQL Server, ADO.NET etc.. etc...

Se puder indicar quais são os erros conceituais, fico grato também.

Boa noite.
KERPLUNK 20/03/2012 23:40:12
#397765
A idéia do método [Ô]BuscaTodas[Ô] é que ele busque somente as cidades, que a query dentro dele seja algo como [Ô]Select * from Cidade[Ô]...
Mas enfim, qual o erro que está dando?

O principal erro conceitual é o que chamamos [Ô]bala de prata[Ô]. As classes, devem ser especializadas, a classe de cidades, busca e trabalha apenas com cidades, a de clientes apenas com clientes.

Precisaria mesmo saber qual o erro que você está tendo para poder continuar...
LUIS.HERRERA 21/03/2012 07:43:01
#397770
KERPLUNK descobri o problema, estava faltando a criação de um objeto para tratar a lista recebida antes de passar ao DataSource do combo.

O erro era na linha:
cboCidades.DataSource = new Loja.DAL.ComboCarregaDados.BuscaTodas([Ô]Cidades[Ô]);

E a mensagem ao sublinhar o método: BuscaTodas() é:
[ô]Loja.DAL.ComboCarregaDados.BuscaTodas(string)[ô] is a [ô]method[ô] but is used like a [ô]type[ô]

O código correto é:
private void ClientesForm_Load(object sender, EventArgs e)
{
AtualizaGrid();
nomeTextBox.Focus();

[txt-color=#e80000] ComboCarregaDados objCbo = new ComboCarregaDados();[/txt-color]
[txt-color=#e80000]cboCidades.DataSource = objCbo.BuscaTodas([Ô]Cidades[Ô]);[/txt-color]
//cboCidades.DataSource = new ComboCarregaDados BuscaTodas();
// A linha comentada acima foi retirada pois gerava o erro.

cboCidades.DisplayMember = [Ô]Nome[Ô];
cboCidades.ValueMember = [Ô]ID[Ô];
cboCidades.AutoCompleteMode = AutoCompleteMode.Suggest;
//cboCidades.Text = [Ô][Ô];
}

Agora funcionou perfeitamente, inclusive usando a propriedade String tabela, e assim tendo uma classe genérica para carregar qualquer combo com listagens.

Entendi o que você disse sobre as classes serem especializadas, mas se programar é também a otimização de código e OOP é o reaproveitamento do mesmo evitando reescrita, então usar uma classe genérica nesse caso vejo como o mais correto e não criar 30 classes para carregar os compos pelos inúmeros furmulários de um software, só para ter cada uma com seu nome específico.

Tenho dezenas de combos no sistema, que são carregados com base na estrutura padrão de suas tabelas específicas para povoar Combos com a estrutura ID(Int), Nome(Varchar(50), Inativo(bool) ex: Departamentos; Funcionários; Cargos; Cursos; Categorias, etc..etc...

Dessa forma, eu consigo com uma única classe (otimização de código) carregar qualquer combo do meu sistema, pois todos usarm ID, NOME. Será que isso é um [Ô]Erro[Ô] pois escrever a mesma classe 30 vezes só para ter uma com o nome de cada tabela não me parece bem OOP.
KERPLUNK 21/03/2012 09:06:37
#397784
Citação:

Será que isso é um [Ô]Erro[Ô] pois escrever a mesma classe 30 vezes só para ter uma com o nome de cada tabela não me parece bem OOP.


Ao contrário, é EXATAMENTE isso que se trata a OOP. No caso atual, você está usando o seu pequeno Framework(suas classes DAL, conexão...) para o SEU aplicativo. Imagine que amanhã ou depois, você queira encapsular isso para colocar em um webservice para que um cliente possa integrar uma outra aplicação(de outra pessoa) com a sua. Nesse caso, as classes especializadas é que são a solução. Você não vai querer dar acesso à uma outra pessoa, à fazer seleção em QUALQUER tabela do seu sistema, vai? Pois é, é pra isso que servem as classes especializadas. Ok, você pode fazer um projeto específico para esse hipotético webservice, mas então, você não estaria reaproveitando o código. Acho válida a idéia de ter uma classe [Ô]genérica[Ô] para busca de dados simples(como combos, por exemplo), mas essa parte deveria estar separada do seu framework de aplicação(na minha opinião).

Agora que você entendeu bem o conceito, pesquise sobre o Entity Framework. Ele faz exatamente isso, pega o seu banco de dados(SQL Server somente) e cria todinhas as classes DAL, Entidades... tudinho mesmo. Com ele, você não precisa mais se preocupar com sintaxe de SQL, boas práticas em leitura e gravação de dados, nem nada disso, o Entity Framework cuida disso para você.
Página 1 de 2 [13 registro(s)]
Tópico encerrado , respostas não são mais permitidas