Cuidados na utilização de Interop em aplicações do Integration Services

Veja neste artigo como evitar problemas no uso de recursos de Interop em soluções do Integration Services. Modificar planilhas do Excel, adequando as mesmas a um padrão específico, representa um exemplo de tarefa bastante comum em projetos deste tipo. Quando isso acontecer, tais operações poderão exigir medidas no sentido de liberar recursos, impedindo assim um consumo anormal de memória no computador que hospeda uma aplicação.

Embora mecanismos como os Web Services tenham atualmente uma importância fundamental na integração entre diferentes sistemas, arquivos-texto, planilhas do Excel e, até mesmo, bancos de dados do Access ainda são utilizados em larga escala por muitas organizações. O uso de um arquivo no intercâmbio de dados está normalmente associado ao processamento de um grande volume de transações, algo que se revelaria provavelmente impraticável se implementado através de um Web Service. Neste último caso, questões como o tráfego de um grande volume de informações em uma rede podem se constituir num fator limitante, degradando consideravelmente a performance do processo como um todo.

O SQL Server Integration Services (ou simplesmente Integration Services) representa a solução que a Microsoft disponibiliza para estes tipos de cenários. Trata-se de uma ferramenta que é parte integrante do SQL Server, com a implementação de funcionalidades a partir do mesmo envolvendo a criação de pacotes (packages).

Um package é uma estrutura extremamente flexível, já que possibilita a conexão a qualquer banco de dados que suporte os padrões OleDb e/ou ADO.NET (incluindo nisto planilhas do Excel e arquivos do Access), além de fornecer alternativas bastante flexíveis para a manipulação de arquivos-texto.

A implementação de estruturas baseadas em recursos do Integration Services acontece por meio do Visual Studio. Cada package criado contará com um conjunto de tarefas, as quais são modeladas seguindo um fluxo lógico de execução. A seguir são listados alguns dos tipos de tarefas de uso mais comum em projetos para o Integration Services:

Considerando agora a importação via Integration Services de dados que constem em arquivos do Excel, alguns cuidados precisarão ser tomados com o intuito de garantir que esta atividade ocorra sem erros no processamento de informações:

Não há como fugir destas recomendações para a importação de dados do Excel ao se empregar um componente do tipo Data Flow Task. No entanto, haverá situações (e isto não será tão incomum) em que se fará necessário processar planilhas que não estão em conformidade com essas diretrizes (cabeçalhos formados por várias linhas, células preenchidas em pontos que não deveriam conter informações etc.).

Quando isto acontecer, não restará aos desenvolvedores outra alternativa senão remover as células necessárias via componente Script Task, deixando assim as planilhas em uma disposição que possa ser compreendida por uma solução criada para o Integration Services. Se o arquivo em questão tiver a extensão .xls (formato do Excel 97-2003), será preciso então empregar os recursos de Interop na manipulação do mesmo.

Interop é um mecanismo que se baseia, dentro da plataforma .NET, na utilização de recursos da tecnologia COM para a comunicação entre softwares instalados no sistema operacional Windows e aplicações construídas a partir do Framework. Já o padrão COM (Component Object Model) foi criado pela própria Microsoft, tendo surgido inclusive antes do próprio .NET. O objetivo principal do mesmo é fornecer alternativas para a interoperabilidade entre sistemas, sendo seu uso bem comum em softwares que dependam de recursos oferecidos pelo pacote Office.

A Figura 1 apresenta um exemplo de planilha em que as células de cabeçalho precisariam ser removidas. Trata-se de um arquivo contendo informações de cargos e salários, o qual seria importado mensalmente numa aplicação hipotética responsável por cálculos de folha de pagamento.


Figura 1: Arquivo do Excel com cabeçalho que precisaria ser removido

Uma classe poderia então ser implementada a fim de acertar a planilha que foi demonstrada. Na Listagem 1 está o código-fonte para o tipo ArquivoExcel, sendo que o mesmo faz uso dos recursos de Interop disponibilizados pelo Microsoft Excel.

O construtor de ArquivoExcel recebe como informações o caminho do arquivo (parâmetro “arquivo”), além do nome da planilha que será acertada (parâmetro “nomePlanilhaMovimentacao”). Uma instância do aplicativo Microsoft Excel será disponibilizada em memória, com isto acontecendo através da criação de um objeto do tipo Application (namespace Microsoft.Office.Interop.Excel). Já o atributo de nome “_sheet” conterá uma referência para a classe Worksheet, a qual representa uma planilha dentro de um arquivo Excel.

É possível observar ainda em ArquivoExcel a presença dos seguintes métodos:

OBSERVAÇÃO IMPORTANTE: o objetivo deste artigo não é estabelecer uma discussão profunda acerca do uso de Excel via Interop, mas sim demonstrar um possível caso de uso do mesmo, destacando ainda ao final quais cuidados precisarão ser tomados.

Listagem 1: Classe ArquivoExcel

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Office.Interop.Excel; namespace TesteInterop.Utils.Excel { public class ArquivoExcel { private Application _app; private Workbooks _books; private Workbook _book; private Sheets _sheets; private Worksheet _sheet; public ArquivoExcel( string arquivo, string nomePlanilhaMovimentacao) { // Gera uma instância do Excel _app = new Application(); if (_book == null) { _books = _app.Workbooks; _book = _books.Open(arquivo, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); _sheets = _book.Worksheets; _sheet = (Worksheet)_sheets[1]; _sheet.Name = nomePlanilhaMovimentacao; } } public void EliminarLinha(int numeroLinha) { Range range = _sheet.get_Range( String.Format("A:A", numeroLinha.ToString()), Type.Missing); range.EntireRow.Delete(XlDeleteShiftDirection.xlShiftUp); } public void FecharArquivo() { // Fecha a edição do arquivo Excel _book.Save(); _book.Close(false, Type.Missing, Type.Missing); // Encerra a instância do Excel _app.Quit(); } } }

Já dentro do componente Script Task empregado no acerto da planilha existiria um método Main similar àquele que consta na Listagem 2. Importante lembrar que esta operação (Main) estará presente sempre que uma tarefa Script Task for empregada. No exemplo serão removidas as três primeiras linhas da planilha de cargos e salários, produzindo-se como resultado uma planilha como a que é demonstrada na Figura 2.

Listagem 2: Exemplo de método Main que utiliza a classe ArquivoExcel

public void Main() { string nomeArquivo; string nomePlanilha; // Outras operações ... ArquivoExcel arquivo = new ArquivoExcel( nomeArquivo, nomePlanilha); arquivo.EliminarLinha(1); arquivo.EliminarLinha(2); arquivo.EliminarLinha(3); arquivo.FecharArquivo(); Dts.TaskResult = (int)ScriptResults.Success; }

Figura 2: Arquivo do Excel após ajustes

A funcionalidade implementada na classe ArquivoExcel atende perfeitamente à necessidade de se formatar a planilha, deixando esta última estruturada numa forma que possa ser compreendida por um componente Data Flow Task do Integration Services. Entretanto, a execução sucessiva do package em que constar este tratamento acarretará em um efeito colateral: sucessivas instâncias do Microsoft Excel permanecerão em memória (Figura 3), não sendo descartadas ao término da execução do pacote.

Imaginando que a aplicação discutida fique hospedada em um servidor em que o package de importação é acionado diversas vezes, poderá se chegar a um momento no qual o uso de recursos do mesmo ficará comprometido (já que a memória empregada na manipulação de arquivos não é desalocada).


Figura 3: Instâncias do Excel não liberadas e que permaneceram em memória

A solução para este problema passará, forçosamente, pela utilização de chamadas a operações do Garbage Collector (GC), dispositivo este responsável pelo gerenciamento de memória em aplicações .NET. Na Listagem 3 está uma nova versão para a classe ArquivoExcel, com os ajustes necessários já realizados.

Merecem destaque nesta nova implementação do tipo ArquivoExcel os seguintes pontos:

Listagem 3: Classe ArquivoExcel após ajustes

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Office.Interop.Excel; namespace TesteInterop.Utils.Excel { public class ArquivoExcel : IDisposable { private Application _app; private Workbooks _books; private Workbook _book; private Sheets _sheets; private Worksheet _sheet; public ArquivoExcel( string arquivo, string nomePlanilhaMovimentacao) { // Gera uma instância do Excel _app = new Application(); if (_book == null) { _books = _app.Workbooks; _book = _books.Open(arquivo, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); _sheets = _book.Worksheets; _sheet = (Worksheet)_sheets[1]; _sheet.Name = nomePlanilhaMovimentacao; } } public void EliminarLinha(int numeroLinha) { Range range = _sheet.get_Range( String.Format("A:A", numeroLinha.ToString()), Type.Missing); range.EntireRow.Delete(XlDeleteShiftDirection.xlShiftUp); ReleaseComObject(range); range = null; } public void FecharArquivo() { // Fecha a edição do arquivo Excel _book.Save(); _book.Close(false, Type.Missing, Type.Missing); LiberarProcessoExcel(); } public void Dispose() { LiberarProcessoExcel(); } private void LiberarProcessoExcel() { // Libera objetos relativos ao arquivo que // está sendo manipulado ReleaseComObject(_sheet); _sheet = null; ReleaseComObject(_sheets); _sheets = null; ReleaseComObject(_book); _book = null; ReleaseComObject(_books); _books = null; // Encerra a instância do Excel _app.Quit(); ReleaseComObject(_app); _app = null; } private void ReleaseComObject(object o) { // Liberar instância do Interop if (o != null) System.Runtime.InteropServices .Marshal.ReleaseComObject(o); } } }

Por fim, o método Main do controle Script Task também necessita passar por uma readequação, conforme indicado na Listagem 4.

A manipulação da instância da classe ArquivoExcel está sendo feita dentro de um bloco try-finally. Ocorrendo ou não problemas, a instância que estaria associada à variável de nome “arquivo” é liberada (definindo-se null como valor da mesma). Em seguida são invocadas as seguintes operações da classe GC (a qual representa o Garbage Collector):


Listagem 4: Método Main após as adequações

public void Main() { ArquivoExcel arquivo = null; string nomeArquivo; string nomePlanilha; // Outras operações ... try { ArquivoExcel arquivo = new ArquivoExcel( nomeArquivo, nomePlanilha); arquivo.EliminarLinha(1); arquivo.EliminarLinha(2); arquivo.EliminarLinha(3); arquivo.FecharArquivo(); } finally { arquivo = null; // Libera objetos da memória e não mais em uso até este ponto System.GC.Collect(); System.GC.WaitForPendingFinalizers(); } Dts.TaskResult = (int)ScriptResults.Success; }

Este artigo procurou abordar uma situação bastante específica, mas que requer cuidados ao se implementar uma solução que dependa do uso de Interop dentro do Integration Services. Espero que esse conteúdo possa auxiliá-lo em cenários como o apresentado aqui. Até uma próxima oportunidade!

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados