Nessa serie de textos irei mostrar como criar uma aplicação simples de cadastro de Contato, essa aplicação inicialmente não será nada mais do que uma simples aplicação CRUD, não contendo nenhuma regra de negocio complexa.

Usarei essa aplicação para dar uma breve introdução sobre diversos assuntos e frameworks, nesse primeiro texto irei apresentar a estrutura da aplicação e construiremos um cadastro inicial apenas com o nome do Contato, mais tarde iremos incorporar outras funcionalidade para que essa mesma aplicação sirva de base para os demais textos.

Montando o ambiente

Abra o Visual Studio e crie uma Solution vazia, dentro dessa solution crie um projeto do tipo Windows Form, com o nome de ClienteWindows, depois disso crie outros dois projetos do tipo Class Library, um com o nome de Data e o outro com o nome de Dominio.

Feito isso você terá uma estrutura parecida com a figura mostrada abaixo.


Agora crie uma estrutura de pasta de modo que fique igual à figura abaixo.


Com a casa quase em ordem, vamos fazer os downloads do NHibernate, StructureMap e do Fluent NHibernate (Todos os links se encontram na seção de links no final do texto)

Borá coda um tiquinho.

É chegada à hora de codificar um pouquinho, sendo assim iremos implementar a nossa classe contato. A classe contato é uma classe simples que contem apenas duas propriedades como pode ser visto na listagem abaixo.

public class Contato
{
        private int _id;
        public virtual int Id { get { return _id; } }
        public virtual string Nome { get; set; }
}

Com classe Contato já esta definida, iremos criar a interface do repositório, ela será o nosso contrato padrão para o acesso a base de dados, sendo assim especificará todas as operações pertinentes ao domínio que diz respeito ao acesso ao banco. Como o nosso domínio apenas diz respeito a um cadastro simples não contem nenhuma novidade, como podemos verificar na listagem abaixo.

public interface IContatoRepositorio
{
        void Salvar(Contato contato);
        void Excluir(Contato contato);
        Contato ObterPor(int id);
        IEnumerable ObterTodos();
}

Criadas a classe Contato e definida a interface IContatoRepositorio, encerramos aqui camada de Domínio.

Acesso a dados

A camada de acesso a dados se encontra no projeto Data da nossa solução, antes de dar inicio a implementação dessa camada teremos que fazer algumas referencias aos projetos que fizemos download e ao Projeto Domino da nossa solução.

Adicione ao projeto as seguintes referencias:

  • FluentNHibernate
  • NHibernate
  • Remotion.Data.Linq
  • StructureMap

ContatoRepositorio

A classe ContatoRepositorio é a implementação da interface IContatoRepositorio que criamos na camada de domínio.

No construtor da classe passamos uma ISession fazemos isso para viabilizar a injeção de dependência.

public class ContatoRepositorio : IContatoRepositorio
{
        private readonly ISession _session;
 
        public ContatoRepositorio(ISession session)
        {
            _session = session;
        }
...

O método Salvar() recebe um contato como parâmetro e na sua implementação criamos uma transação como método BeginTransaction() do objeto ISession, a transação esta encapsulada por uma diretiva using no escopo da diretiva usamos novamente o objeto ISession agora para persistir o objeto no banco de dados através do método SaveOrUpdate() e por fim fazemos uma chamada ao método Commit() para efetivar o processo de persistência.

        public void Salvar(Contato contato)
        {
            using (var tran = _session.BeginTransaction())
            {
                _session.SaveOrUpdate(contato);
                tran.Commit();
            }
        }
...

O método Excluir() segue a mesma dinâmica do Salvar() com a exceção que ele faz uma chamada ao método Delete() do objeto ISession.

        public void Excluir(Contato contato)
        {
            using (var tran = _session.BeginTransaction())
            {
                _session.Delete(contato);
                tran.Commit();
            }
        }
...

O método ObterPor() ainda segue a mesma dinâmica dos dois últimos métodos, mas agora retornando um objeto contato através do método genérico Get() ele possui algumas sobrecargas, mas a mais usada e a apresentada abaixo, onde ela recebe um “id”, ou seja, o identificado do objeto como parâmetro e nos retorna o objeto que satisfaça esse “id”, se não houver um registro no banco com esse “id” ele retorna null.

        public Contato ObterPor(int id)
        {
            return _session.Get(id);
        }
...

O método ObterTodos() faz uso de LINQ liberado na versão 3.0 do NHibernate, antes o LINQ era distribuído em um projeto à parte, mas agora ele esta incorporado ao NHibernate. O LINQ acredito eu dispensar apresentação é um recurso que foi liberado na versão 3.5 do .NET framework, e não poderia estar de fora de um projeto com o NHibernate.

        public IEnumerable ObterTodos()
        {
            return _session.Query().ToList();
        }
}

Criando o mapeamento.

Como já foi dito antes o NHibernate é uma framework de ORM (Mapeamento objeto relacional), ou seja, ele mapeia o nosso modelo orientado a objeto para o nosso modelo relacional, como sabemos, os bancos de dados relacionais são os mais populares e os mais usados, também sabemos que esse banco, é construído dentro do paradigma relacional, e quando programamos OO temos uma divergência entre esses paradigmas, o padrão ORM veio para diminuir esse atrito, outra tentativa de eliminar esse atrito são os bancos de dados OO, mas esse foge do escopo desse texto.

No NHibernate temos diversas maneiras de fazer esse mapeamento uma dela é usando o Fluent NHibernate, o Fluent é um projeto que possibilita realizar o mapeamento através de interface fluente como podemos ver na classe abaixo.

public class ContatoMap : ClassMap
{
        public ContatoMap()
        {
            Id(x => x.Id)
                .GeneratedBy.Identity()
                .UnsavedValue(0)
                .Access.CamelCaseField(Prefix.Underscore);
 
            Map(x => x.Nome);
        }
}

A classe ContatoMap faz uma herança da classe genérica ClassMap, que nos fornece métodos para realizar o mapeamento da classe Contato. No construtor da classe temos duas linhas onde fazemos o mapeamento.

A primeira mapeia a propriedade Id do objeto Contato, fazemos isso usando o método Id() onde usamos uma lambda para setar a propriedade, em seguida acessamos a propriedade GeneratedBy, essa propriedade nos fornece métodos para especificar como o Id será gerado no nosso caso usamos o método Identity() que determina que o id do objeto seja gerado pelo próprio banco de dados, no nosso caso o SQLServer, logo em seguida configuramos a estratégia que será aplicada a o método SaveOrUpdate() do objeto ISession, que usamos para inserir ou atualizar os nosso objetos, fazemos essa configuração como método UnsavedValue() que recebe um object no nosso caso passamos zero, isso resultara no seguinte comportamento, toda vez que executarmos o método SaveOrUpdate() para persistir um contato que tenha o Id com o valor igual a zero o NHibernate disparara um Insert e para os demais valores um Update, por ultimo acessamos a propriedade Access que possibilita determinarmos como o acesso a propriedade será feito, no nosso caso que a propriedade id é somente leitura determinamos que o NHibernate fizesse esse acesso através do campo _id do objeto, isso será feito via reflection de maneira transparente.

A segunda linha mapeia a propriedade Nome do objeto Contato, da maneira mais simples possível, usando o método Map() também podemos definir a maneira que será feito o acesso, o nome da coluna no banco entre outras configurações, aconselho que você explore os recursos de mapeamento com calma e dedicação.

Criando a classe NhibernateRegistry

Feito isso podemos dar continuidade a implementação do acesso a dados, sendo assim vamos criar a nossa classe NhibernateRegistry ela é responsável por configurar o StructureMap para que ele se comporte adequadamente.

Quem é esse StructureMap e para que ele serve?

O StructureMap e um framework de DI (Injeção de Dependência) e IOC (inversão de controle), que nos permite configurar como serão resolvidas as dependências da nossa aplicação. DI e IOC são padrões de projetos que proporciona um maior desacoplamento da aplicação, beneficiando os testes e tornando a aplicação mais extensível.

O StructureMap no fornece diversas maneira de configurá-lo e uma delas é criando módulos de configuração e a classe NhibernateRegistry será o modulo de configuração responsável por direcionar como StructureMap devera resolver as dependências referentes a o NHibernate.

A classe NhibernateRegistry

Criamos a classe fazendo herança da classe Registry do StructureMap, essa por sua vez nos fornece acesso aos métodos de configuração, logo no construtor da classe, já fazemos duas configurações essenciais, na primeira especificamos que toda vez que solicitarmos uma instancia de ISessionFactory ele nos retornara a mesma instancia, isso ocorre graças ao método Singleton() e em seguida temos o método Use() onde passamos como será criada essa instancia, que no caso é criada pelo método ObterSessionFactory() na segunda linha temos a configuração de com será o comportamento do StructureMap quando solicitado um instância de ISession, nessa linha usamos o método HybridHttpOrThreadLocalScoped() que tem o comportamento um pouco diferente do Singleton(), ele no fornece um instância por thread ou por Request se falarmos de aplicações Web o método Use() por sua vez tem o mesmo comportamento da primeira linha, mas nesse já fazemos uso do contêiner do StructureMap acessando e solicitando um instancia de ISessionFactory e logo em seguida usamos o método OpenSession() da SessionFactory obtida através GetInstance() para nos fornecer uma ISession.

public class NhibernateRegistry : Registry
{
        public NhibernateRegistry()
        {
            For().Singleton().Use(ObterSessionFactory());
            For().HybridHttpOrThreadLocalScoped().Use(
                x => x.GetInstance().OpenSession());
        }
...

O método ObterSession().

Esse método é onde fazemos a configuração da string de conexão e mostramos para o Fluent onde se encontra as nossas classes de mapeamento, alem de configurar, retornamos um objeto ISession.

Toda a configuração é feita pelo do próprio Fluent através da classe Fluently que contem o método estático Configure() onde especificamos o dialeto que o NHibernate ira usar, no nosso caso é o MsSqlConfiguration.MsSql2008 e o próprio dialeto no fornece o método ConnectionString() onde podemos configura a string de conexão, esse método tem varias sobrecargas com diversas maneiras de configurar. Depois de resolver o dialeto e a string de conexão, temos que mostrar onde se encontra as nossas classes de mapeamento, fazemos isso com o método Mappings() acessamos a propriedade FluentMappings que contem o método genérico AddFromAssemblyOf() onde passamos pelo seu parâmetro genérico uma classe de nosso mapeamento e ele trata de resolver o resto e finalizando fazemos uma chamada ao método BuildSessionFactory() que compila todas essas informações e nos retorna uma ISession do NHibernate.

        private static ISessionFactory ObterSessionFactory()
        {
            return Fluently.Configure()
                .Database(
                MsSqlConfiguration.MsSql2008.ConnectionString(
                    @"Data Source=.\SQLEXPRESS;Initial Catalog=ContatoDevmedia;Persist Security Info=True;User ID=sa;Password=123456")
                    //habilita a exibição dos SQL disparados contra o banco de dados no output do VS
                    .ShowSql())
                .Mappings(c => c.FluentMappings.AddFromAssemblyOf())
                .ExposeConfiguration(c => new SchemaExport(c).Create(false, true))
                .BuildSessionFactory();
        }
}

Criando a interface da nossa aplicação

As interfaces alem de exerce o papel de apresentação criando um meio para interação com o usuário também funciona como um elo entre as camadas de Domínio e de Acesso a Dados é nessa camada que orquestramos o fluxo de edição até o momento da persistência.

O layout

Criamos uma janela simples com dois TextBox, dois Button e um DataGrigView como pode ser vista na janela abaixo.


Codificando a janela.

Depois de criar o layout iremos codificar os métodos da nossa aplicação, iniciando pelo método Salvar() que contem um código simples primeiro ele obtém o id do textbox txtCodigo em seguida tenta obter um objeto contato com o respectivo id através do método ObterPor() do objeto ContatoRepositorio ,se não existir nenhum objeto que satisfaça esse id o método retorna null e se isso ocorrer criamos um novo objeto contato e atribuímos o valor da propriedade Text do txtNome, depois validamos e por fim persistimos o objeto no banco com o método Salvar() do objeto ContatoRepositorio.

 void Salvar()
{
            var id = string.IsNullOrEmpty(txtCodigo.Text)
                         ? 0
                         : Convert.ToInt32(txtCodigo.Text);
 
            var contato = _repositorio.ObterPor(id) ?? new Contato();
 
            contato.Nome = txtNome.Text;
 
            if (ValidaContato(contato))
            {
                _repositorio.Salvar(contato);
                CarregaGrid();
                LimpaControles();
            }
}
…

Excluindo

O método Excluir também é bem simples primeiro verifica se existe algum item selecionado no datagrid se houver algo continua montando uma mensagem para informar o usuário que ele esta preste a excluir um contato em seguida temos um bloco de código dentro de uma instrução IF que espera o retorno do usuário se ele ira continuar com a exclusão, se sim recuperamos do banco o usuário do banco e submetemo-lo ao método Excluir() do repositório e finalizamos fazendo os devidos tratamentos de interface.

void Excluir()
{
            if(dgdContatos.CurrentRow == null) return;
 
            var msg = string.Format("Deseja excluir o contato: {0}",
                dgdContatos.CurrentRow.Cells["clnNome"].Value);
 
            if (MessageBox.Show(msg, Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                MessageBoxDefaultButton.Button2).Equals(DialogResult.Yes))
            {
                var contato = _repositorio.ObterPor((int) dgdContatos.CurrentRow.Cells["clnCodigo"].Value);               
                _repositorio.Excluir(contato);
               
                CarregaGrid();
                LimpaControles();
            }
}
...

Limpando os controles

Criamos um foreach para percorrer os controles textbox do formulário executamos o método Clear() .

void LimpaControles()
{
            foreach (TextBox control in grpContato.Controls.OfType())
                (control).Clear();
}
...

Realizando validação.

Validamos os controles do formulário que no caso não faz nada alem de verificar se foi informado um nome para o contato.

bool ValidaContato(Contato contato)
{
            return !string.IsNullOrEmpty(contato.Nome);
}
...

Selecionado o contato para ser alterado.

Criamos um método para selecionar um item no datagrid e apresentar o mesmo na área de edição do formulário.

void SelecionaContato()
{
            if(dgdContatos.CurrentRow== null) return;
 
            txtCodigo.Text = Convert.ToString(dgdContatos.CurrentRow.Cells["clnCodigo"].Value);
            txtNome.Text = Convert.ToString(dgdContatos.CurrentRow.Cells["clnNome"].Value);
}
...

Carregando os dados no grid.

Aqui fazemos uma chamada ao método CarregaGrid() do repositório a fim de obter todos os contatos preencher o datagrid.

void CarregaGrid()
{
            dgdContatos.DataSource = _repositorio.ObterTodos().ToList();
}
...

O Construtor

Para finalizar a nossa app implementamos o construtor do nosso formulário onde iremos linkar os nossos métodos aos eventos dos componentes , mas antes iremos obter uma instancia de IContatoRepositorio, e fazemos isso através do contêiner do StructureMap. Você se lembra que a nossa classe ContatoRepositorio tem apenas um construtor que recebe um objeto ISession , é isso mesmo, o StructureMap devidamente configurado resolve essa dependência injetando o objeto no construtor da classe nos livrando o trabalho de gerenciar e criar o objeto ISession e o objeto ContatoRepositorio .

public FromContato()
{
            InitializeComponent();
            _repositorio = ObjectFactory.GetInstance();
            Load += (s, e) => CarregaGrid();
            btnSalvar.Click += (s, e) => Salvar();
            btnExcluir.Click += (s, e) => Excluir();
            dgdContatos.DoubleClick += (s, e) => SelecionaContato();
}

Considerações e conclusão

Muito se fala sobre em que momento podemos fazer uso de ferramentas de DI e ORM, se fala muito em inserir complexidade desnecessária ao projeto, eu sinceramente não vi um cenário que eu desaconselharia o uso dessas ferramentas, lógico que adoção de tais ferramentas requer cuidado e estudo, afinal o que não requer estudo nessa profissão e antes de mais nada não podemos confundir padrões com ferramentas, você sempre pode aplicar esses padrões sem o uso de uma ferramenta especifica.

Nos próximo post irei falar um pouco mais sobre os mapeamentos do NHibernate.

links

Código fonte aqui.

NHibernate - Site do Projeto:

http://nhforge.org/

Download:

http://sourceforge.net/projects/nhibernate/files/NHibernate/3.0.0.GA/NHibernate-3.0.0.GA-bin.zip/download

StructureMap - Site do Projeto:

http://structuremap.net/structuremap/index.html

Download:

https://github.com/downloads/structuremap/structuremap/StructureMap2.6.1.zip

Fluent NHibernate - Site do Projeto:

http://fluentnhibernate.org/" rel="nofollow" target="_blank">http://fluentnhibernate.org/

Download:

http://fluentnhibernate.org/dls/v1.x/fluentnhibernate-NH3.0-binary-1.2.0.694.zip