Veremos como acessar e enviar dados para um webservice usando o recurso Assembly do SQL Server, uma ferramenta que pode ajudar muito na hora de realizar tarefas complexas onde apenas os recursos nativos não são o bastante.
O recurso Assembly pode estender (e muito) o poder de processamento do SQL Server utilizando um aplicativo escrito em VB, C# e F#.
O cliente
Agora vamos imaginar o seguinte cenário: uma livraria contratou você para adicionar novas funcionalidades no programa que ela já possui, um programa que gerencia as vendas e todo o estoque.
Se o programa foi criado por você fica fácil resolver o problema, mas se esse programa foi criado por uma empresa e você não tem acesso ao código-fonte ou se tem acesso, a linguagem usada não é a mesma que você programa. Fica complicado resolver!
A solução
Então você descobre que o banco de dados utilizado é o SQL Server e você pode acessá-lo facilmente, agora ficou melhor!
Graças ao Assembly nosso problema ficou fácil de resolver!
Só precisamos programar um pouco e depois criar um assembly no SQL Server para executar a tarefa especificada por nosso cliente.
Especificações do cliente
Atualmente nosso aplicativo de gerenciamento não se integra com nossa plataforma web e cada vez que um novo livro chega em nosso estabelecimento devemos cadastra-lo 2 vezes, uma no sistema de gerenciamento da loja (física) e outra no sistema web onde vendemos nossos produto.
O problema é que além da grande perda de tempo cadastrando o mesmo produto 2 vezes, outro ponto crítico é que se um produto está em falta no estoque meus funcionários devem atualizar o estoque do site, o que nem sempre acontece.
É fundamental que as tarefas citadas sejam automáticas e independente de manipulação por parte dos meus funcionários.
Como você pode notar que existe duas tarefas que devem ser feitas, a inclusão de produtos na loja virtual quando são cadastrados no sistema de gerenciamento e atualização do estoque quando uma venda é realizada. Tudo isso será feito através do webservice que será criado para o cliente.
O sistema deve funcionar como na ilustração abaixo.
Criando o webservice (exemplo)
Vamos criar um webservice muito simples, nosso webservice vai apenas incluir novos produtos e dar baixa no estoque no momento que um produto é vendido.
Vamos ao código!
A criação das tabelas são apenas para ilustrar, de forma mais simples, as tabelas que o cliente já possui na loja virtual .
1 – Crie a tabela Livros;
CREATE TABLE [dbo].[Livros](
[Codigo] [int] IDENTITY(1,1) NOT NULL,
[DescricaoProduto] [nvarchar](max) NOT NULL,
[ValorProduto] [float] NOT NULL,
[ISBN] [bigint] NOT NULL,
[QtdEstoque] [int] NOT NULL,
CONSTRAINT [PK_Livros] PRIMARY KEY CLUSTERED
(
[Codigo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
2 – Crie a tabela Vendas onde vamos guardar as vendas feitas no site;
CREATE TABLE [dbo].[Vendas](
[Codigo] [int] IDENTITY(1,1) NOT NULL,
[DataCompra] [datetime] NOT NULL,
[CodLivro] [int] NOT NULL,
[QtdComprada] [int] NOT NULL,
CONSTRAINT [PK_Vendas] PRIMARY KEY CLUSTERED
(
[Codigo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Vendas] WITH CHECK ADD CONSTRAINT [FK_Vendas_Livros] FOREIGN KEY([CodLivro])
REFERENCES [dbo].[Livros] ([Codigo])
ON UPDATE CASCADE
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Vendas] CHECK CONSTRAINT [FK_Vendas_Livros]
GO
ALTER TABLE [dbo].[Vendas] ADD CONSTRAINT [DF_Vendas_DataCompra] DEFAULT (getdate()) FOR [DataCompra]
GO
3 – Agora vamos criar um novo projeto do tipo webservice, o meu VS está em português;
4 – Vamos codificar nosso webservice;
''' <summary>
''' Procedimento necessário para incluir um novo livro na loja virtual
''' </summary>
''' <param name="DescricaoProduto">Descrição completa do livro</param>
''' <param name="ValorProduto">Preço do livro</param>
''' <param name="ISBN">ISBN do livro</param>
''' <param name="QtdEstoque">Quantidaden disponível em estoque</param>
<WebMethod()> _
Public Function IncluirNovoLivro(ByVal DescricaoProduto As String, ByVal ValorProduto As Double, _
ByVal ISBN As Integer, ByVal QtdEstoque As Integer) As Boolean
Try
Dim cmdSQL As New SqlClient.SqlCommand("INSERT INTO dbo.Livros (DescricaoProduto, ValorProduto, ISBN, QtdEstoque) VALUES (@DescricaoProduto, @ValorProduto, @ISBN, @QtdEstoque)", objConexao)
With cmdSQL
.CommandType = CommandType.Text
.Parameters.AddWithValue("@DescricaoProduto", DescricaoProduto)
.Parameters.AddWithValue("@ValorProduto", ValorProduto)
.Parameters.AddWithValue("@ISBN", ISBN)
.Parameters.AddWithValue("@QtdEstoque", QtdEstoque)
If .ExecuteNonQuery() > 0 Then
Return True
Else
Return False
End If
End With
Catch ex As Exception
Return False
End Try
End Function
''' <summary>
''' Função que da baixa no estoque da loja virtual.
''' </summary>
''' <param name="CodigoProduto">Código do produto</param>
''' <param name="QtdVendido">Quantidade de produtos vendidos</param>
<WebMethod()> _
Public Function BaixaNoEstoque(ByVal CodigoProduto As Integer, ByVal QtdVendido As Integer)
Try
If QtdVendido > 0 Then
Dim cmdSQL As New SqlClient.SqlCommand("UPDATE dbo.Livros SET QtdEstoque = QtdEstoque - @QtdVendida WHERE Codigo = @Codigo", objConexao)
With cmdSQL
.CommandType = CommandType.Text
.Parameters.AddWithValue("@QtdVendida", QtdVendido)
.Parameters.AddWithValue("@Codigo", CodigoProduto)
If .ExecuteNonQuery() > 0 Then
Return True
Else
Return False
End If
End With
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function
Agora que o webservice está pronto vamos criar o Assembly do SQL Server.
Usando o SQL Server para enviar as informações
Agora chegou a hora de criar o Assembly, antes disso nós precisamos saber mais algumas coisas sobre como o Assembly funciona no SQL Server. Um assembly é uma biblioteca dinâmica (.dll) que pode ser escrito utilizando as principais linguagens da Microsoft como o VB.NET, C# e F#.
Para utilizar o assembly no SQL Server é preciso seguir algumas regras de segurança exigidas no momento da criação, ao criar um assembly o SQL Server primeiro vai validá-lo para ter certeza de que ele não carrega códigos do tipo inseguro.
A validação do SQL Server faz as seguintes verificações antes de criar nosso assembly:
- Os segmentos de código deve ter instruções do Microsoft Intermediate Language (MSIL) válidas;
- Deve fazer referência apenas para assemblies do .NET Framework válidos, atualmente são suportados os seguintes assemblies;
custommarshallers.dll, Microsoft.visualbasic.dll, Microsoft.visualc.dll, mscorlib.dll, system.data.dll, System.Data.SqlXml.dll, system.dll, system.security.dll, system.web.services.dll, system.xml.dll, System.Transactions, System.Data.OracleClient, System.Configuration;
- Obedecer as regras da permissão que foi dada durante o camando CREATE ASSEMBLY, veja os tipos de permissões disponíveis:
- SAFE – é o conjunto de permissões mais restritivo e recomendado, o código executado por um assembly com as permissões SAFE não pode acessar recursos externos do sistema;
- EXTERNAL_ACCESS – permite que os assemblies acessem certos recursos externos do sistema, como arquivos, redes, variáveis de ambiente e o Registro;
- UNSAFE – concede aos assemblies acesso irrestrito aos recursos, internos ou externos, de uma instância do SQL Server.
Agora vamos ao programar um pouco.
1 – Inicie seu visual studio e crie um novo projeto chamado LivrariaFict do tipo Biblioteca de Classes ou Class Library (.dll);
2 – Agora vamos programar a inclusão do livro e a baixa no estoque;
Importe os namespaces abaixo.
Imports Microsoft.SqlServer.Server
Imports System.Data.SqlClient
Imports System.Xml
O atributo <SqlProcedure> marca este código como um procedimento armazenado e <SqlTrigger()> indica que a função será usada como uma Trigger. O atributo não é obrigatório, mas é boa forma de documentação do código.
OBS.: Para utilizar os métodos o SQL Server exige que todos eles sejam estáticos.
Public Class MateriaClrSQLServer
''' <summary>
''' Pega o XML retornado pelo webservice e retorna 0/1 de forma compatível com SQL Server
''' </summary>
''' <param name="xmlRetorno">XML que é retornado quando a requisição ao webservice é concluída.</param>
''' <returns> 1 - True / 0 - False </returns>
Public Shared Function getRetorno(ByVal xmlRetorno As String) As SqlTypes.SqlInt32
Dim xmlLeitor As New XmlDocument()
xmlLeitor.LoadXml(xmlRetorno)
Dim blnRetorno As XmlNodeList = xmlLeitor.GetElementsByTagName("boolean")
If blnRetorno(0).InnerText = "true" Then
getRetorno = 1
Else
getRetorno = 0
End If
End Function
'Função que será usada como Procedimento Armazenado dentro do BD
<SqlProcedure()> _
Public Shared Function spIncluirNovoLivro(ByVal DescricaoProduto As String, ByVal ValorProduto As Double, _
ByVal ISBN As Long, ByVal QtdEstoque As Integer) As SqlTypes.SqlInt32
Dim objEnviarDados As New System.Net.WebClient()
objEnviarDados.Headers.Add("Content-Type", "application/x-www-form-urlencoded")
Dim strArgumentos As String = "DescricaoProduto=" & Trim(DescricaoProduto) & "&ValorProduto=" & ValorProduto & "&ISBN=" & ISBN & "&QtdEstoque=" & QtdEstoque
Dim bArgumentos As Byte() = System.Text.Encoding.ASCII.GetBytes(strArgumentos)
Dim bRetorno As Byte() = objEnviarDados.UploadData("http://localhost:4740/LivrariaFict.asmx/IncluirNovoLivro", "POST", bArgumentos)
Return getRetorno(System.Text.Encoding.ASCII.GetString(bRetorno))
End Function
'A tag abaixo informa o nome da trigger, a tabela onde ela será acionada e o evento que
'vai dispara o gatilho
<SqlTrigger(Name:="trgBaixaEstoque", Target:="[dbo].[Vendas]", Event:="FOR INSERT")> _
Public Shared Sub trgBaixaEstoque()
''Fornece informações contextuais sobre as Trigger que foi acionada.
Dim trgContexto As SqlTriggerContext = SqlContext.TriggerContext
Dim objDataReader As SqlDataReader = Nothing
Dim CodigoProduto As Integer = 0, QtdVendido As Integer = 0
''Se a ação realizada for insert entra no bloco
If trgContexto.TriggerAction = TriggerAction.Insert Then
'Pegamos a conexão usada na operação
Dim Conexao As New SqlConnection("context connection=true")
Conexao.Open()
'Pegamos os dados inseridos na tabela
Dim cmdTrg As New SqlCommand("SELECT * FROM INSERTED;", Conexao)
objDataReader = cmdTrg.ExecuteReader
objDataReader.Read()
'Pega as colunas da tabela
CodigoProduto = objDataReader!CodLivro.ToString
QtdVendido = objDataReader!QtdComprada.ToString
'Envia os dados
Dim objEnviarDados As New System.Net.WebClient()
objEnviarDados.Headers.Add("Content-Type", "application/x-www-form-urlencoded")
Dim strArgumentos As String = "CodigoProduto=" & CodigoProduto & "&QtdVendido=" & QtdVendido
'Configura os argumentos
Dim bArgumentos As Byte() = System.Text.Encoding.ASCII.GetBytes(strArgumentos)
objEnviarDados.UploadData("http://localhost:4740/LivrariaFict.asmx/BaixaNoEstoque", "POST", bArgumentos)
objDataReader.Close()
End If
End Sub
End Class
Eu decidi utilizar o método de envio diretamente por HTTP POST ao invés do método tradicional. O visual studio usa o namespace System.ServiceModel no método tradicional e para usá-lo dentro do SQL Server seria necessário importar vários assemblies junto, isso iria causar muita confusão entre os usuários menos experientes.
Após compilar a DLL é necessário importá-la para o SQL Server.
5 – Para utilizar o Assembly é necessário habilitá-lo e dar as permissões necessárias para o banco de dados;
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE WITH OVERRIDE;
GO
ALTER DATABASE LivrariaFict SET TRUSTWORTHY ON;
GO
6– Agora é só criar o Assembly no banco de dados;
CREATE ASSEMBLY LivrariaFict FROM 'C:SqlAcessarWebservice.dll' WITH PERMISSION_SET = UNSAFE
7 – Após a criação do Assembly no banco de dados, vamos criar a STORED PROCEDURE que vai executar nosso assembly e incluir novos títulos na loja virtual do cliente;
CREATE PROCEDURE [dbo].[spIncluirNovoLivro]
@DescricaoProduto [nvarchar](max),
@ValorProduto [float],
@ISBN [bigint],
@QtdEstoque [int]
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [LivrariaFict].[LivrariaFict.MateriaClrSQLServer].[spIncluirNovoLivro]
Também é possível criar uma trigger que atualiza o estoque da loja virtual no momento que uma venda é realizada na loja física.
CREATE TRIGGER trgBaixaEstoque
ON [Vendas]
AFTER INSERT
AS EXTERNAL NAME [LivrariaFict].[LivrariaFict.MateriaClrSQLServer].[trgBaixaEstoque]
Você pode criar uma trigger que executa a STORED PROCEDURE spIncluirNovoLivro para cadastrar automaticamente um novo livro na loja virtual, ou pode modificar o código e criar uma trigger diretamente no banco de dados como foi feito a pouco.
Por enquanto é só, espero que tenham gostado e aproveitem o poder do Assembly no SQL Server com responsabilidade.
Até a próxima.