André Alves de Lima

Talking about Software Development and more…

Trabalhando com SQLite no C# e VB.NET

Você sabe as diferenças entre os bancos de dados locais SQLite, SQL Compact (CE) e LocalDb? E você sabe como utilizar cada um desses bancos de dados na sua aplicação? Um dos leitores desse site me fez justamente essa pergunta, que eu não sabia responder de “bate-pronto“, então, fui pesquisar. Depois de finalizar a minha pesquisa, cheguei à conclusão que ficaria impraticável escrever somente um artigo mostrando as diferenças de cada um desses bancos locais, além de dar exemplos de como utilizá-los. É um assunto muito extenso. Portanto, resolvi escrever uma série, primeiramente mostrando como utilizá-los e, por fim, um artigo mostrando as diferenças entre eles. Nesse primeiro artigo você vai ver como utilizar o SQLite no C# e VB.NET.

Criando um banco de dados SQLite

Uma das grandes vantagens dos bancos de dados locais é que eles são facilmente transportados e distribuídos com a aplicação. Normalmente, com esse tipo de banco de dados, tudo fica armazenado em um arquivo único, sem a necessidade de um servidor rodando por trás dele. Com o SQLite, toda a estrutura das tabelas, bem como os seus dados, ficam armazenados em um arquivo de extensão “.db“. Existem duas opções para criarmos os nossos bancos de dados do SQLite: via código ou utilizando ferramentas de administração.

Uma vez criado um novo arquivo com a extensão “.db“, a inicialização das tabelas via código pode ser feita via ADO.NET puro (com comandos do tipo “CREATE TABLE“) ou através do Entity Framework Code First (onde você tem as classes de domínio e o Entity Framework cria as tabelas automaticamente para você). No final desse artigo veremos como podemos utilizar o Code First com o SQLite (que não funciona “por padrão“). Não mostrarei a criação das tabelas via comandos SQL porque esse não é um cenário muito comum. Além disso, uma vez que você saiba utilizar o SQLite com o ADO.NET (que eu vou mostrar no artigo), você pode simplesmente adaptar o código para executar comandos “CREATE TABLE“.

Ao invés de mostrar a criação do banco via comandos SQL, resolvi mostrar uma ferramenta de administração de bancos SQLite que é bastante famosa: o DB Browser for SQLite. Essa ferramenta é open source e sua interface é bem simples e eficiente.

Uma vez instalado o DB Browser, ao abrir a aplicação, temos duas opções: criar um novo banco de dados ou abrir um banco de dados já existente. No nosso caso, como nós ainda não temos nenhum banco, vamos criar um novo arquivo, clicando na opção “New Database“:

Por padrão, ao criarmos um novo banco com o DB Browser, ele mostrará uma janela para criarmos a nossa primeira tabela. Poderíamos cancelar essa tela, mas, como realmente precisamos de uma tabela para fazer os nossos testes, vamos aproveitar para cria-la agora mesmo. Dê o nome de “Cliente” para a tabela sendo criada e adicione duas colunas: “Id” (chave primária, auto incremento) e “Nome” (texto, não nulo), conforme apresentado na imagem abaixo:

Note que, mesmo depois de termos criado a tabela, ela ainda está armazenada somente em memória. Para realmente salvarmos as alterações no arquivo, temos que clicar no botão “Write Changes“:

Como qualquer outro banco de dados, no SQLite podemos criar diversas tabelas e relacionamentos (através de “foreign keys“). O SQLite suporta até mesmo triggers (apesar de eu não gostar delas). Para não complicar muito o nosso exemplo, vamos trabalhar somente com essa única tabela.

Você pode conferir o conteúdo de cada tabela dentro da aba “Browse Data” do DB Browser. Obviamente, como acabamos de criar a tabela, ela estará vazia:

Adicionando as bibliotecas do SQLite via NuGet

Agora que já temos o nosso banco de dados criado, vamos conferir como podemos utilizá-lo nas nossas aplicações .NET. Para simplificar, vamos criar um novo projeto do tipo “Console Application“. Poderíamos baixar as dlls do SQLite diretamente no site oficial, porém, existe uma maneira muito mais simples de adicionarmos o SQLite no nosso projeto: através do NuGet!

Para isso, abra o Package Manager Console ou a janela de administração dos pacotes do NuGet, faça uma busca por SQLite e instale o pacote “System.Data.SQLite“:

Nota: caso você não saiba como o NuGet funciona, confira este artigo que eu escrevi sobre ele.

No meu caso, devido a algum problema na minha instalação do Visual Studio, eu não estava conseguindo instalar esse pacote. O Package Manager Console sempre me apresentava esse erro esquisito:

Depois de pesquisar bastante, eu encontrei esta thread no StackOverflow, sugerindo que eu apagasse o arquivo Nuget.config da pasta AppData. Entretanto, essa solução não funcionou. Por sorte, acabei encontrando essa issue no GitHub do NuGet, onde uma pessoa sugeriu limpar o cache através da ferramenta de linha de comando do NuGet. E foi isso que acabou resolvendo o meu problema:

Numa outra oportunidade eu escrevo um artigo contando em detalhes esse problema que eu tive com o NuGet. Se você estiver passando pelo mesmo problema e não conseguir resolver, entre em contato comigo que eu tento te ajudar.

Acessando um banco SQLite com ADO.NET puro

Após instalarmos a biblioteca do SQLite no nosso projeto, acessar um banco de dados com ADO.NET puro é muito simples. Dentro do namespace System.Data.SQLite temos as classes usuais do ADO.NET, que são as implementações de DbConnection, DbCommand, DbDataReader, DbDataAdapter, etc. Com essas classes, conseguimos criar uma conexão com o banco para executarmos comandos SQL que, ou farão alguma alteração nos dados do banco, ou retornarão um conjunto de dados baseado em uma consulta.

Para exemplificarmos a utilização das classes ADO.NET do SQLite, vamos fazer as operações CRUD básicas (Create, Read, Update, Delete). Primeiramente, temos que abrir uma conexão passando a connection string. Você encontra os mais diversos padrões de connection strings no site www.connectionstrings.com. A string de conexão padrão do SQLite é muito simples: “Data Source=CAMINHO_DO_ARQUIVO;“. No nosso caso, vamos supor que o arquivo que criamos anteriormente tenha o nome de “banco.db” e que ele esteja presente no diretório “bin/debug” do projeto:

            // C#
            using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=banco.db;"))
            {
                conn.Open();
            }
        ' VB.NET
        Using Conn As New System.Data.SQLite.SQLiteConnection("Data Source=banco.db;")
            Conn.Open()
        End Using

Em seguida, vamos executar um comando para deletar todos os registros da tabela “Cliente“:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using

Agora que a tabela está vazia, vamos criar um novo registro contendo o nome “Novo Cliente“:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using

Até aqui, tudo bem tranquilo, não é mesmo? Só utilizamos uma instância de SQLiteCommand e chamamos o método ExecuteNonQuery. Agora, como é que fazemos para atualizar o nome desse cliente que acabamos de cadastrar? Primeiro temos que pegar o seu “Id” utilizando o método ExecuteScalar e depois efetuamos um comando de UPDATE, só que agora utilizando a funcionalidade de parâmetros:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id";
                        comm.Parameters.AddWithValue("@Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id"
                    Comm.Parameters.AddWithValue("@Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using

Nota: se você não utiliza parâmetros nos seus comandos ADO.NET, pare agora mesmo com isso. Você corre vários riscos ao concatenar valores nas suas consultas. Veja este artigo onde eu expliquei as implicações disso, além de uma demonstração de como utilizar parâmetros com o ADO.NET.

Por fim, só ficou faltando a parte “R” do CRUD, que significa “Read“, ou seja, leitura. Como é que fazemos para retornar todos os registros da tabela de clientes para listarmos os seus nomes? Fácil: é só utilizar um comando SELECT. Porém, para iterarmos nos resultados, temos duas opções: a primeira delas é utilizarmos um DataReader e a segunda opção é preenchermos uma DataTable utilizando um DataAdapter. Veja a diferença no trecho de código abaixo:

                // C#
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new System.Data.SQLite.SQLiteDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            ' VB.NET
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New System.Data.SQLite.SQLiteDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using

Pronto! Com isso nós vimos todas as operações de criação, leitura, atualização e remoção de registros com o SQLite utilizando ADO.NET puro. Veja só como ficou o código completo:

            // C#
            using (var conn = new System.Data.SQLite.SQLiteConnection("Data Source=banco.db;"))
            {
                conn.Open();

                // DELETE
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "DELETE FROM Cliente";
                    comm.ExecuteNonQuery();
                }
                // INSERT
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')";
                    comm.ExecuteNonQuery();
                }
                // UPDATE
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT MAX(Id) FROM Cliente";
                    var clienteId = comm.ExecuteScalar();
                    if (clienteId != null && clienteId != DBNull.Value)
                    {
                        comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id";
                        comm.Parameters.AddWithValue("@Id", clienteId);
                        comm.ExecuteNonQuery();
                    }
                }
                // SELECT
                using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
                {
                    comm.CommandText = "SELECT * FROM Cliente";

                    Console.WriteLine("DataReader:");
                    using (var reader = comm.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Console.WriteLine("Nome do Cliente: {0}", reader["Nome"]);
                        }
                    }

                    Console.WriteLine("DataAdapter:");
                    var adapter = new System.Data.SQLite.SQLiteDataAdapter(comm);
                    var dataTable = new System.Data.DataTable();
                    adapter.Fill(dataTable);
                    foreach (System.Data.DataRow row in dataTable.Rows)
                    {
                        Console.WriteLine("Nome do Cliente: {0}", row["Nome"]);
                    }
                }
            }
        ' VB.NET
        Using Conn As New System.Data.SQLite.SQLiteConnection("Data Source=banco.db;")
            Conn.Open()

            ' DELETE
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "DELETE FROM Cliente"
                Comm.ExecuteNonQuery()
            End Using
            ' INSERT
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "INSERT INTO Cliente (Nome) VALUES ('Novo Cliente')"
                Comm.ExecuteNonQuery()
            End Using
            ' UPDATE
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT MAX(Id) FROM Cliente"
                Dim ClienteId = Comm.ExecuteScalar()
                If (ClienteId IsNot Nothing And ClienteId IsNot DBNull.Value) Then
                    Comm.CommandText = "UPDATE Cliente SET Nome = 'Novo Cliente Alterado' WHERE Id = @Id"
                    Comm.Parameters.AddWithValue("@Id", ClienteId)
                    Comm.ExecuteNonQuery()
                End If
            End Using
            ' SELECT
            Using Comm As New System.Data.SQLite.SQLiteCommand(Conn)
                Comm.CommandText = "SELECT * FROM Cliente"

                Console.WriteLine("DataReader:")
                Using Reader = Comm.ExecuteReader()
                    While Reader.Read()
                        Console.WriteLine("Nome do Cliente: {0}", Reader("Nome"))
                    End While
                End Using

                Console.WriteLine("DataAdapter:")
                Dim Adapter As New System.Data.SQLite.SQLiteDataAdapter(Comm)
                Dim DataTable As New System.Data.DataTable()
                Adapter.Fill(DataTable)
                For Each Row As DataRow In DataTable.Rows
                    Console.WriteLine("Nome do Cliente: {0}", Row("Nome"))
                Next
            End Using
        End Using

Execute a aplicação e abra o banco no DB Browser para ver o novo registro criado na tabela de Clientes.

Acessando um banco SQLite com Entity Framework

Na seção anterior nós vimos como é simples acessarmos o SQLite através do ADO.NET puro. Mas, e se utilizamos o Entity Framework nos nossos projetos? Como é que fica a questão do SQLite? Sem problema algum! Quando adicionamos a biblioteca do SQLite pelo NuGet, ele já adiciona o suporte ao Entity Framework. Tem alguns truquezinhos para que tudo funcione corretamente, mas, não se preocupe. É isso que eu vou mostrar para você agora.

Antes de tudo, temos que criar uma nova classe que representará a nossa entidade (Cliente). Essa classe será bem simples, conforme você pode conferir abaixo:

    // C#
    public class Cliente
    {
        public long Id { get; set; }
        public string Nome { get; set; }
    }
' VB.NET
Public Class Cliente
    Public Property Id As Long
    Public Property Nome As String
End Class

Atenção! Note que o tipo do campo “Id” deve ser “long”, e não “int”. Isso se deve ao fato que todas as colunas INTEGER no SQLite são consideradas como Int64 pelo Entity Framework. Portanto, precisamos sempre utilizar o tipo “long” para evitarmos problemas.

A segunda coisa que precisamos adicionar ao trabalharmos com o Entity Framework é o contexto. Adicione uma nova classe no projeto, dando o nome de “EfContext“. Essa classe tem que herdar do DbContext do Entity Framework e, dentro dela, temos que criar um DbSet de Clientes:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)
End Class

Não sei se você está acostumado a utilizar o Entity Framework, mas, existem algumas maneiras de passarmos a string de conexão para o contexto. Para simplificar as coisas, a metodologia que eu vou utilizar neste artigo é a especificação da string de conexão diretamente no arquivo app.config.

Como o banco estará armazenado no mesmo diretório da aplicação, temos que adicionar o seguinte item dentro da tag “configuration” do arquivo app.config:

  <connectionStrings>
    <add name="EfContext" connectionString="Data Source=.\banco.db;" providerName="System.Data.SQLite.EF6" />
  </connectionStrings>

Note que o Entity Framework só conseguirá encontrar a nossa string de conexão se ela tiver exatamente o mesmo nome da nossa classe de contexto (“EfContext“).

Uma vez criada a classe de contexto, vamos utilizá-la para fazermos as operações CRUD na tabela de Clientes. A primeira coisa que temos que fazer é criarmos uma instância do contexto:

            // C#
            using (var contexto = new EfContext())
            {

            }
        ' VB.NET
        Using Contexto = New EfContext()

        End Using

Em seguida, como fizemos anteriormente com o ADO.NET puro, vamos deletar todos os clientes da tabela de clientes:

                // C#
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();
            ' VB.NET
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

E aí começam os problemas do SQLite com o Entity Framework. Tente executar a aplicação com o trecho de código acima e receba esse belo erro:

Unable to determine the provider name for provider factory of type ‘System.Data.SQLite.SQLiteFactory’. Make sure that the ADO.NET provider is installed or registered in the application config.

Esse erro acontece porque o arquivo app.config não é devidamente alterado para que consigamos utilizar o SQLite com o Entity Framework. Ao adicionarmos o SQLite ao nosso projeto, ele até faz algumas alterações referentes à utilização do SQLite com o Entity Framework, mas, infelizmente, algumas configurações ficam faltando. Para que consigamos fazer o SQLite funcionar com o Entity Framework, temos que adicionar três chaves no arquivo app.config:

E tome cuidado porque essas chaves precisam estar exatamente nessa ordem. Dependendo da ordem que você as colocar, o projeto continuará não funcionando.

Veja como deve ficar a chave “entityframework/providers” do seu app.config:

    <providers>
      <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
      <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
      <provider invariantName="System.Data.SQLite" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
    </providers>

E aqui temos a chave “system.data/DbProviderFactories“:

      <DbProviderFactories>
        <remove invariant="System.Data.SQLite" />
        <remove invariant="System.Data.SQLite.EF6" />
        <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".NET Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
        <add name="SQLite Data Provider (Entity Framework 6)" invariant="System.Data.SQLite.EF6" description=".NET Framework Data Provider for SQLite (Entity Framework 6)" type="System.Data.SQLite.EF6.SQLiteProviderFactory, System.Data.SQLite.EF6" />    
      </DbProviderFactories>

Pronto. Com essas alterações nós conseguimos resolver o primeiro problema, ou seja, agora o Entity Framework conseguirá encontrar as informações necessárias para utilizarmos o SQLite. Porém, ao tentarmos executar o projeto novamente, receberemos um segundo erro:

Ao abrirmos os detalhes da Inner Exception, fica claro o motivo de estarmos recebendo esse erro:

O Entity Framework não está conseguindo encontrar a tabela chamada “Clientes“. Mas, a nossa tabela se chama “Cliente“, por que é que o Entity Framework está considerando “Clientes“? Simples: por padrão, o Entity Framework pluraliza os nomes das tabelas. Dessa forma, mesmo tendo dado o nome de “Cliente” para o DbSet, o Entity Framework pluralizará e considerará “Clientes” no seu lugar.

Para desativarmos a pluralização do Entity Framework, temos que fazer um “override” no método “OnModelCreating” do nosso contexto. Dentro desse método, nós encontramos e removemos a convenção da pluralização do modelo:

    // C#
    public class EfContext : System.Data.Entity.DbContext
    {
        public System.Data.Entity.DbSet<Cliente> Cliente { get; set; }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
        }
    }
' VB.NET
Public Class EfContext
    Inherits System.Data.Entity.DbContext

    Public Property Cliente As System.Data.Entity.DbSet(Of Cliente)

    Protected Overrides Sub OnModelCreating(modelBuilder As Entity.DbModelBuilder)
        MyBase.OnModelCreating(modelBuilder)
        modelBuilder.Conventions.Remove(Of System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention)()
    End Sub
End Class

Agora sim, finalmente, ao executarmos a aplicação, não teremos nenhum erro e a tabela “Cliente” ficará vazia, pois estamos deletando todos os registros dela. Com isso, podemos continuar com a próxima etapa das nossas operações CRUD: a inserção de um novo cliente. O código nesse caso é muito simples. Basta adicionarmos uma nova instância da classe “Cliente” no nosso DbSet de Clientes e, logo em seguida, ao chamarmos o método “SaveChanges” um novo cliente será adicionado no banco de dados:

                // C#
                contexto.Cliente.Add(new Cliente() { Nome = "Novo Cliente EF" });
                contexto.SaveChanges();
            ' VB.NET
            Contexto.Cliente.Add(New Cliente() With {.Nome = "Novo Cliente EF"})
            Contexto.SaveChanges()

Em seguida, temos o código para alterarmos um cliente. Nesse caso, temos que recuperar o cliente do nosso DbSet, fazer as alterações desejadas e, por fim, temos que chamar o método “SaveChanges” para persistir as alterações (estou utilizando o método “First” porque só temos um cliente cadastrado – em um cenário real, teríamos que fazer uma pesquisa no DbSet através do “Id” do cliente):

                // C#
                var cliente = contexto.Cliente.First();
                cliente.Nome = "Novo Cliente EF Alterado";
                contexto.SaveChanges();
            ' VB.NET
            Dim Cliente = Contexto.Cliente.First()
            Cliente.Nome = "Novo Cliente EF Alterado"
            Contexto.SaveChanges()

Por fim, para listarmos as informações dos nossos clientes, basta percorrermos o DbSet com um laço “foreach“:

                // C#
                foreach (var c in contexto.Cliente)
                {
                    Console.WriteLine("Nome do Cliente: {0}", c.Nome);
                }
            ' VB.NET
            For Each C In Contexto.Cliente
                Console.WriteLine("Nome do Cliente: {0}", C.Nome)
            Next

Veja só como fica o código completo:

            // C#
            using (var contexto = new EfContext())
            {
                // DELETE
                foreach (var c in contexto.Cliente)
                    contexto.Cliente.Remove(c);
                contexto.SaveChanges();

                // INSERT
                contexto.Cliente.Add(new Cliente() { Nome = "Novo Cliente EF" });
                contexto.SaveChanges();

                // UPDATE
                var cliente = contexto.Cliente.First();
                cliente.Nome = "Novo Cliente EF Alterado";
                contexto.SaveChanges();

                // SELECT
                foreach (var c in contexto.Cliente)
                {
                    Console.WriteLine("Nome do Cliente: {0}", c.Nome);
                }
            }
        ' VB.NET
        Using Contexto = New EfContext()
            ' DELETE
            For Each C In Contexto.Cliente
                Contexto.Cliente.Remove(C)
            Next
            Contexto.SaveChanges()

            ' INSERT
            Contexto.Cliente.Add(New Cliente() With {.Nome = "Novo Cliente EF"})
            Contexto.SaveChanges()

            ' UPDATE
            Dim Cliente = Contexto.Cliente.First()
            Cliente.Nome = "Novo Cliente EF Alterado"
            Contexto.SaveChanges()

            ' SELECT
            For Each C In Contexto.Cliente
                Console.WriteLine("Nome do Cliente: {0}", C.Nome)
            Next
        End Using

E o code first?

Uma das grandes maravilhas do Entity Framework é a funcionalidade chamada “Code First“. Com ela, nós podemos criar o banco de dados através das nossas classes de domínio. Com o SQL Server nós temos essa funcionalidade nativamente, mas, e com o SQLite? Nesse caso, o “Code First” não está disponível por padrão, mas, existe uma biblioteca que adiciona essa funcionalidade: a SQLite.CodeFirst.

Após adicionarmos essa biblioteca através do NuGet, podemos ativar o “Code First” no nosso contexto. Isso é muito simples de ser feito, basta adicionarmos uma linha no método “OnModelCreating” do nosso contexto:

// C#
Database.SetInitializer(new SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists<EfContext>(modelBuilder));
' VB.NET
System.Data.Entity.Database.SetInitializer(New Sqlite.CodeFirst.SqliteCreateDatabaseIfNotExists(Of EfContext)(modelBuilder))

Pronto! Só com a adição dessa linha de código o Entity Framework criará o banco de dados automaticamente baseado nas entidades do seu contexto (caso o banco ainda não exista).

Concluindo

O SQLite é um dos tipos de bancos de dados “portáteis” disponíveis atualmente (e, a propósito, um dos mais utilizados). Este artigo foi um guia definitivo da utilização do SQLite com C# e VB.NET, onde eu mostrei uma ferramenta de administração de bancos SQLite, a utilização do SQLite com ADO.NET puro, a utilização do SQLite com Entity Framework e, por fim, mostrei como podemos habilitar o “Code First” do Entity Framework em projetos que utilizam o SQLite.

Você já conhecia o esse banco de dados? Como é que você faz para acessar os dados desse banco na sua aplicação? Você uiliza ADO.NET puro ou Entity Framework? Conhecia todas essas bibliotecas que eu mostrei no artigo? Conte-nos mais sobre as suas experiências na caixa de comentários!

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

Newsletter do André Lima

* indicates required



Powered by MailChimp

75 thoughts on “Trabalhando com SQLite no C# e VB.NET

  • Guilherme Petenuci disse:

    Legal, passei um tempão procurando sobre esse assunto em julho, depois acabei usando NHibernate que me ajudou bem, mas o simples que você mostrou agora, na época que eu estava procurando não foi tão fácil achar como parece.
    vlw

    • andrealveslima disse:

      Olá Guilherme, obrigado pelo comentário!

      Poxa vida, se eu tivesse publicado o artigo um pouquinho antes ele teria te ajudado bastante aí, não é? Bom, espero que ele consiga ajudar outras pessoas que passem por essa mesma dificuldade daqui para frente..

      A ideia é essa.. Publicar artigos simples (porém completos) e fáceis de entender.. É bem difícil encontrar artigos desse tipo por aí..

      Um grande abraço!
      André Lima

  • Edward Gómez disse:

    Buen día Andre,

    Como siempre muy buenos tus aportes, justo estoy investigando sobre estas dos base de datos, por ahora comencé con SQLite, pero hasta donde he investigado para trabajar el ReportViewer con SQLite mediante agregar nuevo origen de datos, solo lo puedo hacer mediante conexión ODBC y la base debe estar sin contraseña, la verdad me he frustrado bastante debido a que no me agrada tener que usar ODBC para una conexión a un archivo local y mas pensar en la ruta que debo modificar en las maquinas clientes.

    Estare muy pendiente de tus aportes para estas dos bases de datos, me interesa aprender mas sobre este tema.

    Un abrazo saludos desde Colombia.

    • andrealveslima disse:

      Olá Edward, obrigado pelo comentário!

      Não sabia que só dá para utilizar o SQLite com o Report Viewer através de ODBC.. Se for realmente assim, com certeza é um absurdo.. Vou colocar aqui na minha lista para investigar esse cenário e, assim que eu conseguir testar, eu te aviso..

      Um grande abraço!
      André Lima

  • Ronildo Souza disse:

    Artigo muito bom.

  • André Rogério Bononi disse:

    Olá, André Lima. Tudo em ordem?
    Cara, gostei deste artigo. Sempre quis saber como fazer uma aplicação em C# que não dependesse de instalação de banco de dados. Já tinha ouvido falar do SQLite, mas, nunca tinha utilizado. Achei bem interessante.
    Claro que seu artigo foi bem prático e suscinto, resumindo as aplicações ao CRUD, mas, utilizando Entity Framework, como proceder para pesquisas mais elaboras e complexas (Selects com filtros where, left joins, etc) que são necessárias em qualquer aplicação, pois mais simples que seja?
    Poderia comentar alguma coisa sobre isso? Meu grande receio em utilizar este “componentes” (Entity Framework, NHibernate, entre outros) é justamente não ter controle sobre as operações realizadas, principalmente as mais complexas como, por exemplo, CRUD em tabelas master/details.
    Fale mais sobre isso, por favor! Você foi um achado e tanto na internet, cara. Pena seu MVP não ter sido renovado, mas, a vida continua! Abraços e aguardo algo sobre o assunto citado!

    • andrealveslima disse:

      Olá André, obrigado pelo comentário!

      Normalmente você consegue converter qualquer pesquisa que você tenha em SQL para LINQ, que é a linguagem que você terá que utilizar ao trabalhar com o Entity Framework.. Se você não estiver familiarizado com a sintaxe do LINQ, dê uma olhada nesta lista de exemplos, que mostra inclusive exemplos de joins, where, etc:

      101 LINQ Samples

      Ao fazer uma consulta com o LINQ, por trás dos panos o Entity Framework converte ela para SQL e retorna os dados em forma de instâncias de objetos.. Se por algum acaso você tiver alguma query extremamente complexa que não se encaixe na sintaxe do LINQ, você sempre pode optar por executar uma query SQL diretamente, como mostrado neste artigo:

      Executing Raw SQL Queries Using Entity Framework

      Abraço!
      André Lima

      • André Rogério Bononi disse:

        Olá, André.
        Darei uma olhada sim, com certeza. É uma questão de prática, pois, ainda sinto uma certa dificuldade em trabalhar com LINQ,mas tenho certeza que os exemplos irão ajudar. No momento não tenho como avaliar, mas, o farei assim que estiver em casa.
        Obrigado pelo retorno. Abraços e sucesso!

        • andrealveslima disse:

          Beleza André! Qualquer dúvida é só entrar em contato novamente..

          Abraço!
          André Lima

          • André Rogério Bononi disse:

            Olá, André, tudo bem? Surgiu-me mais uma dúvida. A Conexão com o banco através do Entity Framework parece ser definida num arquivo de configurações e é automaticamente reconhecida. É possível tornar isso configurável, permitindo que eu configure, digamos, na primeira inicialização do sistema, caso não exista? Digo isso, pois pensei na possibilidade de não saber, antecipadamente, onde a base ficará. É possível? Desde já agradeço a atenção. Abraçso.

          • andrealveslima disse:

            Olá André!

            A connection string fica armazenada no arquivo app.config.. Você precisa ter uma entrada com a tag “connectionStrings” utilizando o mesmo nome do seu contexto, conforme eu indiquei no artigo (no exemplo do artigo, o contexto se chama “EfContext”):

            Se por algum acaso você tiver que alterar o conteúdo da connection string armazenada no app.config em tempo de execução, você pode utilizar a estratégia apresentada neste link:

            Change connection string & reload app.config at run time

            Abraço!
            André Lima

          • André Rogério Bononi disse:

            OK, André. Vou dar uma lida neste artigo. Agora estou com outro problema. Utilizando o modelo que você colocou, tentei montar um projeto meu, porém, quando vou incluir um registro ele me diz que a entrada está diferente do indicado no registro. No momento não estou com a mensagem exata, mas, quando estiver em casa, vou pegar a mensagem e te passar.

          • andrealveslima disse:

            Olá André!

            Estranho hein.. Veja se os tipos de dados estão exatamente compatíveis entre o banco de dados e a classe na sua aplicação.. Pode ser isso.. Quando você tiver um tempinho, me manda a mensagem exata para eu dar uma olhada..

            Abraço!
            André Lima

  • […] do Oracle em uma aplicação de exemplo, vamos seguir a mesma estratégia que eu utilizei no artigo sobre SQLite e vamos criar uma nova tabela extremamente simples, chamada “Cliente“, contendo somente […]

  • ricardo disse:

    Parabéns pelo artigo..aprendi muito!! eu apanhei por uns dois dias tentando de tudo pra resolver o problema dos providers..(o erro no System.Data.SQLite.EF6) eu tentei todas as configurações q encontrei pela net..e somente a sua funcionou!! salvou meu projeto rsrs

    • andrealveslima disse:

      Olá Ricardo, muito obrigado pelo comentário!

      Fico feliz que o artigo tenha te ajudado na solução do problema com o provider do EF.. Qualquer dúvida, estamos aí.. :)

      Abraço!
      André Lima

  • Flavio Nascimento Jr. disse:

    Opa amigo, tudo bem, notei que vc está utilizando codigos nesse estilo:
    using (var comm = new System.Data.SQLite.SQLiteCommand(conn))
    {
    comm.CommandText = “INSERT INTO Cliente (Nome) VALUES (‘Novo Cliente’)”;
    comm.ExecuteNonQuery();
    }

    porem, eu poderia fazer assim que funcionaria do mesmo jeito ?
    cmd = new SqlCommand(“INSERT INTO [Usuarios](usuario,senha) VALUES (@usuario,@senha)”,
    cmd.Parameters.AddWithValue(“@usuario”, usuario);
    cmd.Parameters.AddWithValue(“@senha”, senha);
    cn.Open();
    cmd.ExecuteNonQuery();

    • andrealveslima disse:

      Olá novamente, Flavio!

      Você quer dizer o esquema de parâmetros? Se for isso, com certeza.. Inclusive eu falo sobre como você sempre deve utilizar parâmetros do ADO.NET neste artigo aqui.. Só não utilizei nesse exemplo para simplificar e porque o valor era estático (não estava vindo de uma variável)..

      Agora, de qualquer forma eu recomendo que você coloque a criação do comando em um bloco “using”, como eu fiz no exemplo.. Dessa forma, ele será automaticamente descarregado da memória quando o bloco for finalizado..

      Abraço!
      André Lima

  • André Rogério Bononi disse:

    Olá, André Lima, tudo bem? Faz tempo que não apareço por aqui, mas tenho usado muito o SQLite. Foi uma maravilha este artigo. Minhas aplicações de pequeno porte só estão sendo desenvolvido com ele. Agora, eu quero evoluir a aplicação em termos de automação de processos, como por exemplo, criação do banco, tabelas, alteração de tabela, tudo em tempo de execução. Já encontrei alguma coisa na internet que já ajudou bastante, mas, estou com algumas dificuldades. Será que poderia me ajudar? Eu gostaria de saber como, através de código C# eu conseguiria checar a estrutura de uma tabela para saber se existe ou não uma determinada coluna.

    Valeu! Abraços e fico no aguardo de seu comentário!

    • andrealveslima disse:

      Olá André, muito obrigado pelo comentário! Que bom que você está conseguindo utilizar o SQLite com sucesso nas suas aplicações.. Fico feliz por ter conseguido ajudar.. :)

      Quanto à sua questão, você pode verificar a existência de colunas em uma determinada tabela no SQLite utilizando o comando PRAGMA.. Veja se este link te ajuda:

      SQLite check if a column exists c#

      Abraço!
      André Lima

      • André Bononi disse:

        Olá, André.

        Maravilha!!! Era o que eu precisava. Eu até cheguei neste comando PRAGMA table_info(tabela), mas, realmente, não entendi como poderia verificar a existência de uma coluna.

        Seria interessante se houvesse um método que fizesse a checagem diretamente de uma coluna, como existe no SQL SERVER, mas, isso já resolve o problema.

        Valeu! Abraços e obrigado.

  • ricardo disse:

    olá André td bem? sempre que eu vejo algum artigo sobre SQlite ele geralmente esta relacionado com “aplicações de pequeno porte”, confesso que pela minha pouca experiencia, tenho dificuldades em dimensionar um projeto para saber se é de pequeno, medio ou grande porte e assim escolher as tecnologias mais adequadas, existe alguma maneira de fazer essa analise pelo menos no que diz respeito ao banco de dados?
    obrigado.

    • andrealveslima disse:

      Olá Ricardo!

      Cara, isso é muito subjetivo.. Cada um vai ter uma opinião diferente.. A minha é a seguinte: quanto a banco de dados, aplicações mobile são, de qualquer forma, de “pequeno porte”.. Ou seja, você não vai armazenar gigas e mais gigas de dados (de maneira local) em uma aplicação móvel, porque isso não faz sentido.. Dessa forma, um banco como o SQLite sempre vai se encaixar bem nesse tipo de cenário..

      Já quanto a aplicações desktop, se considerarmos somente o quesito “banco de dados”, eu consideraria até centenas de megas a aplicação como “pequeno porte”.. Eu particularmente não utilizaria um banco de dados SQLite para armazenar dados nas casas de gigabytes (apesar de ele suportar até mesmo petabytes).. Como o banco de dados nesse caso estaria em um único arquivo, as chances de corromper ou perder esse arquivo seriam muito grandes.. Um banco de dados mais robusto faria, então, mais sentido.. Bancos mais robustos (como SQL Server, por exemplo) contam com um ferramental muito mais poderoso para que você consiga gerenciar toda essa quantidade de dados (backup automatizado, por exemplo)..

      Mas, essa é só a minha opinião.. Tem gente que vai falar que SQLite suporta qualquer quantidade de dados e utilizaria ele em qualquer cenário que fosse..

      Abraço!
      André Lima

  • André Rogério Bononi disse:

    Olá, André.

    Olha eu novamente! (rs).

    Seguinte, minha dúvida agora é quanto às ferramentas de administração do SQLite.
    Eu já usei o DB Browser SQLite e, agora, estou testando o SQLite Administrator.

    Tanto num quanto no outro eu não consegui executar uma instrução SQL (um simples select) com variáveis.

    As instruções Declare e Set parece não funcionar.

    Saberia me informar se existe esta possibilidade?

    O select que quero testar é o seguinte:

    declare @Sup integer;
    declare @Lider integer;
    declare @Celula integer;
    declare @Ano integer;
    declare @Mes integer;
    declare @Dia integer;

    set @Sup = 1;
    set @Lider = 2;
    set @Celula = 10;
    set @Ano = 0;
    set @Mes = 0;
    set @Dia = 0;

    Select I.*, C.Nome as NomeCidade, C.UF as UFCidade,
    E.Nome as NomeConjuge1, L.Nome as NomeLider,
    S.Nome as NomeSupervisor, Cel.Nome as NomeCelula
    from Integrantes I
    left join Cidades C on C.Id = I.IdCidade
    left join Integrantes E on E.Id = I.IdConjuge
    left join Integrantes L on L.Id = I.IdLider
    left join Integrantes S on S.Id = I.IdSupervisor
    left join Celulas Cel on Cel.IdCelula = I.IdCelula
    where I.IdSupervisor = @Sup
    and I.IdLider = @Lider
    and I.IdCelula = @Celula
    and (@Ano > 0 and I.Ano = @Ano)
    and (@Mes > 0 and I.Mes = @Mes)
    and (@Dia > 0 and I.Dia = @Dia)
    order by I.DataNascto

    • andrealveslima disse:

      Olá André!

      Até onde eu sei, o SQLite não suporta esse conceito de variáveis declaradas.. Pelo que pesquisei aqui rapidamente, você pode emular o mesmo conceito com tabelas temporárias, como você pode conferir nesta thread do StackOverflow:

      Declare variable in sqlite and use it

      Abraço!
      André Lima

      • Andre - RSYS disse:

        Maravilha, André. Chegando em casa vou analisar esta questão. É basicamente pra testar instruções select e depois implementá-las no sistema.

        Valeu! Abraços e obrigado novamente!

  • André Rogério Bononi disse:

    Olá, André, tudo bem?

    Eu ainda estou evoluindo na utilização do SQLite. Agora me deparei com uma outra situação que não conseguir entender como funciona.

    Pesquisei vários sites e encontrei vários exemplos de Trigger no SQLite, mas, nenhum que demonstrasse como criar uma que atendesse à todas as situações possíveis: Insert, Update e Delete, tudo numa trigger só.

    Vou lhe dar um exemplo:

    Tenho uma tabela de Produtos e uma de Saldo de Estoque e gostaria de, quando incluísse um produto, a trigger incluísse um registro na tabela Saldo, caso este registro ainda não exista.

    O mesmo no Update e a exclusão no caso do Delete.

    Isso é possível? Tudo numa trigger só?

    Valeu. Abraços!!!

    • andrealveslima disse:

      Olá André, tudo tranquilo por aqui, e aí?

      Seguinte.. Não tenho nenhuma experiência com triggers no SQLite, mas, depois de dar uma pesquisada aqui, eu acredito que você não conseguirá fazer tudo em uma trigger só.. Isso porque a sintaxe da trigger no SQLite (que você consegue conferir todos os detalhes neste link) só recebe um “database event” por vez.. Ou seja, você consegue criar a trigger para INSERT, UPDATE ou DELETE, e não para mais de uma condição ao mesmo tempo..

      Entretanto, gostaria de perguntar: por que você quer fazer isso com trigger? Existe um motivo para não implementar isso na própria aplicação, ao invés de ficar colocando lógica de negócio no banco de dados.. Eu costumo agregar toda a lógica de negócio na aplicação, evitando colocar qualquer implementação no banco, que acaba servindo somente como um depósito de dados mesmo..

      Já trabalhei em projetos onde parte da lógica estava no banco e parte estava na aplicação.. A manutenção nesse tipo de projeto é extremamente difícil.. Quando acontece um erro você acaba não sabendo exatamente onde o erro está sendo ocasionado..

      Abraço!
      André Lima

      • Andre - RSYS disse:

        Olá, André!

        Na verdade, eu queria era mesmo automatizar alguns processos diretamente no banco, pois, no exemplo dado, digamos, não ser uma regra de negócios, mas, sim, uma regra geral. Isso sempre vai acontecer, ou seja, inseriu um registro na tabela Produtos, tenho que criar um registro na tabela Saldo de Estoque. Só levantei a hipótese do registro não existir mais por uma questão de segurança, de integridade de dados.

        O link que você passou já ajuda bastante. Até já tinha visto, mas, acreditei ser possível algo como no SQL SERVER: Uma Trigger com FOR Insert, FOR UPDATE, FOR DELETE.

        Mas, tranquilo. Vou usar o que está como exemplo no link que você passou.

        Obrigado, André.

        Abraços!

        • andrealveslima disse:

          Legal, André.. Entendi.. Pois é, pelo que vi, não dá para fazer que nem no SQL Server, onde podemos especificar mais de uma “trigger action”.. Se descobrir algo mais elegante, volta aqui e dá um toque.. ;)

          Abraço!
          André Lima

  • João Victor disse:

    Olá André!
    Post extremamente simples e interessante.

    Comente mais a respeito de sua afirmação rsrs

    O SQLite suporta até mesmo triggers (apesar de eu não gostar delas)

    Mais uma dúvida. Apesar de ser um banco local, é possível acessar o SQLite via REDE?

    • andrealveslima disse:

      Olá João, obrigado pelo comentário!

      Qual das partes você quer que eu comente? A que o SQLite suporta triggers? Ou o por que de eu não gostar delas? Se for a parte que o SQLite suporta triggers, você pode ver toda a sintaxe delas no próprio site do SQLite:

      SQLite CREATE TRIGGER

      Já o motivo de eu não gostar delas é que eu já trabalhei em mais de um projeto que utilizava muitas triggers no banco e elas só causaram dor de cabeça.. Fica muito difícil saber de onde é que estão vindo alterações malucas no banco, é extremamente doloroso fazer o debug de aplicações desse tipo.. Na minha opinião, esse tipo de lógica tem que ficar no código da aplicação, e o banco de dados servindo mais como um repositório de dados mesmo.. Mas esse é só a minha opinião.. ;)

      Quanto à sua pergunta se dá para acessar o SQLite via rede, até dá se você criar um diretório mapeado que seja acessível por todos os clientes.. Porém, não recomendo.. Ele é mais voltado para ser um banco de dados local mesmo.. Se você precisa de acesso simultâneo de mais de um usuário em computadores diferentes, eu partiria para um banco de dados “de verdade”, como SQL Server, MySQL, PostgreSQL, etc..

      Abraço!
      André Lima

  • […] Se você tiver dificuldades nessa área, recomendo que você dê uma conferida primeiro no meu artigo básico sobre SQLite no C# e VB.NET. O código para salvarmos a lista de gastos no banco ficaria […]

  • Ramon Shuan disse:

    Andre Boa tarde!

    Tenho minha aplicação toda feita em c# com sql server estou precisando passar para sqlite para economizar tempo na instalação do sistema. A minha pergunta é a seguinte.

    Consigo passar meu banco de dado de sql server para o sqlite? Fazendo essa alteração teria que trocar minha classe DATA toda?

    • andrealveslima disse:

      Olá Ramon!

      Para converter o banco de dados em si, tem uma ferramenta no CodeProject que promete fazer esse tipo de conversão:

      Convert SQL Server DB to SQLite DB

      Agora, o código da aplicação em si, vai depender muito de como você estruturou a sua camada de acesso a dados.. Se você estiver trabalhando mais com ADO.NET puro, utilizando sentenças sem muita frescura e com sintaxe exclusiva do SQL Server, você só terá que praticamente alterar o provider do SQL Server para SQLite.. Mas, você só vai conseguir ter uma ideia real da amplitude depois que você começar a converter o projeto mesmo..

      Abraço!
      André Lima

  • Walter Santos disse:

    Boa Tarde Andre, primeiramente gostaria de parabeniza-lo pelo excelente conteúdo que você posta.

    Não estou conseguindo fazer funcionar o SQLite.CodeFirst.

    Eu acrescentei a linha no OnModelCreating mas não cria o .db

    Não apresenta nenhum erro apenas não cria.

    Você tem aluma ideia, Obrigado.

    • andrealveslima disse:

      Olá Walter!

      Muito obrigado pelo elogio.. :)

      Quanto ao seu problema, que estranho hein.. Você tem certeza que você não apagou acidentalmente a linha que chama o “base.OnModelCreating”? Como é que ficou o seu código exatamente?

      Abraço!
      André Lima

        • andrealveslima disse:

          Olá Walter!

          Que coisa.. Está igual ao que eu utilizei no projeto de exemplo.. Você chegou a baixar o meu exemplo para ver se ele funciona aí pra você? Caso ainda não tenha testado, por favor, veja se ele funciona no seu computador.. Não esqueça de descomentar a última linha do método OnModelCreating que eu deixei comentada no projeto de exemplo..

          Depois me avisa se o meu projeto de exemplo funcionou ou não aí pra você.. Se funcionar, aí deve ter alguma coisa errada nas configurações do seu projeto, muito provavelmente no
          app.config.. Mas, vamos analisando uma coisa de cada vez.. Primeiro testa o meu projeto e me avisa se funcionou..

          Abraço!
          André Lima

          • Walter Santos disse:

            Boa Tarde André, não consegui achar o link para baixar o projeto.

            Você pode me enviar para eu testar.

            Obrigado.

          • andrealveslima disse:

            Olá Walter!

            Mandei lá no seu e-mail.. Qualquer dificuldade é só entrar em contato..

            Abraço!
            André Lima

          • Walter Santos disse:

            Olá André tudo bom?

            No link que você me enviou não tem esse projeto do SQlite.

            Você teria outro link?

            Obrigado.

          • andrealveslima disse:

            Olá Walter!

            Realmente não está disponível esse projeto de exemplo do SQLite.. Eu comecei a disponibilizar os exemplos de todos os artigos escritos a partir de janeiro de 2017, e esse artigo sobre SQLite é um pouco mais antigo.. Vou ficar te devendo.. :(

            Mas, de qualquer forma, você já tentou reconstruir esse projeto utilizando os códigos apresentados no artigo? Deu alguma coisa errada? Era para funcionar sem problema nenhum seguindo as instruções do artigo..

            Abraço!
            André Lima

  • Fabio disse:

    Olá Andre, tudo bem ??
    Bom meu problema está relacionando tanto ao SQLite quanto a um video que voce postou sobre criar um instalador a partir do visual studio 2015.
    Está ocorrendo um erro na hora de compilar pois o visual studio informa que está faltando o sql server…

    • andrealveslima disse:

      Olá Fabio!

      Será que você poderia postar a mensagem de erro exata que você está recebendo? Talvez com a mensagem de erro fique mais fácil de entender o que está acontecendo..

      Abraço!
      André Lima

  • FERNANDO DE SOUSA disse:

    Boa tarde André, vc pode me dar uma ajuda? Estou com um problema no método UPDATE do SQLite C#. Eu não sei programação, sou um curioso, fiz uma aplicação seguindo um passo a passo no youtube.

    Segue a classe GET, SET:
    public class Contato
    {
    public Contato()
    {
    this.IdContato = 0;
    this.Posto = “”;
    this.Especialidade = “”;
    this.Nip = “”;
    this.Nome = “”;
    this.NomeGuerra = “”;
    this.Identidade = “”;
    this.TS = “”;
    this.Logradouro = “”;
    this.Numero = “”;
    this.Cep = “”;
    this.UF = “”;
    this.Cidade = “”;
    this.Bairro = “”;
    this.Telefone = “”;
    this.Celulara = “”;
    this.Celularb = “”;
    this.Email = “”;
    this.Funcao = “”;
    this.Observacao = “”;
    this.Credseg = “”;
    }
    public Contato(long IdContato, string Posto, string Especialidade, string Nip, string Nome, string NomeGuerra, string Identidade, string TS, string Logradouro,
    string Numero, string Cep, string UF, string Cidade, string Bairro, string Telefone, string Celulara, string Celularb, string Email, string Funcao, string Observacao, string Credseg)
    {
    this.IdContato = IdContato;
    this.Posto = Posto;
    this.Especialidade = Especialidade;
    this.Nip = Nip;
    this.Nome = Nome;
    this.NomeGuerra = NomeGuerra;
    this.Identidade = Identidade;
    this.TS = TS;
    this.Logradouro = Logradouro;
    this.Numero = Numero;
    this.Cep = Cep;
    this.UF = UF;
    this.Cidade = Cidade;
    this.Bairro = Bairro;
    this.Telefone = Telefone;
    this.Celulara = Celulara;
    this.Celularb = Celularb;
    this.Email = Email;
    this.Funcao = Funcao;
    this.Observacao = Observacao;
    this.Credseg = Credseg;
    }
    public long IdContato { get; set; }
    public string Posto { get; set; }
    public string Especialidade { get; set; }
    public string Nip { get; set; }
    public string Nome { get; set; }
    public string NomeGuerra { get; set; }
    public string Identidade { get; set; }
    public string TS { get; set; }
    public string Logradouro { get; set; }
    public string Numero { get; set; }
    public string Cep { get; set; }
    public string UF { get; set; }
    public string Cidade { get; set; }
    public string Bairro { get; set; }
    public string Telefone { get; set; }
    public string Celulara { get; set; }
    public string Celularb { get; set; }
    public string Email { get; set; }
    public string Funcao { get; set; }
    public string Observacao { get; set; }
    public string Credseg { get; set; }
    }

    ==========================================================

    segue o método UPDATE:
    public void Alterar(Contato contato)
    {
    SQLiteCommand cmd = new SQLiteCommand(conn);
    if (conn.State == ConnectionState.Closed)
    {
    conn.Open();
    }
    cmd.CommandText = “UPDATE contato SET Posto=@Posto, Especialidade=@Especialidade, Nip=@Nip, Nome=@Nome, ” +
    “NomeGuerra=@NomeGuerra, Identidade=@Identidade, TS=@TS, Logradouro=@Logradouro, Numero=@Numero, Cep=@Cep, UF=@UF, Cidade=@Cidade, Bairro=@Bairro, ” +
    “Observacao=@Observacao, Telefone=@Telefone, Celulara=@Celulara, Celularb=@Celularb, Email=@Email, Funcao=@Funcao, Credseg=@Credseg WHERE IdContato=@IdContato”;
    cmd.Parameters.AddWithValue(“@IdContato”, contato.IdContato);
    cmd.Parameters.AddWithValue(“@Posto”, contato.Posto);
    cmd.Parameters.AddWithValue(“@Especialidade”, contato.Especialidade);
    cmd.Parameters.AddWithValue(“@Nip”, contato.Nip);
    cmd.Parameters.AddWithValue(“@Nome”, contato.Nome);
    cmd.Parameters.AddWithValue(“@NomeGuerra”, contato.NomeGuerra);
    cmd.Parameters.AddWithValue(“@Identidade”, contato.Identidade);
    cmd.Parameters.AddWithValue(“@TS”, contato.TS);
    cmd.Parameters.AddWithValue(“@Logradouro”, contato.Logradouro);
    cmd.Parameters.AddWithValue(“@Numero”, contato.Numero);
    cmd.Parameters.AddWithValue(“@Cep”, contato.Cep);
    cmd.Parameters.AddWithValue(“@UF”, contato.UF);
    cmd.Parameters.AddWithValue(“@Cidade”, contato.Cidade);
    cmd.Parameters.AddWithValue(“@Bairro”, contato.Bairro);
    cmd.Parameters.AddWithValue(“@Observacao”, contato.Observacao);
    cmd.Parameters.AddWithValue(“@Telefone”, contato.Telefone);
    cmd.Parameters.AddWithValue(“@Celulara”, contato.Celulara);
    cmd.Parameters.AddWithValue(“@Celularb”, contato.Celularb);
    cmd.Parameters.AddWithValue(“@Email”, contato.Email);
    cmd.Parameters.AddWithValue(“@Funcao”, contato.Funcao);
    cmd.Parameters.AddWithValue(“@Credseg”, contato.Credseg);
    try
    {
    cmd.ExecuteNonQuery();
    MessageBox.Show(“O Registro ‘” + contato.IdContato.ToString() + “‘, foi atualizado com sucesso!”);
    }
    catch (SQLiteException ex)
    {
    MessageBox.Show(“Erro ao atualizar registro: ” + ex.Message);
    }
    finally
    {
    conn.Close();
    }
    }

    segue o método SALVAR:
    private void btSalvar_Click(object sender, EventArgs e)
    {
    try
    {
    if (txtNome.Text.Length <= 0)
    {
    MessageBox.Show("Nome obrigatório");
    return;
    }
    Contato contato = new Contato();
    contato.Posto = txtPosto.Text;
    contato.Especialidade = cbEsp.Text;
    contato.Nip = txtNip.Text;
    contato.Nome = txtNome.Text;
    contato.NomeGuerra = txtNomeGuera.Text;
    contato.Identidade = txtIdentidade.Text;
    contato.TS = cbTS.Text;
    contato.Logradouro = txtLogradouro.Text;
    contato.Numero = txtNumero.Text;
    contato.Cep = txtCEP.Text;
    contato.UF = cbUF.Text;
    contato.Cidade = cbCidade.Text;
    contato.Bairro = cbBairro.Text;
    contato.Observacao = cbObservacao.Text;
    contato.Telefone = txtTelefone.Text;
    contato.Celulara = txtCelulara.Text;
    contato.Celularb = txtCelularb.Text;
    contato.Email = txtEmail.Text;
    contato.Funcao = txtFuncao.Text;
    contato.Credseg = txtCredSeg.Text;
    conn.Open();
    if (this.operacao == "inserir")
    {
    Incluir(contato);
    MessageBox.Show("O código gerado foi: " + contato.IdContato.ToString());
    }
    else if (this.operacao == "alterar")
    {
    contato.IdContato = Convert.ToInt32(txtIdContato.Text);
    Alterar(contato);
    }
    this.LimpaCampos();
    this.AlteraBotoes(1);
    }
    catch (SQLiteException ex)
    {
    MessageBox.Show("Erro ao salvar registro: " + ex.Message);
    }
    finally
    {
    conn.Close();
    }
    }
    segue o método INSERIR:
    public void Incluir(Contato contato)
    {
    SQLiteCommand cmd = new SQLiteCommand(conn);
    if (conn.State == ConnectionState.Closed)
    {
    conn.Open();
    }
    cmd.CommandText = "insert into contato(Posto, Especialidade, Nip, Nome, " +
    "NomeGuerra, Identidade, TS, Logradouro, Numero, Cep, UF, Cidade, Bairro, " +
    "Observacao, Telefone, Celulara, Celularb, Email, Funcao, Credseg)" +
    "values (@Posto, @Especialidade, @Nip, @Nome, @NomeGuerra, @Identidade, @TS, @Logradouro, @Numero, @Cep, @UF, " +
    " @Cidade, @Bairro, @Observacao, @Telefone, @Celulara, @Celularb, @Email, @Funcao, @Credseg); SELECT MAX(IdContato) FROM contato";
    cmd.Prepare();
    cmd.Parameters.AddWithValue("Posto", contato.Posto);
    cmd.Parameters.AddWithValue("Especialidade", contato.Especialidade);
    cmd.Parameters.AddWithValue("Nip", contato.Nip);
    cmd.Parameters.AddWithValue("Nome", contato.Nome);
    cmd.Parameters.AddWithValue("NomeGuerra", contato.NomeGuerra);
    cmd.Parameters.AddWithValue("Identidade", contato.Identidade);
    cmd.Parameters.AddWithValue("TS", contato.TS);
    cmd.Parameters.AddWithValue("Logradouro", contato.Logradouro);
    cmd.Parameters.AddWithValue("Numero", contato.Numero);
    cmd.Parameters.AddWithValue("Cep", contato.Cep);
    cmd.Parameters.AddWithValue("UF", contato.UF);
    cmd.Parameters.AddWithValue("Cidade", contato.Cidade);
    cmd.Parameters.AddWithValue("Bairro", contato.Bairro);
    cmd.Parameters.AddWithValue("Observacao", contato.Observacao);
    cmd.Parameters.AddWithValue("Telefone", contato.Telefone);
    cmd.Parameters.AddWithValue("Celulara", contato.Celulara);
    cmd.Parameters.AddWithValue("Celularb", contato.Celularb);
    cmd.Parameters.AddWithValue("Email", contato.Email);
    cmd.Parameters.AddWithValue("Funcao", contato.Funcao);
    cmd.Parameters.AddWithValue("Credseg", contato.Credseg);
    try
    {
    contato.IdContato = Convert.ToInt32(cmd.ExecuteScalar());
    MessageBox.Show("Registro salvo com sucesso!");
    }
    catch (SQLiteException ex)
    {
    MessageBox.Show("Erro ao salvar registro: " + ex.Message);
    }
    finally
    {
    conn.Close();
    }
    }
    Desde já obrigado.

    Fernando.

    • andrealveslima disse:

      Olá Fernando!

      Qual é o erro que você está obtendo? E em qual linha?

      Abraço!
      André Lima

      • FERNANDO DE SOUSA disse:

        Bom dia André, o erro é este: “Erro ao atualizar registro: database is locked database is locked”. Este erro acontece nesta linha: cmd.ExecuteNonQuery(); do método alterar alterar abaixo:

        public void Alterar(Contato contato)
        {
        SQLiteCommand cmd = new SQLiteCommand(conn);
        if (conn.State == ConnectionState.Closed)
        {
        conn.Open();
        }
        cmd.CommandText = “UPDATE contato SET Posto = @Posto, Especialidade = @Especialidade, Nip = @Nip, Nome = @Nome, ” +
        “NomeGuerra = @NomeGuerra, Identidade = @Identidade, TS = @TS, Logradouro = @Logradouro, Numero = @Numero, Cep = @Cep, UF = @UF, Cidade = @Cidade, Bairro = @Bairro, ” +
        “Observacao = @Observacao, Telefone = @Telefone, Celulara = @Celulara, Celularb = @Celularb, Email = @Email, Funcao = @Funcao, Credseg = @Credseg WHERE IdContato = @IdContato”;
        cmd.Parameters.AddWithValue(“IdContato”, contato.IdContato);
        cmd.Parameters.AddWithValue(“Posto”, contato.Posto);
        cmd.Parameters.AddWithValue(“Especialidade”, contato.Especialidade);
        cmd.Parameters.AddWithValue(“Nip”, contato.Nip);
        cmd.Parameters.AddWithValue(“Nome”, contato.Nome);
        cmd.Parameters.AddWithValue(“NomeGuerra”, contato.NomeGuerra);
        cmd.Parameters.AddWithValue(“Identidade”, contato.Identidade);
        cmd.Parameters.AddWithValue(“TS”, contato.TS);
        cmd.Parameters.AddWithValue(“Logradouro”, contato.Logradouro);
        cmd.Parameters.AddWithValue(“Numero”, contato.Numero);
        cmd.Parameters.AddWithValue(“Cep”, contato.Cep);
        cmd.Parameters.AddWithValue(“UF”, contato.UF);
        cmd.Parameters.AddWithValue(“Cidade”, contato.Cidade);
        cmd.Parameters.AddWithValue(“Bairro”, contato.Bairro);
        cmd.Parameters.AddWithValue(“Observacao”, contato.Observacao);
        cmd.Parameters.AddWithValue(“Telefone”, contato.Telefone);
        cmd.Parameters.AddWithValue(“Celulara”, contato.Celulara);
        cmd.Parameters.AddWithValue(“Celularb”, contato.Celularb);
        cmd.Parameters.AddWithValue(“Email”, contato.Email);
        cmd.Parameters.AddWithValue(“Funcao”, contato.Funcao);
        cmd.Parameters.AddWithValue(“Credseg”, contato.Credseg);
        try
        {
        cmd.ExecuteNonQuery();
        MessageBox.Show(“O Registro ‘” + contato.IdContato.ToString() + “‘, foi atualizado com sucesso!”);
        }
        catch (SQLiteException ex)
        {
        MessageBox.Show(“Erro ao atualizar registro: ” + ex.Message);
        }
        finally
        {
        conn.Close();
        }
        }

        Segue-se também o método salvar:

        private void btSalvar_Click(object sender, EventArgs e)
        {
        try
        {
        if (txtNome.Text.Length <= 0)
        {
        MessageBox.Show("Nome obrigatório");
        return;
        }
        Contato contato = new Contato();
        contato.Posto = txtPosto.Text;
        contato.Especialidade = cbEsp.Text;
        contato.Nip = txtNip.Text;
        contato.Nome = txtNome.Text;
        contato.NomeGuerra = txtNomeGuera.Text;
        contato.Identidade = txtIdentidade.Text;
        contato.TS = cbTS.Text;
        contato.Logradouro = txtLogradouro.Text;
        contato.Numero = txtNumero.Text;
        contato.Cep = txtCEP.Text;
        contato.UF = cbUF.Text;
        contato.Cidade = cbCidade.Text;
        contato.Bairro = cbBairro.Text;
        contato.Observacao = cbObservacao.Text;
        contato.Telefone = txtTelefone.Text;
        contato.Celulara = txtCelulara.Text;
        contato.Celularb = txtCelularb.Text;
        contato.Email = txtEmail.Text;
        contato.Funcao = txtFuncao.Text;
        contato.Credseg = txtCredSeg.Text;
        conn.Open();
        if (this.operacao == "inserir")
        {
        Incluir(contato);
        MessageBox.Show("O código gerado foi: " + contato.IdContato.ToString());
        }
        else if (this.operacao == "alterar")
        {
        contato.IdContato = Convert.ToInt32(txtIdContato.Text);
        Alterar(contato);
        //MessageBox.Show("O código alterado foi: " + contato.IdContato.ToString());
        }
        this.LimpaCampos();
        this.AlteraBotoes(1);
        }
        catch (SQLiteException ex)
        {
        MessageBox.Show("Erro ao salvar registro: " + ex.Message);
        }
        finally
        {
        conn.Close();
        }
        }

        Desde já obrigado

        Fernando.

        • andrealveslima disse:

          Olá Fernando!

          Provavelmente, em algum ponto do código, você está deixando uma conexão, comando, datareader, etc. aberto e não descartado, aí o banco fica travado para escritas.. Você precisa sempre abrir a conexão, comando, etc. dentro de “blocos using”.. Assista este vídeo (até o final!) para entender o que eu estou falando:

          Evitando memory leaks no .NET com Dispose e blocos using

          Abraço!
          André Lima

  • […] pode utilizar na sua aplicação. Por exemplo, uns tempos atrás eu escrevi um artigo mostrando como trabalhar com o SQLite no C# e VB.NET, que é um dos bancos de dados locais mais utilizados no mercado. Como você pode conferir nesse […]

  • Luiz disse:

    Olá Boa noite. Tem como vc me mandar um exemplo básico de cadastro de um cliente com o salitre mas no utilizando o padrão MAC?

    • andrealveslima disse:

      Olá Luiz! Respondi o seu e-mail.. Fico aguardando o seu retorno.. Da próxima vez, por favor, envie a sua dúvida somente por um canal (e-mail, comentário no site ou no Youtube), e não em todos eles!

      Abraço!
      André Lima

  • GILBERTO VAGNER GONÇALVES disse:

    Olá André, como vai ?.

    Parabéns pelo Tutorial.

    Estou implementando o SQLile na minha aplicação e me guiando pelo seu tutorial. Como estou usando o CodeFirst segui está orientação: Database.SetInitializer(new SQLite.CodeFirst.SqliteCreateDatabaseIfNotExists(modelBuilder));

    Porém o VS está retornando estpa mensagem:

    O nome de namespace ou o tipo ‘SQLite’ não pôde ser encontrado. Precisa de uma diretiva using ou de uma referência de assembly?

    Defini este using: System.Data.SQLite;

    Mas o erro persiste.

    Obrigado pela ajuda.

    Um abraço.

    • andrealveslima disse:

      Olá Gilberto, obrigado pelo comentário!

      Você precisa adicionar a referência para a biblioteca “SQLite.CodeFirst” via NuGet.. Eu expliquei no artigo, dentro da seção “E o code first?”.. Você chegou a ver?

      Abraço!
      André Lima

      • GILBERTO VAGNER GONÇALVES disse:

        Olá André.

        Obrigado, consegui me achar.

        Um dúvida, onde fica gravado o arquivo banco.db ?.

        Abraço.

        • andrealveslima disse:

          Olá Gilberto!

          A não ser que você passe um caminho fixo na string de conexão, o arquivo do banco fica no diretório da aplicação..

          Abraço!
          André Lima

  • GILBERTO VAGNER GONÇALVES disse:

    Olá André.

    Como usar MIgrations no SQLite. Está retornando está mensagem quando adiciono a migração.

    Não é possível acessar um objeto descartado.
    Nome do objeto: ‘SQLiteConnection’.

    Um abraço.

    Obrigado.

    • andrealveslima disse:

      Olá Gilberto!

      Cara, infelizmente vou ficar te devendo.. Não tenho experiência com migrations + SQLite.. :(

      Se você descobrir o problema, volta aqui e avisa a gente, OK?

      Abraço!
      André Lima

  • GILBERTO VAGNER GONÇALVES disse:

    Olá André.

    Sabe como proceder no caso de modificar a estrutura do banco e precisar atualizar um banco existente ?.

    Obrigado.

    • andrealveslima disse:

      Olá Gilberto!

      Se você não estiver trabalhando com Entity Framework + Migrations (que eu não sei se funciona em conjunto com o SQLite), você terá que implementar algum sistema de atuatlização dentro da sua aplicação (salvando a versão atual do banco em alguma tabela e executando os “ALTER TABLE”, “CREATE TABLE”, etc necessários para atualizar o banco para a versão mais recente)..

      Abraço!
      André Lima

    • André R. Bononi disse:

      Olá Gilberto.

      Eu tenho todas as estas rotinas definidas no meu sistema.
      Criei uma classe chamada ExecutorSqLite, onde defino todos os métodos de acesso ao banco. Dentre eles, destacam-se:

      1- Checar se o banco existe;
      2- Criar o banco;
      3- Checar se uma tabela existe;
      4- Checar se uma coluna existe dentro de uma tabela;
      5- Executar uma lista de comandos SQL para diversas finalidades.

      Tenho por padrão criar uma tabela chamada Versões dentro de todos os bancos que crio. Esta tabela é checada para verificar se é compatível com a versão que o sistema exigirá do banco.

      Caso não seja, executa uma rotina com todos os scripts necessários para que a versão do banco fique exatamente igual à versão do sistema.

      É relativamente simples, porém, trabalhosa.
      São executados scripts de criação de tabela, criação de colunas, alteração de tipos de dados da coluna, quando for o caso.

      Se você estruturar e pensar bem, conseguirá fazer isso sem grandes dificuldades.

      Abraços!

      • andrealveslima disse:

        Olá André!

        Muito obrigado pela dica que você deu aqui para o Gilberto.. Valeu por ter tomado um tempo para vir aqui e explicar como você implementou no seu sistema.. Acredito que vá ajudar o Gilberto e outras pessoas que possam passar pela mesma dificuldade..

        Abraço!
        André Lima

  • Daniel Marinho disse:

    Fala André,

    Na utilização do SQLite com Entity estava apresentando o erro abaixo:
    ___

    “An unhandled exception of type ‘System.TypeInitializationException’ occurred in EntityFramework.dll
    Additional information: O inicializador de tipo de ‘System.Data.Entity.Internal.AppConfig’ acionou uma exceção.”
    ___

    Descobri o motivo, a TAG “…” deve ser a primeira do App.Config.

    Acabei inserindo a TAG … antes e isso gerava o erro.

    Encontrei a solução nesse post “https://pt.stackoverflow.com/questions/165612/erro-ao-fazer-o-codefirst-no-enityframework”.

    Obrigado pelo seu trabalho, seus artigos certamente ajudam muitas pessoas.

    Abração.

  • André R. Bononi disse:

    Olá, André, tudo bem?

    Gostaria de um esclarecimento seu em relação ao SQLite com VS2017 e C#.

    É possivel referenciar as Dlls do SQLite sem utilizar o NuGet? Pergunto isso porque passei por um problema com minha internet e fiquei impossibilitado de desenvolver novas aplicações com este banco de dados porque não tinha como instalar essas bibliotecas em acesso à internet.

    Seria possível fazer algo manualmente? Referenciá-las como se faz com outras Dlls do Framework. NET?

    Aproveito a oportunidade para relatar um problema que tive com respostas nesta sua página. Alguns dias atrás tentei postar um comentário/dúvidas numa postagem de um colega aí e não consegui. Minhas postagens (tentei várias vezes) não foram finalizadas corretamente. Sabe o porquê?

    Desde já agradeço a atenção!

    • andrealveslima disse:

      Olá André!

      Vou copiar aqui a resposta que eu te mandei por e-mail.. E quanto ao problema com os comentários, meu site estava com algumas instabilidades nas últimas semanas.. Eu desabilitei alguns plugins do WordPress e agora é pra ter melhorado.. Se você tiver mais algum problema, me avisa..

      Respondendo à pergunta relacionada ao SQLite.. Sim, é possível.. Todo pacote NuGet na verdade é simplesmente um arquivo zip com as dlls.. A “magia” é que quando você adiciona a referência pelo NuGet o Visual Studio faz todo o trabalho por trás dos panos, possibilitando também o upgrade para versões mais novas da biblioteca, etc..

      Se você for no site do NuGet, você consegue baixar o pacote pelo browser.. Por exemplo, o pacote do SQLite:

      https://www.nuget.org/packages/System.Data.SQLite.Core/

      Ao clicar em “Download package”, ele vai baixar um arquivo .nupkg.. Altere a extensão desse arquivo para .zip, descompacte e você encontrará as dlls.. Aí é só copiar pra pasta do projeto e referenciar como você já está acostumado com outras dlls..

      Abraço!
      André Lima

      • André R. Bononi disse:

        Bom dia, André!

        Ótimo! Bom saber disso. Coomo eu relatei no email, tive problemas com minha internet e me impossibilitado de criar novos projetos com SQLite. Até fiz as referências como você disse, mas, não tinha certeza se daria certo, por isso o questionamento.

        Obrigado pela atenção e espero que este comentário seja publicado!

        Abraços!

        • andrealveslima disse:

          Legal, André! Que bom que deu certo.. Os comentários não são publicados imediatamente porque eles passam pela minha moderação (senão tem muito spam).. Mas, como você pode ver, esse seu comentário foi publicado.. :)

          Abraço!
          André Lima

        • andrealveslima disse:

          Ah, inclusive acabei de ver aqui que tem alguns comentários antigos seus que ainda estão na minha fila para processar.. É que eu estou dando um tempo das publicações para focar mais na família, por isso tem vários comentários atrasados ainda.. Pode ficar tranquilo que assim que eu conseguir processá-los aqueles seus comentários anteriores serão publicados também..

          Abraço!
          André Lima

  • André R. Bononi disse:

    OK, André. Família sempre é prioridade!

    Agradeço a atenção e fique com DEUS! Abraços!

  • Lucas disse:

    Artigo Excelente! Ajudou demais!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *