Quantcast
Channel: Visual Studio – André Alves de Lima
Viewing all articles
Browse latest Browse all 89

Instalando o SQL Server junto com a aplicação

$
0
0

Finalmente, depois de meses desenvolvendo o seu aplicativo, você acabou o projeto. Você já testou todos os cenários que você conseguiu imaginar e o resultado foi positivo: tudo está funcionando perfeitamente. Pequeno detalhe: no seu computador. É nessa etapa que começa uma nova batalha: distribuir a sua aplicação.

Uns tempos atrás eu escrevi um artigo sobre instaladores para aplicativos .NET, onde eu abordo três opções de instaladores para aplicativos criados com o .NET Framework. Porém, e se a sua aplicação utilizar um banco de dados SQL Server e você quiser instalá-lo com a aplicação? Bom, se essa é a sua necessidade, você veio ao lugar certo. Hoje eu vou mostrar para você como ajustar a sua aplicação para que ela crie um banco de dados padrão para a sua aplicação na sua primeira execução. Depois, vou mostrar também como utilizar um script ninja do InnoSetup que instala o SQL Server Express junto com a sua aplicação.

A aplicação de exemplo

Para demonstrar a instalação e configuração do SQL Server junto com a aplicação, a primeira coisa que temos que fazer é criar uma aplicação que utilize o SQL Server. Dessa forma, vamos construir uma aplicação Windows Forms com um DataGridView mostrando as linhas de uma tabela de um banco de dados SQL Server. É importante salientar que a aplicação poderia ser de qualquer outro tipo (por exemplo, WPF ou Console Application). Só escolhi Windows Forms porque fica mais fácil de explicar.

Dito isso, crie um novo projeto do tipo Windows Forms Application e ajuste o formulário de forma que ele fique parecido com a imagem abaixo:

Note que a interface é extremamente simples: um botão (visualizarDadosButton) e um DataGridView (dadosDataGridView). No clique do botão, vamos carregar as informações de uma tabela do banco de dados “ExemploInstalador“, instância “SQLEXPRESS“:

        private void visualizarDadosButton_Click(object sender, EventArgs e)
        {
            try
            {
                using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=.\SQLEXPRESS;Database=ExemploInstalador;Trusted_Connection=True;"))
                {
                    conn.Open();
                    using (var comm = conn.CreateCommand())
                    {
                        comm.CommandText = "SELECT * FROM ExemploTabela";
                        using (var reader = comm.ExecuteReader())
                        {
                            var dataTable = new System.Data.DataTable();
                            dataTable.Load(reader);
                            dadosDataGridView.DataSource = dataTable;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Atenção: eu poderia (ou deveria) ter armazenado a string de conexão em um arquivo de configuração. Só fiz dessa forma para simplificar o exemplo e focar no que realmente interessa (o acesso ao banco de dados e criação do banco caso ainda não exista). Além disso, ao invés de utilizar ADO.NET puro, eu poderia ter utilizado algum ORM. O fato é: esses detalhes não fazem a mínima diferença para a essência desse artigo, portanto, resolvi utilizar a maneira mais simples possível para que todo mundo consiga acompanhar.

Se você, de fato, tiver uma instância chamada “SQLEXPRESS“, com um banco de dados chamado “ExemploInstalador” que tenha uma tabela chamada “ExemploTabela“, a aplicação se comportará dessa forma ao clicarmos no botão:

Porém, se você não tiver essa instância ou esse banco de dados ou essa tabela, a aplicação obviamente não funcionará.

O banco de dados de exemplo

O banco de dados que eu utilizei nesse exemplo é absurdamente simples: ele contém somente uma tabela, chamada “ExemploTabela“. Para conseguirmos fazer o processo automático de criação do banco de dados, a ideia que eu tive foi gerar um backup com o banco de dados “padrão” para servir de template. Quando o aplicativo for executado, verificaremos se o banco de dados ainda não existe e restauraremos esse backup caso necessário.

Obviamente você pode utilizar o seu próprio banco de dados, mas, se você quiser testar exatamente esse exemplo, crie uma nova database no seu SQL Server e dê o nome de “ExemploInstalador“. Nessa nova database, execute o seguinte script para criar a tabela “ExemploTabela“:

CREATE TABLE ExemploTabela
(
	ID INT IDENTITY(1,1) NOT NULL,
	Descricao VARCHAR(100) NOT NULL,
	CONSTRAINT PK_ExemploTabela PRIMARY KEY (ID ASC)
)

Feito isso, crie um backup completo desse banco de dados, dando o nome de “DBTemplate.bak” ao arquivo de backup. Copie o arquivo de backup para a pasta do seu projeto e adicione-o ao projeto utilizando a opção “Add” => “Existing Item” no Visual Studio:

O próximo passo é alterar esse arquivo de backup no Visual Studio de forma que ele sempre seja copiado para o diretório de compilação. Para isso, vá até o Solution Explorer, clique no arquivo de backup e, na janela de propriedades, altere a opção “Copy to Output Directory” para “Copy always“:

Nota: é importante que você gere o arquivo de backup com uma versão idêntica (ou inferior) do SQL Server que deverá ser instalado no computador cliente. Para esse exemplo, eu gerei o arquivo de backup com uma instância do SQL Server 2008 R2. Caso você queira, você pode fazer o download do meu arquivo de backup aqui (clique com o botão direito escolha a opção “Salvar como”).

Detectando se o banco de dados já existe

Até aqui, tudo muito simples, não é mesmo? Temos uma aplicação que tem um grid que lista as linhas da tabela “ExemploTabela” ao clicarmos em um botão. Já vimos como criar o banco de dados e fizemos um backup que servirá de template caso a database “ExemploInstalador” ainda não exista no SQL Server do usuário. Estamos levando em conta que o instalador irá fazer a instalação automática do SQL Server (veremos como fazer isso com o InnoSetup na seção final deste artigo). Porém, como é que podemos fazer essa checagem se o banco de dados já existe ou não? Simples! É só fazer uma query utilizando a consulta “SYS.DATABASES“, procurando pelo banco de dados chamado “ExemploInstalador“:

SELECT 1 FROM SYS.DATABASES WHERE NAME LIKE 'ExemploInstalador'

Se essa sentença retornar “1“, quer dizer que o banco já existe. Se ela retornar nulo, quer dizer que o banco ainda não existe. Dessa forma, podemos criar um método na nossa aplicação que faça essa checagem e retorne verdadeiro caso o banco de dados exista ou falso case o banco de dados não exista:

        private bool VerificaSeBancoJaExiste()
        {
            bool retorno = false;

            try
            {
                using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=.\SQLEXPRESS;Database=master;Trusted_Connection=True;"))
                {
                    conn.Open();
                    using (var comm = conn.CreateCommand())
                    {
                        comm.CommandText = "SELECT 1 FROM SYS.DATABASES WHERE NAME LIKE 'ExemploInstalador'";
                        var valor = comm.ExecuteScalar();

                        if (valor != null && valor != DBNull.Value && Convert.ToInt32(valor).Equals(1))
                        {
                            retorno = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            return retorno;
        }

Restaurando o template caso o ainda banco não exista

Agora que já temos um método que verifica se o banco de dados existe ou não, vamos criar o método que restaura o template caso o banco de dados não exista. Porém, antes de fazermos isso, temos que copiar o arquivo de backup que geramos anteriormente (“DBTemplate.bak“) para o diretório de backup do SQL Server. E como descobrimos qual é o diretório de backup do SQL Server? Utilizando SMO (SQL Server Management Objects)! Se você não sabe o que é SMO, dê uma olhada no meu artigo sobre criação e restauração de backups do SQL Server através do C#, onde eu utilizo o SMO para realizar essa tarefa.

Primeiramente, adicione as referências às dlls do SMO:

– Microsoft.SqlServer.ConnectionInfo
– Microsoft.SqlServer.Management.Sdk.Sfc
– Microsoft.SqlServer.Smo
– Microsoft.SqlServer.SmoExtended

Você encontra essas referências na seção “Extensions” da tela “Reference Manager“:

Feito isso, vamos adicionar o método que descobrirá e retornará os diretórios de backup, de dados e de log da instância do SQL Server:

        private void DescobrirDiretoriosPadrao(out string diretorioDados, out string diretorioLog, out string diretorioBackup)
        {
            using (var connection = new System.Data.SqlClient.SqlConnection(@"Server=.\SQLEXPRESS;Database=master;Trusted_Connection=True;"))
            {
                var serverConnection = new Microsoft.SqlServer.Management.Common.ServerConnection(connection);
                var server = new Microsoft.SqlServer.Management.Smo.Server(serverConnection);
                diretorioDados = !string.IsNullOrWhiteSpace(server.Settings.DefaultFile) ? server.Settings.DefaultFile : (!string.IsNullOrWhiteSpace(server.DefaultFile) ? server.DefaultFile : server.MasterDBPath);
                diretorioLog = !string.IsNullOrWhiteSpace(server.Settings.DefaultLog) ? server.Settings.DefaultLog : (!string.IsNullOrWhiteSpace(server.DefaultLog) ? server.DefaultLog : server.MasterDBLogPath);
                diretorioBackup = !string.IsNullOrWhiteSpace(server.Settings.BackupDirectory) ? server.Settings.BackupDirectory : server.BackupDirectory;
            }
        }

Note que é muito fácil descobrirmos esses diretórios com o SMO: eles estarão definidos na propriedade server.Settings.DefaultFile ou server.DefaultFile ou server.MasterDBPath (ou Default”Log” para o diretório de log e BackupDirectory para o diretório de backup).

Com os diretórios de dados, log e backup em mãos, podemos simplesmente copiar o arquivo “DBTemplate.bak” para o diretório de backup e executarmos um comando “RESTORE DATABASE” para criar o banco de dados utilizando o template:

        private void RestaurarDBPadrao()
        {
            try
            {
                string diretorioDados, diretorioLog, diretorioBackup;
                DescobrirDiretoriosPadrao(out diretorioDados, out diretorioLog, out diretorioBackup);

                using (var conn = new System.Data.SqlClient.SqlConnection(@"Server=.\SQLEXPRESS;Database=master;Trusted_Connection=True;"))
                {
                    conn.Open();
                    using (var comm = conn.CreateCommand())
                    {
                        var caminhoCompletoBackup = System.IO.Path.Combine(diretorioBackup, "DBTemplate.bak");
                        var caminhoCompletoDados = System.IO.Path.Combine(diretorioDados, "ExemploInstalador.mdf");
                        var caminhoCompletoLog = System.IO.Path.Combine(diretorioLog, "ExemploInstalador_Log.ldf");
                        System.IO.File.Copy("DBTemplate.bak", caminhoCompletoBackup, true);
                        comm.CommandText =
                            @"RESTORE DATABASE ExemploInstalador " +
                            @"FROM DISK = N'" + caminhoCompletoBackup + "' " +
                            @"WITH FILE = 1, " +
                            @"MOVE N'ExemploInstalador' TO N'" + caminhoCompletoDados + "', " +
                            @"MOVE N'ExemploInstalador_LOG' TO N'" + caminhoCompletoLog + "', " +
                            @"NOUNLOAD, REPLACE, STATS = 10";
                        comm.ExecuteNonQuery();
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

Finalmente, vamos criar um método que faz a checagem se o banco de dados existe ou não e, caso ele não exista, nós chamamos o método que faz a criação do banco de dados:

        public FormMain()
        {
            InitializeComponent();
            RestaurarDBPadraoCasoNaoExista();
        }

        private void RestaurarDBPadraoCasoNaoExista()
        {
            try
            {
                var bancoExiste = VerificaSeBancoJaExiste();

                if (!bancoExiste)
                {
                    RestaurarDBPadrao();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

E com isso, o template do banco de dados será restaurado automaticamente caso o banco de dados “ExemploInstalador” não exista.

Uma única observação sobre esse código é que ele só funcionará caso seja rodado como administrador, uma vez que precisamos de acesso de escrita em pastas localizadas dentro do diretório “Arquivos de Programas” (que é onde as pastas do SQL Server normalmente ficam). Para que o Windows peça permissão de administrador automaticamente, adicione um arquivo de manifesto à sua aplicação e altere o “level” da tag “requestedExecutionLevel” para “requireAdministrator“:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

Nota: você pode adaptar esse exemplo para que o método verifique a existência do seu próprio banco de dados (e não o banco de dados “ExemploInstalador”).

Nota 2: para simplificar, implementei todo o código no formulário. Porém, isso não é o mais indicado. Separe esse código em uma classe e utilize essa classe para fazer a verificação. De preferência, separe esse código da verificação em um outro executável e configure o instalador a executá-lo após a instalação. Como esse código precisa de acesso à pasta “Arquivos de Programas” (onde os diretórios do SQL Server normalmente estão localizados por padrão), ele só funcionará caso seja executado como administrador.

Instalando o SQL Server junto com a aplicação

Agora que você já viu como criar uma aplicação que restaura automaticamente o banco de dados caso ele ainda não exista, vamos aprender como gerar um instalador que faça a instalação do SQL Server Express de forma automática e silenciosa. Para isso, eu escolhi o gerador de instaladores InnoSetup.

Procurando na Internet, encontrei este artigo no CodeProject: Modular InnoSetup Dependency Installer. Parecia exatamente o que eu estava procurando. Com esse esquema, conseguimos ativar e desativar dependências relacionadas ao nosso aplicativo e, para cada dependência ativada, uma checagem é feita para ver se ela já está instalada ou não. Caso ainda não esteja instalada, o download é automaticamente realizado e a dependência é instalada. Uma das dependências que ele suporta é o SQL Server 2008 R2 Express. Mágico, não? Isso é, se funcionasse.

Na minha primeira tentativa com esse script, a instalação do SQL Server falhava. Pesquisando bastante e fazendo vários testes, consegui encontrar o problema presente nesse script. Consertei esse problema e, finalmente, tenho uma versão do instalador funcionando perfeitamente. Você quer aprender? Então, vamos lá!

Primeiramente, instale o InnoSetup (se você tiver alguma dúvida nesse processo, dê uma olhada no meu artigo sobre instaladores para aplicativos .NET). Depois, faça o download dos scripts do CodeProject ou, se preferir, baixe-os diretamente através deste link. Descompacte o conteúdo deste pacote em uma pasta no seu sistema de arquivos e abra o arquivo “setup.iss” no InnoSetup.

Logo no começo do script, note que você encontra várias linhas com “#define use_xxx“. Cada uma dessas linhas representa uma dependência que será checada durante a instalação:

No nosso caso, vamos deixar somente as dependências do .NET Framework 4 (use_dotnetfx40) e do SQL Server 2008 R2 Express (use_sql2008express) habilitadas. Comente todas as outras dependências utilizando um ponto-e-vírgula no começo da linha:

;comment out product defines to disable installing them
;#define use_iis
;#define use_kb835732

;#define use_msi20
;#define use_msi31
;#define use_msi45

;#define use_ie6

;#define use_dotnetfx11
;#define use_dotnetfx11lp

;#define use_dotnetfx20
;#define use_dotnetfx20lp

;#define use_dotnetfx35
;#define use_dotnetfx35lp

#define use_dotnetfx40
;#define use_wic

;#define use_dotnetfx46

;#define use_msiproduct
;#define use_vc2005
;#define use_vc2008
;#define use_vc2010
;#define use_vc2012
;#define use_vc2013
;#define use_vc2015

;#define use_mdac28
;#define use_jet4sp8

;#define use_sqlcompact35sp2

;#define use_sql2005express
#define use_sql2008express

Feito isso, altere a definição de MyAppSetupName, MyAppVersion, AppCopyright, VersionInfoCompany, AppPublisher e UninstallDisplayIcon de forma que elas representem corretamente as propriedades do seu aplicativo. Por exemplo, eu alterei o nome da aplicação para “Exemplo Instalador“, versão “0.1” e nome da empresa “abc-dev“:

#define MyAppSetupName 'Exemplo Instalador'
#define MyAppVersion '0.1'

[Setup]
AppName={#MyAppSetupName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppSetupName} {#MyAppVersion}
AppCopyright=Copyright © 2007-2015 abc-dev
VersionInfoVersion={#MyAppVersion}
VersionInfoCompany=abc-dev
AppPublisher=abc-dev
;AppPublisherURL=http://...
;AppSupportURL=http://...
;AppUpdatesURL=http://...
OutputBaseFilename={#MyAppSetupName}-{#MyAppVersion}
DefaultGroupName={#MyAppSetupName}
DefaultDirName={pf}\{#MyAppSetupName}
UninstallDisplayIcon={app}\AppInstaladorDB.exe

Em seguida, precisamos configurar as tags “Files“, “Icons” e “Run“, de forma que elas apontem para o executável do nosso aplicativo. Porém, antes disso, precisamos compilar o nosso aplicativo e mover os arquivos necessários para dentro da pasta “src” do instalador:

Além do executável da aplicação, temos que copiar também o arquivo de template do banco de dados (DBTemplate.bak) e as dlls necessárias pelas classes do SMO (que eu listei anteriormente na seção “Restaurando o template caso o banco ainda não exista“). Você encontra a maioria das dlls do SMO dentro da pasta “C:\Program Files (x86)\Microsoft SQL Server\120\SDK\Assemblies\“. Somente uma delas (Microsoft.SqlServer.SqlClrProvider.dll) fica dentro do GAC. Veja neste link como conseguir copiar essa dll do GAC para outra pasta. Para facilitar a sua vida, eu compactei as dlls do SMO e estou disponibilizando para que você faça o download através deste link.

Uma vez copiados o executável (no meu caso o executável se chama “AppInstaladorDB.exe”), o template do banco e todas as dlls necessárias à aplicação, a sua pasta “src” deve ficar parecida com a screenshot abaixo:

Agora que já copiamos os arquivos para a pasta “src“, precisamos ajustar as tags “Files“, “Icons” e “Run” no arquivo “setup.iss” para que elas considerem esses itens:

[Files]
Source: "src\AppInstaladorDB.exe"; DestDir: "{app}"; DestName: "AppInstaladorDB.exe";
Source: "src\DBTemplate.bak"; DestDir: "{app}"; DestName: "DBTemplate.bak";
Source: "src\Microsoft.SqlServer.ConnectionInfo.dll"; DestDir: "{app}"; DestName: "Microsoft.SqlServer.ConnectionInfo.dll";
Source: "src\Microsoft.SqlServer.Management.Sdk.Sfc.dll"; DestDir: "{app}"; DestName: "Microsoft.SqlServer.Management.Sdk.Sfc.dll";
Source: "src\Microsoft.SqlServer.Smo.dll"; DestDir: "{app}"; DestName: "Microsoft.SqlServer.Smo.dll";
Source: "src\Microsoft.SqlServer.SqlClrProvider.dll"; DestDir: "{app}"; DestName: "Microsoft.SqlServer.SqlClrProvider.dll";
Source: "src\Microsoft.SqlServer.SqlEnum.dll"; DestDir: "{app}"; DestName: "Microsoft.SqlServer.SqlEnum.dll";

[Icons]
Name: "{group}\{#MyAppSetupName}"; Filename: "{app}\AppInstaladorDB.exe"
Name: "{group}\{cm:UninstallProgram,{#MyAppSetupName}}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#MyAppSetupName}"; Filename: "{app}\AppInstaladorDB.exe"; Tasks: desktopicon
Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppSetupName}"; Filename: "{app}\AppInstaladorDB.exe"; Tasks: quicklaunchicon

[Run]
Filename: "{app}\AppInstaladorDB.exe"; Description: "{cm:LaunchProgram,{#MyAppSetupName}}"; Flags: nowait postinstall skipifsilent

Finalmente, lembra que eu falei que o script para instalar o SQL Server Express não estava funcionando? Pois bem, precisamos consertá-lo! Para isso, abra o arquivo “sql2008express.iss” que se encontra dentro da pasta “scripts/products” do instalador e altere a linha da instalação do SQL Server (originalmente linha 33 do arquivo “sql2008express.iss“). Aqui vai a linha original do exemplo:

'/QS  /IACCEPTSQLSERVERLICENSETERMS /ACTION=Install /FEATURES=All /INSTANCENAME=SQLEXPRESS /SQLSVCACCOUNT="NT AUTHORITY\Network Service" /SQLSYSADMINACCOUNTS="builtin\administrators"',

Você precisa alterá-la para que ela fique assim:

'/QS  /IACCEPTSQLSERVERLICENSETERMS /ACTION=Install /FEATURES=SQL,Tools /INSTANCENAME=SQLEXPRESS /SQLSVCACCOUNT="NT AUTHORITY\Network Service" /SQLSYSADMINACCOUNTS="builtin\administrators"',

Veja que alteramos de “/FEATURES=All” para “/FEATURES=SQL,Tools” (uma vez que esse parâmetro foi alterado no SQL Server 2008 R2 e não aceita mais o termo “All“).

Nota: a instalação do SQL Server (tanto manual como automática via linha de comando) não funciona se o nome do usuário for exatamente igual ao nome do computador. Se algum erro acontecer durante a instalação do SQL Server com esse instalador, atente-se para esse cenário problemático!

Tendo feitas essas alterações, salve todos os arquivos e compile o projeto de setup (no InnoSetup, menu “Build => Compile“). O instalador será criado dentro da pasta “bin“:

Ao executar esse instalador em um sistema que ainda não tenha o SQL Server 2008 R2 Express instalado, o instalador fará o download automaticamente e instalará para você! Fantástico, não?

Concluindo

A criação de instaladores é muitas vezes deixada de lado e feita “de qualquer jeito” pelos desenvolvedores. Na minha opinião, isso é um tremendo erro. A primeira impressão que o usuário tem com a sua aplicação é a experiência de instalação. Se a sua aplicação utiliza o banco de dados SQL Server, por favor, nem pense em fazer com que o usuário tenha que instalá-lo manualmente!

O exemplo apresentado nesse artigo mostra como fazer a instalação automática do SQL Server Express durante a instalação do seu aplicativo com o InnoSetup. Além disso, quando o aplicativo é executado, fazemos uma checagem para ver se o banco ainda não existe. Caso ele ainda não exista, nós criamos esse banco utilizando um template salvo em forma de backup.

Acredito que esse exemplo fará com que a experiência de instalação dos seus aplicativos melhore exponencialmente. Fico no aguardo para que você experimente essa sistemática no seu próximo instalador e me conte nos comentários como foi a experiência.

Por fim, convido você a inscrever-se na minha newsletter. Ao fazer isso, você receberá um e-mail toda semana sobre o artigo publicado e ficará sabendo também em primeira mão sobre o artigo da próxima semana, além de receber dicas “bônus” que eu só compartilho por e-mail. Inscreva-se utilizando o formulário logo abaixo.

Até a próxima!

André Lima

The post Instalando o SQL Server junto com a aplicação appeared first on André Alves de Lima.


Viewing all articles
Browse latest Browse all 89