Integração SQL Server 2012 x .NET: criando procedures utilizando código C#

Veja neste artigo como criar um stored procedure para uma base do SQL Server 2012 através de código-fonte escrito em C#. Construções deste tipo são conhecidas como CLR Stored Procedures, permitindo a criação de rotinas a partir do .NET framework

Além de comandos para a consulta, inclusão, alteração e exclusão de informações, os principais bancos de dados relacionais contam ainda com mecanismos que estendem esses recursos básicos da linguagem SQL. Talvez uma das estruturas mais conhecidas desse tipo sejam as stored procedures (também chamadas de “procedimentos armazenados” ou, simplesmente, “procedures”).

Uma stored procedure é um objeto que combina instruções convencionais de acesso a dados a elementos como variáveis, estruturas de decisão e laços de repetição, conseguindo assim ampliar as capacidades oferecidas por comandos SQL padrão. Não existe, contudo, uma padronização quanto à forma como procedures são implementadas; cada fornecedor de banco de dados costuma disponibilizar um conjunto de recursos específicos e que, em muitos casos, não encontra paralelo em produtos similares da concorrência.

Podem ser citadas como vantagens proporcionadas pela utilização de stored procedures:

Dentre as principais tecnologias de banco de dados da atualidade, o SQL Server é sem sombra de dúvidas uma das que conta com maior aceitação por parte do mercado. Por ser um produto robusto, escalável e extremamente flexível, não é de se estranhar que grandes corporações e órgãos governamentais façam uso de bancos de dados SQL Server em inúmeras aplicações de caráter crítico.

Sendo um banco de dados relacional, o SQL Server conta tanto com construções típicas da linguagem SQL (instruções baseadas nas cláusulas SELECT, INSERT, UPDATE e DELETE), quanto oferece mecanismos para a codificação de estruturas mais complexas. Neste último caso, objetos como stored procedures, triggers e functions são implementados utilizando uma extensão conhecida como T-SQL (sigla do inglês “Transact-SQL”).

Inúmeros foram os recursos introduzidos a partir da versão 2005 do SQL Server. Considerando o desenvolvimento voltado a banco de dados, merece destaque a integração que passou existir neste novo release com o Common Language Runtime (CLR) do .NET Framework: tornou-se possível desde então a criação de stored procedures, triggers e functions empregando linguagens compatíveis com a plataforma .NET, como C# ou VB.NET.

Procedures criadas em .NET permitem a realização de ações não suportadas por rotinas equivalentes escritas em Transact-SQL. Construções deste tipo são conhecidas como CLR Stored Procedures e, por serem baseadas em recursos da plataforma .NET, permitem a realização de operações como o acesso a diretórios, leitura/escrita de arquivos, criptografia, manipulação de arrays ou o tratamento de strings por meio de expressões regulares.

O objetivo deste artigo é demonstrar como procedures do SQL Server 2012 podem ser implementadas a partir de código .NET. Buscando cumprir essa meta, será apresentado um exemplo de implementação de uma stored procedure deste tipo através do Visual Studio.

Criando uma stored procedure através do Visual Studio

A solução apresentada neste artigo foi criada no .NET framework 4.5, através da utilização do Microsoft Visual Studio 2012 Professional. Basicamente, será construída biblioteca em que constará um método público definido em uma classe estática. Essa operação corresponde, por sua vez, à implementação da stored procedure a ser acionada a partir de comandos enviados a uma base do SQL Server.

Quanto ao funcionamento desta procedure, essa rotina receberá como parâmetro um diretório capaz de ser acessado pelo servidor SQL Server, produzindo como resultado de sua execução lista dos arquivos contidos em tal caminho (incluindo arquivos vinculados a subdiretórios da pasta-base).

Para gerar o projeto de testes será necessário, dentro do Visual Studio, acessar o menu File, opção New, sub opção Project. Dentro da tela New Project (Figura 1) selecionar o template SQL Server Database Project (que deve estar em “Installed > Templates > Other Languages > SQL Server”), informando no campo Name o nome da aplicação a ser gerada (“TesteCLRProcedure”, neste caso); no campo Location é possível ainda definir o diretório no qual serão criados os arquivos para este projeto.


Figura 1: Criando o projeto TesteCLRProcedure

Prosseguiremos agora com a implementação da procedure baseada em recursos do .NET Framework.

Clicar dentro do Solution Explorer com o botão direito do mouse sobre o projeto TesteCLRProcedure, escolhendo em seguida no menu de atalho a opção Add, sub opção New Item. Neste momento será exibida a tela Add New Item; selecionar do lado esquerdo o item “SQL CLR C#” e, após isto, o template “SQL CLR C# Stored Procedure”; preencher o campo Name com “SP_ListarArquivosDiretorio.cs”.


Figura 2: Criando a procedure SP_ListarArquivosDiretorio

Como resultado dessa última ação, será criado o arquivo SP_ SP_ListarArquivosDiretorio.cs, em que estará o código que implementa uma procedure com este mesmo nome.

A operação SP_ListarArquivosDiretorio (Listagem 1) deve fazer parte de uma classe parcial chamada StoredProcedures. O uso desse tipo de construção (classes parciais) é próprio de projetos envolvendo a implementação de procedures por meio do Visual Studio, cabendo ao desenvolvedor apenas o trabalho de codificar as ações que serão disparadas ao se invocar tais rotinas.

Além do fato de estarem associadas a uma partial class, stored procedures baseadas em recursos do CLR possuem ainda as seguintes características:

No caso da procedure SP_ListarArquivosDiretorio, o método que define esta construção receberá como parâmetro de entrada uma string (referência “diretorio”) contendo um diretório a ser tomado como base. Foi especificado ainda o parâmetro de saída “qtdeArquivos”, no qual constará a quantidade de arquivos que existem no caminho informado ao se acionar a stored procedure.

Quanto ao funcionamento de SP_ListarArquivosDiretorio, é possível observar:

Listagem 1: Implementação da procedure SP_ListarArquivosDiretorio

using System; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using System.IO; public partial class StoredProcedures { [Microsoft.SqlServer.Server.SqlProcedure] public static void SP_ListarArquivosDiretorio( string diretorio, out int qtdeArquivos) { string nomeTabelaTemporaria = "#FEED_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss"); using (SqlConnection conexao = new SqlConnection("context connection=true")) { conexao.Open(); CriarTabelaTemporaria( conexao, nomeTabelaTemporaria); CarregarInformacoesDiretorio( conexao, null, nomeTabelaTemporaria, diretorio); ExecutarConsultaInformacoesArquivos( conexao, nomeTabelaTemporaria); qtdeArquivos = ObterQuantidadeArquivos( conexao, nomeTabelaTemporaria); RemoverTabelaTemporaria( conexao, nomeTabelaTemporaria); } } private static void CriarTabelaTemporaria( SqlConnection conexao, string nomeTabelaTemporaria) { SqlCommand cmdCreateTable = conexao.CreateCommand(); cmdCreateTable.CommandText = String.Format( "CREATE TABLE " + "( " + "NomeArquivo VARCHAR(2000), " + "DataCriacao DATE, " + "TamanhoBytes INT " + ")", nomeTabelaTemporaria); cmdCreateTable.ExecuteNonQuery(); } private static void CarregarInformacoesDiretorio( SqlConnection conexao, SqlCommand cmdInsert, string nomeTabelaTemporaria, string diretorio) { if (cmdInsert == null) { cmdInsert = conexao.CreateCommand(); cmdInsert.CommandText = String.Format( "INSERT INTO VALUES " + "(@NomeArquivo, @DataCriacao, @TamanhoBytes)", nomeTabelaTemporaria); cmdInsert.Parameters.Add( "@NomeArquivo", SqlDbType.VarChar); cmdInsert.Parameters.Add( "@DataCriacao", SqlDbType.Date); cmdInsert.Parameters.Add( "@TamanhoBytes", SqlDbType.Int); } DirectoryInfo informacoesDiretorio = new DirectoryInfo(diretorio); foreach (FileInfo arquivo in informacoesDiretorio.GetFiles()) { cmdInsert.Parameters["@NomeArquivo"].Value = arquivo.FullName; cmdInsert.Parameters["@DataCriacao"].Value = arquivo.CreationTime; cmdInsert.Parameters["@TamanhoBytes"].Value = arquivo.Length; cmdInsert.ExecuteNonQuery(); } foreach (DirectoryInfo subDiretorio in informacoesDiretorio.GetDirectories()) { CarregarInformacoesDiretorio( conexao, cmdInsert, nomeTabelaTemporaria, subDiretorio.FullName); } } private static void ExecutarConsultaInformacoesArquivos( SqlConnection conexao, string nomeTabelaTemporaria) { SqlCommand cmdSelect = conexao.CreateCommand(); cmdSelect.CommandText = "SELECT * FROM " + nomeTabelaTemporaria; SqlContext.Pipe.ExecuteAndSend(cmdSelect); } private static int ObterQuantidadeArquivos( SqlConnection conexao, string nomeTabelaTemporaria) { SqlCommand cmdSelect = conexao.CreateCommand(); cmdSelect.CommandText = "SELECT COUNT(1) FROM " + nomeTabelaTemporaria; return Convert.ToInt32(cmdSelect.ExecuteScalar()); } private static void RemoverTabelaTemporaria( SqlConnection conexao, string nomeTabelaTemporaria) { SqlCommand cmdCreateTable = conexao.CreateCommand(); cmdCreateTable.CommandText = "DROP TABLE " + nomeTabelaTemporaria; cmdCreateTable.ExecuteNonQuery(); } }

Após esta etapa de codificação, compilar o projeto, a fim de permitir que a DLL obtida seja registrada na base de testes.

Configurando o banco de dados de dados para a utilização da procedure criada

Terminada a construção da stored procedure SP_ListarArquivosDiretorio, será necessário agora a configuração/deploy da mesma numa base de dados (este artigo parte do pressuposto que se estará utilizando um banco chamado “TesteCLRProcedure”).

Antes de prosseguir com os ajustes na base TesteCLRProcedure, é preciso se certificar de que no servidor do SQL Server está habilitado o uso do Common Language Runtime da plataforma .NET. A Listagem 2 contém um script que ativa este mecanismo, empregando para isto as rotinas SP_CONFIGURE e RECONFIGURE.

Listagem 2: Habilitando o uso do CLR num servidor SQL Server

sp_configure 'clr enabled', 1 go reconfigure go

Como o banco de dados TesteCLRProcedure depende de uma procedure que acessa informações sobre diretórios e arquivos, o mesmo deverá ser marcado com o parâmetro TRUSTWORTHY (Listagem 3) por um usuário com direitos de administrador: esta alteração indica que a base em questão “confia” no conteúdo do banco de dados (incluindo aqui a stored procedure SP_ListarArquivosDiretorio). Caso isto não aconteça, uma exceção será lançada durante a tentativa de se ler o conteúdo de uma pasta.

Listagem 3: Configurando um banco de dados como TRUSTWORTHY

ALTER DATABASE TesteCLRProcedure SET TRUSTWORTHY ON GO

O próximo passo agora é registrar a biblioteca (arquivo .dll) correspondente ao projeto TesteCLRProcedure. Embora este procedimento possa ser feito através do Visual Studio, a execução de um script permite não apenas configurar opções adicionais que se fazem necessárias (incluindo nisto parâmetros de segurança), como também facilita a vida de DBAs durante a implantação deste tipo de recurso (reaproveitando neste caso os scripts gerados anteriormente).

Na Listagem 4 é apresentado um exemplo disto. O comando CREATE ASSEMBLY gera uma referência que aponta para a .dll gerada anteriormente (deverá ser fornecido o caminho completo deste arquivo). Já a opção PERMISSION_SET definida como “EXTERNAL_ACCESS” permite ao SQL Server acessar recursos externos como o sistema de arquivos, o registro do Windows ou variáveis de sistema se a procedure em questão depender disto.

Listagem 4: Registrando a biblioteca em que foi implementada uma procedure

USE TesteCLRProcedure GO CREATE ASSEMBLY TesteCLRProcedure FROM 'C:\Devmedia\TesteCLRProcedure\TesteCLRProcedure\bin\Debug\TesteCLRProcedure.dll' WITH PERMISSION_SET = EXTERNAL_ACCESS GO

Já na Listagem 5 está o script responsável por registrar a procedure SP_ListarArquivosDiretorio no banco de dados de testes. Os parâmetros informados devem corresponder às definições utilizadas na implementação do método correspondente em .NET, com a cláusula EXTERNAL NAME contendo a referência para esta operação.

Listagem 5: Registrando uma CLR stored procedure numa base de dados

USE TesteCLRProcedure GO CREATE PROCEDURE SP_ListarArquivosDiretorio( @Diretorio NVARCHAR(600), @QtdeArquivos INT OUTPUT) AS EXTERNAL NAME TesteCLRProcedure.StoredProcedures.SP_ListarArquivosDiretorio GO

Na Figura 3 já constam as referências para o Assembly TesteCLRProcedure.dll e a procedure SP_ListarArquivosDiretorio, após a execução dos scripts aqui demonstrados.


Figura 3: Stored procedure SP_ListarArquivosDiretorio já configurada

Efetuando o teste da procedure SP_ListarArquivosDiretorio

Concluídos os ajustes da seção anterior, será efetuado agora um teste envolvendo a stored procedure . Conforme pode ser observado na Listagem 6, as instruções necessárias para a execução desta procedure em nada diferem de construções equivalentes baseadas em T-SQL (invocação de uma procedure, passagem de parâmetros, definição de uma variável de retorno etc.).

Listagem 6: Executando a procedure SP_ListarArquivosDiretorio

USE TesteCLRProcedure GO DECLARE @QtdeArquivosEncontrados INT EXEC [dbo].[SP_ListarArquivosDiretorio] @Diretorio = 'C:\Devmedia\TesteCLRProcedure\TesteCLRProcedure\', @QtdeArquivos = @QtdeArquivosEncontrados OUTPUT SELECT @QtdeArquivosEncontrados as QtdeArquivos

Como resultado da execução do script da Listagem 6, foram retornadas informações referentes ao diretório em que está o projeto de testes (conforme pode ser visto na Figura 4).


Figura 4: Resultado da execução da procedure SP_ListarArquivosDiretorio

Conclusão

Procurei com este artigo demonstrar como o .NET pode ser integrado ao SQL Server, considerando para isto a construção de stored procedures a partir de código C#. Este tipo de construção estende a capacidade do código T-SQL convencional, possibilitando que recursos da plataforma .NET sejam consumidos dentro de uma base de dados SQL Server. Isto pode, em muitos casos, suprir a falta de funcionalidades não oferecidas nativamente (como a manipulação de arquivos, por exemplo).

Espero que o conteúdo aqui abordado possa auxiliá-lo no dia-a-dia. Até uma próxima oportunidade!


Artigos relacionados