O Microsoft ADO.NET Entity Framework, como já se sabe, é um framework ORM (Object-Relational Mapping) nativo do .NET Framework, logo, já vem integrado ao Visual Studio, principal IDE dessa plataforma.

Porém, há quem pense que a função de ORM do Entity se resume a mapear as tabelas do banco de dados e transformá-las em classes e coleções dentro da aplicação. Mas uma grande base de dados dificilmente será composta apenas por tabelas, outros objetos como triggers, functions e stored procedures possuem grande importância e participação fundamental na manutenção das regras de negócio da aplicação.

Cenário

Por exemplo, consideremos uma aplicação qualquer que grava no banco de dados logs de erros que tenham ocorrido. Utilizando o Entity podemos simplesmente inserir um novo registro nessa tabela utilizando o objeto mapeado.

Vamos supor a existência da seguinte tabela:

Tabela de log de erros

Figura 1: Tabela de log de erros

Inicialmente ficou definido que no campo DESCRICAO seria gravada apenas a descrição do erro, em um formato qualquer. Bastaria preencher os atributos desse objeto e adicioná-lo à coleção mapeada pelo Entity. Bastante simples.

Porém, em determinado momento viu-se a necessidade de gravar, junto com a descrição do erro, o login do usuário que está atualmente conectado. Bastaria então adicionar essa informação ao atributo DESCRICAO do objeto tB_LOG_ERROS, sem nenhum mistério. Mas caso tenhamos várias aplicações que utilizem esta mesma tabela e que devem seguir o mesmo procedimento para registrar logs, essa alteração terá de ser feita em todas elas. E sempre que uma nova modificação for necessária, todas as aplicações terão de ser adaptadas.

A solução

Uma solução para essa situação é criar um Stored Procedure que fique responsável por realizar as inserções nessa tabela. Assim, sempre que precisarmos mudar o formato do campo DESCRICAO, bastaria alterar esse procedure no banco e todas as aplicações que o utilizem estariam automaticamente adequadas ao novo formato.

Então, partindo desse modelo, vamos criar este stored procedure de acordo com a Listagem 1.

Listagem 1: Store procedure para registrar logs de erro

CREATE PROCEDURE USP_REGISTRAR_LOG_ERRO
@DESCRICAO	VARCHAR(MAX)
AS
BEGIN
	INSERT INTO TB_LOG_ERROS
	(DATA_HORA, DESCRICAO)
	VALUES
	(GETDATE(),SUSER_SNAME() + ' - ' + 
 @DESCRICAO)
END
GO

Percebemos também mais um ponto que o procedure facilitou: a definição do campo DATA_HORA. Antes era preciso preencher esse campo, provavelmente com o objeto DateTime.Now, agora isso também fica por conta do banco. Ou seja, na aplicação só precisaremos nos preocupar com preencher o campo DESCRICAO com o erro, pois até o nome do usuário conectado será adicionado pelo stored procedure.

Criando o modelo pelo Entity

Observação: Essa seção é destinada a quem ainda não tem o modelo criado na aplicação, caso seu modelo já esteja criado, os passos a seguir não serão necessários.

Agora podemos criar uma aplicação ASP.net, Windows Forms, WPF, ou qualquer tipo que suporte o Entity e testar nosso procedure. Neste artigo será criada uma aplicação Windows Forms pela sua simplicidade.

O primeiro passo é adicionar o Model do Entity clicando com a direita sobre o projeto no Solution Explorer e em Add > New Item. Na categoria Data, selecionamos o item ADO.NET Entity Data Model e clicamos em Add.

Como já temos o banco criado, devemos selecionar a opção Generate from database e clicar em Next. Na tela seguinte, basta configurar a string de conexão, dar um nome ao modelo e clicar em Next novamente.

Na tela que se abrirá poderemos selecionar os objetos que desejamos que sejam mapeados, como tabelas e procedimentos. Como nós precisaremos apenas do procedure criado, basta selecioná-lo na categoria Stored Preocedures and Functions, conforme ilustra a Figura 2.

Adicionado o procedure ao modelo

Figura 2: Adicionado o procedure ao modelo

Observação: Observe no último checkbox dessa tela. Deixando-o marcado, o procedure será automaticamente importado como uma função, identificada pelo próprio nome do procedure.

Caso seu modelo já esteja criado e você deseja inserir um novo stored procedure, proceda como descrito a seguir.

Adicionado um procedure a um modelo já criado

Se você deseja adicionar ao modelo um novo stored procedure, o que é mais comum de acontecer, pois em aplicações reais novos objetos são criados com frequência, abra o modelo e clique com a direita na região central do diagrama. No menu que será exibido, clique em Update model from database e será levado à tela exibida na Figura 2, onde deve-se selecionar o procedure criado.

Em seguida, clique novamente com a direita no diagrama do modelo e agora utilize a opção Add New > Function Import, como se pode ver na Figura 3.

Importando uma função (ou procedure)

Figura 3: Importando uma função (ou procedure)

No campo Function Name da janela que se abrirá, dê um nome à função que representará o stored procedure no modelo. Em seguida selecione o procedure importado no campo Store procedure/Function name e clique em Ok, conforme a Figura 4.

Criando a função para executar o procedure

Figura 4: Criando a função para executar o procedure

Repare que o stored procedure também poderia retornar algum resultado, como uma variável escalar ou uma coleção de registros. Seria necessário apenas definir o tipo de retorno e clicar no botão Get Column Information para verificar a estrutura do retorno do procedure. Esse retorno seria utilizado também como retorno da função, como Int32, String, List, etc.

Após clicar em OK, vamos à guia Model Browser lá veremos a função que foi criada:

Model browser com função criada

Figura 5: Model browser com função criada

Logo abaixo da função que acabamos de criar, vemos outra com o mesmo nome do procedure. Isso ocorre por que mantivemos marcado o checkbox “Import selected procedures and functions into the entity model”. Caso o tivéssemos desmarcado, o procedimento seria importado, mas não seria criada uma função automaticamente. Resumindo, neste caso as duas funções são idênticas, de forma que poderíamos excluir a segunda sem nenhum problema, mantendo apenas a primeira por ter um nome mais adequado ao que estamos acostumados no código C#.

Vale ainda destacar que ao expandir a função temos os parâmetros que ela espera, no caso, o parâmetro DESCRICAO.

Utilizando a função criada

Chegamos então na parte em que iremos enfim utilizar a função criada. No form principal da aplicação que criamos (ou na página ASP.net, janela WPF, etc), o que precisamos fazer é instanciar o modelo criado e executar, a partir dele, a função Registrar_Log_Erro, da seguinte forma:

Listagem 2: Executando a função importada

DBTeste_Entities db = new DBTeste_Entities();
db.Registrar_Log_Erro("Erro inserido para teste");

Observação: o código acima pode ser inserido no evento Click de um botão, por exemplo, ou em qualquer local onde possa ser executado.

Se verificarmos no banco, veremos que o registro foi realmente inserido, como mostra a Figura 6.

Registro inserido

Figura 6: Registro inserido

E como esperado, o nome do usuário conectado ao bd foi inserido na descrição do erro.

Para simular uma situação ainda mais próxima do real, poderemos chamar essa função no tratamento de exceções, para gravar a mensagem de erro real.

No código abaixo simulamos uma exceção e gravamos a mensagem utilizando a função Registrar_Log_Erros.

Listagem 3: Simulando exceção e gravando erro

try
{
    decimal a = 1;
    decimal b = 1;
    decimal c = (a + b) / (a - b);
}
catch (Exception ex)
{
    using (DBTeste_Entities db = new DBTeste_Entities())
    {
        db.Registrar_Log_Erro("[" + ex.Message +"] - " + this.Name);
    }
}

Ao tentarmos efetuar uma divisão por zero, teremos uma exceção, que será tratada e a mensagem de erro gravada no banco, juntamente com o nome do form onde ocorreu a exceção (this.Name).

Ao executar esse código, teremos um registro inserido na tabela, semelhante ao que é mostrado na Figura 7.

Mensagem de exceção gravada no banco

Figura 7: Mensagem de exceção gravada no banco

Conclusão

Utilizar stored procedures com o Entity Framework é bastante simples e útil, pois podemos transferir algumas responsabilidades para o banco e executar essas funções com a sintaxe da linguagem utilizada (neste exemplo, C#). Para utilizar funções do banco, o procedimento é o mesmo.

Concluímos que o mapeamento do Entity não se resume às tabelas, nos permitindo explorar outros objetos do banco de dados e aproveitar sua funcionalidade.