DAO – Data Access Object – Pattern – CRUD – Oracle – IBM DB2 – MSSQL Server – MFC – C++

Publicado: 15 de dezembro de 2014 em C/C++

No ciência da computação, Data Access Object é um padrão para um objeto que fornece uma interface abstrata para algum tipo de banco de dados ou outro mecanismo de persistência e que permite separar regras de negócio das regras de acesso a banco de dados. A vantagem de usar objetos de acesso a dados é a separação simples e rigorosa entre duas partes importantes de uma aplicação que não devem e não podem conhecer quase que nada uma da outra, e que podem evoluir frequentemente e independentemente.

O DAO implementa o mecanismo de acesso necessário para trabalhar com a fonte de dados. A fonte de dados pode ser um armazenamento persistente como um RDBMS, um serviço externo, como uma troca de B2B, um repositório como um banco de dados LDAP, um serviço de negócios acessado via CORBA, Internet Inter-ORB Protocol (IIOP) ou soquetes de baixo nível. O componente de negócio que se baseia no DAO usa a interface mais simples exposta pelo DAO para seus clientes.

Microsoft, C++ e DAO

O DAO Pattern J2EE não deve ser confundido com o DAO método de acesso a dados da Microsoft . O DAO (Microsoftfornece um quadro para a utilização de código para criar e manipular bancos de dados através de um conjunto hierárquico de objetos que usam o mecanismo de banco de dados Microsoft Jet para arquivos MDB, ou através de ODBC ou também banco de dados IASM como o antigo DBase, Paradox entre outros. O DAO Microsoft também é suportado em C++ através das classes de fundação da Microsoft (MFC).

Devido algumas similaridades do C++ com o Java é possível utilizar integralmente o DAO Pattern que é um padrão já consagrado por desenvolvedores Java para separar a camada de negocio da camada do banco de dados. O DAO é um célebre Pattern utilizado sozinho ou também em conjunto com outros frameworks, provavelmente alguns desenvolvedores C++ já o utilizaram de algum modo, implicitamente ou explicitamente, mesmo sem o conhecer em detalhes.

C++ - MFC - DAO

C++ – MFC – DAO

DAO Pattern

O DAO é um padrão flexível e não uma regra ele é utilizado em conjunto com Factory Pattern e Broker Pattern (veja Patterns relacionados no link abaixo) por isto é comum você encontrar algumas variações de sua implementação, o padrão possui quatro especificações para que possa ser utilizado corretamente elas são: DataAccessObject, DataSourceObject, BussinesObject e TransferObject.

  1. DataAccessObjecté o objeto principal desse padrão. O Data Access Object abstrai a implementação fundamental de acesso a dados para o Business Objects para permitir acesso transparente à fonte de dados.
  2. DataSourceObjectrepresenta uma implementação da fonte de dados. Uma fonte de dados pode ser uma base de dados tal como um RDBMS, repositório XML, e assim por diante.
  3. BussinesObject – o objeto de negócios representa o dados do cliente. É o objeto que requer acesso à fonte de dados para obter e armazenar dados.
  4. TransferObjectrepresenta um objeto de transferência usado como um suporte de dados. O Data Access Object pode usar um objeto de transferência para retornar dados para o cliente. O Data Access Object também pode receber os dados do cliente em um objeto de transferência para atualizar os dados na fonte de dados.

DAO Pattern: http://www.oracle.com/technetwork/java/dataaccessobject-138824.html

CRUD – DAO

CRUD acrónimo de Create, Read, Update e Delete, utiliza as quatro operações básicas  para criação, consulta, atualização e exclusão de dados.

Através do padrão DAO vamos utilizar as operações CRUD para manipular dados no banco de dados. O DAO é um Pattern J2EE, mas como ele separa as regras de negocio do acesso a dados também podemos utiliza-lo com qualquer tipo de interface, seja pela linha de comando, uma GUI desktop ou aplicações web.

Para usar o DAO você utiliza o Factory Pattern para criar uma abstração para o acesso a dados e uma classe concreta para seu tipo de acesso como por exemplo OracleDAOFactory, DB2DAOFactory e assim por diante. Então você pode criar a interface para seu objeto DAO chamada DAOFuncionario e a partir da interface você pode criar as classes DAO como, DAOFuncionarioOracle, DAOFuncionarioDB2 e utiliza o objeto de transferência para manipular dados.

Neste exemplo não criamos uma classe abstrata utilizando o Pattern Factory Method, cortamos caminho pois utilizamos apenas um objeto DAO para todos os bancos de dados, mas caso deseje criar uma abstração para classes concretas para cada banco de dados siga a especificação oficial do DAO Pattern no link logo acima.

Visual Studio - Design

Visual Studio – Design

Utilize a imagem acima para criar um design parecido.

Exemplo:

Neste exemplo usamos o Pattern DAO para criar operações CRUD utilizando uma interface gráfica, como este exemplo não possuí uma tela de login mude o acesso ao banco e usuário e senha diretamente na classe FuncionarioDAO.

O DAO  pode ser utilizado para varias soluções como no exemplo abaixo para enfatizar sua eficiência utilizamos o DAO em uma interface gráfica escrita com MFC justamente para mostrar a independência do negocio e acesso a dados.

Este é o terceiro método que disponibilizamos para manipular dados através de um banco de dados, para ver este mesmo programa utilizando SQL direto da aplicação clique aqui.

Pare ver este programa usando objetos no banco de dados como Views e Triggers clique aqui ou procure na busca também por Stored Procedures e cursores.

Ainda veremos outros métodos para manipular dados através de frameworks específicos.

SQL

Oracle

-- Cria tabela de funcionarios
create table Funcionarios(
  ID_Funcionario  NUMBER(5),
  Nome            VARCHAR2(30),
  Sobrenome       VARCHAR2(70),
  Cargo           VARCHAR2(30),
  Salario         NUMBER(9,2));

DB2

-- Cria tabela de funcionarios
create table Funcionarios (
    ID_Funcionario  INTEGER,
    Nome            VARCHAR(30),
    Sobrenome       VARCHAR(70),
    Cargo           VARCHAR(30),
    Salario         NUMERIC(9,2));

MSSQL

-- Cria tabela de funcionarios
create table Funcionarios (
   ID_Funcionario  Int,
   Nome            VARCHAR(30),
   Sobrenome       VARCHAR(70),
   Cargo           VARCHAR(30),
   Salario         Decimal(9,2));

C++

Objeto – Negócio

Header

#pragma once

class Funcionario
{

private:
	// Atributos
	long id;
	CString nome;
	CString sobrenome;
	CString cargo;
	double  salario;

public:

	// Construtor
	Funcionario();
	~Funcionario();

	// Construtor Overload
	Funcionario(long id, CString nome, CString sobrenome, CString cargo, double salario);

	// Metodos Getter e Setter
	void setId(long id);
	long getId();

	void setNome(CString nome);
	CString getNome();

	void setSobrenome(CString sobrenome);
	CString getSobrenome();

	void setCargo(CString cargo);
	CString getCargo();

	void setSalario(double salario);
	double getSalario();

};

Cpp

#include "stdafx.h"
#include "Funcionario.h"

// Implementação da classe Funcionarios

Funcionario::Funcionario()
{
}

Funcionario::~Funcionario()
{
}

// Construtor Overload
Funcionario::Funcionario(long id, CString nome, CString sobrenome, CString cargo, double salario)
{
	this->id = id;
	this->nome = nome;
	this->sobrenome = sobrenome;
	this->cargo = cargo;
	this->salario = salario;
}

// Getters e Setters
void Funcionario::setId(long id)
{
	this->id = id;
}

long Funcionario::getId()
{
	return this->id;
}

void Funcionario::setNome(CString nome)
{
	this->nome = nome;
}

CString Funcionario::getNome()
{
	return this->nome;
}

void Funcionario::setSobrenome(CString sobrenome)
{
	this->sobrenome = sobrenome;
}

CString Funcionario::getSobrenome()
{
	return this->sobrenome;
}

void Funcionario::setCargo(CString cargo)
{
	this->cargo = cargo;
}

CString Funcionario::getCargo()
{
	return this->cargo;
}

void Funcionario::setSalario(double salario)
{
	this->salario = salario;
}

double Funcionario::getSalario()
{
	return this->salario;
}

Factory Pattern – DAConexaoFactory

Header

#pragma once

// Inclui classe de banco de dados MFC
#include "afxdb.h"

class DAConexaoFactory
{
private:

	// Define constantes para fonte de dados
	const int ORACLE = 1;
	const int DB2 = 2;
	const int MSSQL = 3;

	// Define dados do usuario
	CString dns, usuario, senha;

	// Define banco de dados

public:

	DAConexaoFactory();
	~DAConexaoFactory();

	CString getConexao(int banco);
};

Cpp

#include "stdafx.h"
#include "DAConexaoFactory.h"

DAConexaoFactory::DAConexaoFactory()
{
}

DAConexaoFactory::~DAConexaoFactory()
{
}

CString DAConexaoFactory::getConexao(int banco)
{

	// Define banco de dados
	if (banco == ORACLE)
	{
		dns = L"OracleXE";
		usuario = L"user";
		senha = L"pass";
	}

	if (banco == DB2)
	{
		dns = L"IBMDB2";
		usuario = L"user";
		senha = L"pass";
	}

	if (banco == MSSQL)
	{
		dns = L"MSSQLSERVER";
		usuario = L"user";
		senha = L"pass";
	}

	// Cria string de conexão ODBC
	CString conexao;

	// Cria string de conexão
	conexao = L"DSN=" + dns + L";UID=" + usuario + L";PWD=" + senha;

	// Abre conexão

	return  conexao;
}

Objeto DAO – FuncionarioDAO

Header

#pragma once

#include "afxdb.h"
#include "IDaoFuncionario.h"

class FuncionarioDAO
{

private:
	// Define objetos
	CDatabase conn;
	CString erro;
	TCHAR menssagem[255];

public:	

	FuncionarioDAO();
	~FuncionarioDAO();

	// Metodos DAO

	Funcionario buscaFuncionario(CString id);

	bool insereFuncionario(Funcionario& funcionario);

	bool alterarFuncionario(Funcionario& funcionario);

	bool deletarFuncionario(Funcionario& funcionario);

	CString getErro();

};

Cpp

#include "stdafx.h"
#include "FuncionarioDAO.h"
#include "DAConexaoFactory.h"

FuncionarioDAO::FuncionarioDAO()
{
	DAConexaoFactory conexao = DAConexaoFactory();
	conn.OpenEx(conexao.getConexao(1), 0);

}

FuncionarioDAO::~FuncionarioDAO()
{
}

Funcionario FuncionarioDAO::buscaFuncionario(CString id)
{
	// Cria objeto de negocio
	Funcionario funcionario = Funcionario();
	CString m_conteudo;

	try
	{
		// Define Set de dados
		CRecordset dados(&conn);

		// Executa SQL
		dados.Open(CRecordset::forwardOnly,
			L"Select * From Funcionarios Where  ID_FUNCIONARIO = " + id);

		// carrega dados no objeto
		dados.GetFieldValue(L"ID_FUNCIONARIO", m_conteudo);
		funcionario.setId(_wtoi(m_conteudo));
		dados.GetFieldValue(L"NOME", m_conteudo);
		funcionario.setNome(m_conteudo);
		dados.GetFieldValue(L"SOBRENOME", m_conteudo);
		funcionario.setSobrenome(m_conteudo);
		dados.GetFieldValue(L"CARGO", m_conteudo);
		funcionario.setCargo(m_conteudo);
		dados.GetFieldValue(L"SALARIO", m_conteudo);
		m_conteudo.Replace(_T(","), _T("."));
		funcionario.setSalario(_wtof(m_conteudo));

		dados.Close();
	}
	catch (CDBException* ex )
	{
		// Define erro
		ex->GetErrorMessage(menssagem, 255);
	    erro += menssagem ;
	}

	// Retorna objeto
	return funcionario;
}

bool FuncionarioDAO::insereFuncionario(Funcionario& funcionario)
{
	// Define variaveis
	CString m_id;
	CString m_salario;

	// Formata valores
	long id = funcionario.getId();
	m_id.Format(_T("%ld"), id);

	double salario = funcionario.getSalario();
    m_salario.Format(_T("%g"), salario);

	// Cria instrução SQL
	CString sql = L"Insert into  Funcionarios  values ( " + m_id + ", " +
		"'" + funcionario.getNome() + "', " +
		"'" + funcionario.getSobrenome() + "', " +
		"'" + funcionario.getCargo() + "', " +
		m_salario + ")";

	try
	{
		// Executa SQL
		conn.ExecuteSQL(sql);
		return true;
	}
	catch (CDBException* ex)
	{
		// Define erro
		ex->GetErrorMessage(menssagem, 255);
		erro += menssagem;
		return false;
	}

}

bool FuncionarioDAO::alterarFuncionario(Funcionario& funcionario)
{
	// Define variaveis
	CString m_id;
	CString m_salario;

	// Define valores
	long id = funcionario.getId();
	m_id.Format(_T("%ld"), id);

	double salario = funcionario.getSalario();
	m_salario.Format(_T("%g"), salario);

	// Cria instrução SQL
	CString sql = L"Update  Funcionarios  set ID_FUNCIONARIO = " + m_id + ", " +
		" NOME= '" + funcionario.getNome() + "', " +
		" SOBRENOME= '" + funcionario.getSobrenome() + "', " +
		" CARGO = '" + funcionario.getCargo() +
		" ', SALARIO = " + 	m_salario +
		" WHERE ID_FUNCIONARIO = " + m_id ;

	try
	{
		// Executa SQL
		conn.ExecuteSQL(sql);
		return true;
	}
	catch (CDBException* ex)
	{
		// Define erro
		ex->GetErrorMessage(menssagem, 255);
		erro += menssagem;
		return false;
	}

}

bool FuncionarioDAO::deletarFuncionario(Funcionario& funcionario)
{
	// Define variaveis
	CString m_id;

	// Define valores
	long id = funcionario.getId();
	m_id.Format(_T("%ld"), id);

	// Cria instrução SQL
	CString sql = L"Delete from  Funcionarios WHERE ID_FUNCIONARIO = " + m_id;

	try
	{
		// Executa SQL
		conn.ExecuteSQL(sql);
		return true;
	}
	catch (CDBException* ex)
	{
		// Define Erro
		ex->GetErrorMessage(menssagem, 255);
		erro += menssagem;
		return false;
	}
}

CString FuncionarioDAO::getErro()
{
	// Retorna Erro
	return erro;
}

GUI – DADaoApp

Header


// CamposcppDlg.h : header file
//

#pragma once

#include "afxwin.h"

// CCamposcppDlg dialog
class CCamposcppDlg : public CDialogEx
{

public:
	CCamposcppDlg(CWnd* pParent = NULL);	// standard constructor

// Dialog Data
	enum { IDD = IDD_CAMPOSCPP_DIALOG };

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV support

// Implementation
protected:
	HICON m_hIcon;

	// Generated message map functions
	virtual BOOL OnInitDialog();
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()

public:

	// Variaveis Edits
	CEdit m_pesquisa;
	CEdit m_codigo;
	CEdit m_pnome;
	CEdit m_snome;
	CEdit m_cargo;
	CEdit m_salario;

	// Variaveis Botões
	CButton m_apagar;
	CButton m_novo;

	// Eventos
	afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
	afx_msg void OnBnClickedButton1();
	afx_msg void OnBnClickedButton2();
	afx_msg void OnBnClickedButton3();
	afx_msg void OnBnClickedButton4();
	afx_msg void OnBnClickedButton5();

private:

	// Campos
	CString codigo;
	CString pnome;
	CString snome;
	CString cargo;
	CString salario;

};

Cpp


// CamposcppDlg.cpp : implementation file
//

#include "stdafx.h"
#include "afxwin.h"
#include "Camposcpp.h"
#include "CamposcppDlg.h"
#include "afxdialogex.h"
#include "Funcionario.h"
#include "FuncionarioDAO.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CCamposcppDlg::CCamposcppDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CCamposcppDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CCamposcppDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_EDIT6, m_pesquisa);
	DDX_Control(pDX, IDC_EDIT1, m_codigo);
	DDX_Control(pDX, IDC_EDIT2, m_pnome);
	DDX_Control(pDX, IDC_EDIT3, m_snome);
	DDX_Control(pDX, IDC_EDIT4, m_cargo);
	DDX_Control(pDX, IDC_EDIT5, m_salario);
	DDX_Control(pDX, IDC_BUTTON5, m_apagar);
	DDX_Control(pDX, IDC_BUTTON2, m_novo);

}

BEGIN_MESSAGE_MAP(CCamposcppDlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON1, &CCamposcppDlg::OnBnClickedButton1)
	ON_BN_CLICKED(IDC_BUTTON2, &CCamposcppDlg::OnBnClickedButton2)

ON_WM_SHOWWINDOW()
ON_BN_CLICKED(IDC_BUTTON3, &CCamposcppDlg::OnBnClickedButton3)
ON_BN_CLICKED(IDC_BUTTON4, &CCamposcppDlg::OnBnClickedButton4)
ON_BN_CLICKED(IDC_BUTTON5, &CCamposcppDlg::OnBnClickedButton5)
ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

BOOL CCamposcppDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	SetIcon(m_hIcon, TRUE);
	SetIcon(m_hIcon, FALSE);
	return TRUE;
}

void CCamposcppDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); 

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

HCURSOR CCamposcppDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

// ************************         Desenvolvimento Aberto
// Nosso código começa aqui

void CCamposcppDlg::OnBnClickedButton1()
{
	// Referincia objeto statico
	CWnd * p_rotulo;

	// Associa objeto statico
	p_rotulo = GetDlgItem(IDC_Rotulo);
	p_rotulo->SetWindowTextW(L"DAO - Pattern");

	// Define variaveis
	CString codigo;

	// Cria objetos DAO
	Funcionario funcionario = Funcionario();
	FuncionarioDAO pesquisar = FuncionarioDAO();

	// Cria pesquisa
	m_pesquisa.GetWindowTextW(codigo);

	// Busca objeto
	funcionario = pesquisar.buscaFuncionario(codigo);

	// recupera dados do objeto e transfere para a GUI
	CString m_conteudo;
	long m_id = funcionario.getId();
	m_conteudo.Format(_T("%ld"), m_id);

	m_codigo.SetWindowTextW(m_conteudo);
	m_pnome.SetWindowTextW(funcionario.getNome());
	m_snome.SetWindowTextW(funcionario.getSobrenome());
	m_cargo.SetWindowTextW(funcionario.getCargo());

	double m_sal = funcionario.getSalario();
	m_conteudo.Format(_T("%g"), m_sal);
	m_salario.SetWindowTextW(m_conteudo);
}

void CCamposcppDlg::OnBnClickedButton2()
{
	// Limpa componentes
	m_codigo.SetWindowTextW(NULL);
	m_pnome.SetWindowTextW(NULL);
	m_snome.SetWindowTextW(NULL);
	m_cargo.SetWindowTextW(NULL);
	m_salario.SetWindowTextW(NULL);

	// Define foco
	CEdit* pesquisa;
	pesquisa = (CEdit*)GetDlgItem(IDC_EDIT1);
	GotoDlgCtrl(pesquisa);
}

void CCamposcppDlg::OnShowWindow(BOOL bShow, UINT nStatus)
{
	CDialogEx::OnShowWindow(bShow, nStatus);

	// Define foco inicial
	CEdit* pesquisa;
	pesquisa = (CEdit*)GetDlgItem(IDC_EDIT6);
	GotoDlgCtrl(pesquisa);
}

// Clique do botão inserir
void CCamposcppDlg::OnBnClickedButton3()
{
	// Recupara texto dos componentes
	m_codigo.GetWindowTextW(codigo);
	m_pnome.GetWindowTextW(pnome);
	m_snome.GetWindowTextW(snome);
	m_cargo.GetWindowTextW(cargo);
	m_salario.GetWindowTextW(salario);

	// Troca ponto decimal para o banco de dados
	int i = salario.Replace(L",", L".");

	// Cria objetos DAO
	Funcionario funcionario = Funcionario();
	FuncionarioDAO inserir = FuncionarioDAO();

	// Alimenta dados ao objeto
	funcionario.setId(_wtoi(codigo));
	funcionario.setNome(pnome);
	funcionario.setSobrenome(snome);
	funcionario.setCargo(cargo);
	funcionario.setSalario(_wtof(salario));

	// Verifica resultado
	bool resultado = inserir.insereFuncionario(funcionario);

	if (resultado)
	{
		AfxMessageBox(L"Dados inseridos com sucesso!");
	}
	else
	{
		AfxMessageBox(inserir.getErro());
	}	

}

// botão Updata
void CCamposcppDlg::OnBnClickedButton4()
{
	// Recupara texto dos componentes
	m_codigo.GetWindowTextW(codigo);
	m_pnome.GetWindowTextW(pnome);
	m_snome.GetWindowTextW(snome);
	m_cargo.GetWindowTextW(cargo);
	m_salario.GetWindowTextW(salario);

	// Troca ponto decimal para o banco de dados
	int i = salario.Replace(L",", L".");

	// Cria objetos DAO
	Funcionario funcionario = Funcionario();
	FuncionarioDAO alterar = FuncionarioDAO();

	// Alimenta dados no objeto
	funcionario.setId(_wtoi(codigo));
	funcionario.setNome(pnome);
	funcionario.setSobrenome(snome);
	funcionario.setCargo(cargo);
	funcionario.setSalario(_wtof(salario));

	// Altera objeto
	bool resultado = alterar.alterarFuncionario(funcionario);

	// Verifica resultado
	if (resultado)
	{
		AfxMessageBox(L"Dados alterados com sucesso!");
	}
	else
	{
		AfxMessageBox(alterar.getErro());
	}
}

void CCamposcppDlg::OnBnClickedButton5()
{
	// Recupera dados da GUI
	m_codigo.GetWindowTextW(codigo);
	m_pnome.GetWindowTextW(pnome);
	m_snome.GetWindowTextW(snome);
	m_cargo.GetWindowTextW(cargo);
	m_salario.GetWindowTextW(salario);

	// Troca ponto decimal para o banco de dados
	int i = salario.Replace(L",", L".");

	// Cria objetos DAO
	Funcionario funcionario = Funcionario();
	FuncionarioDAO apagar = FuncionarioDAO();

	// Alimenta dados ao objeto
	funcionario.setId(_wtoi(codigo));
	funcionario.setNome(pnome);
	funcionario.setSobrenome(snome);
	funcionario.setCargo(cargo);
	funcionario.setSalario(_wtof(salario));

	// Apaga objeto
	bool resultado = apagar.deletarFuncionario(funcionario);

	// Verifica resultado
	if (resultado)
	{
		AfxMessageBox(L"Dados apagados com sucesso!");
	}
	else
	{
		AfxMessageBox(apagar.getErro());
	}
}

Publicidade

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s