System.Data.SQLite Close () não libera arquivo de banco de dados

93

Estou tendo problemas para fechar meu banco de dados antes de tentar excluir o arquivo. O código é apenas

 myconnection.Close();    
 File.Delete(filename);

E o Delete lança uma exceção de que o arquivo ainda está em uso. Tentei novamente Delete () no depurador após alguns minutos, portanto, não é um problema de tempo.

Eu tenho o código de transação, mas ele não é executado antes da chamada Close (). Portanto, tenho quase certeza de que não é uma transação aberta. Os comandos sql entre abrir e fechar são apenas seleções.

ProcMon mostra meu programa e meu antivírus olhando o arquivo de banco de dados. Ele não mostra meu programa liberando o arquivo db após o close ().

Visual Studio 2010, C #, System.Data.SQLite versão 1.0.77.0, Win7

Eu vi um bug de dois anos assim, mas o changelog diz que foi consertado.

Há mais alguma coisa que eu possa verificar? Existe uma maneira de obter uma lista de quaisquer comandos ou transações abertas?


Novo código funcional:

 db.Close();
 GC.Collect();   // yes, really release the db

 bool worked = false;
 int tries = 1;
 while ((tries < 4) && (!worked))
 {
    try
    {
       Thread.Sleep(tries * 100);
       File.Delete(filename);
       worked = true;
    }
    catch (IOException e)   // delete only throws this on locking
    {
       tries++;
    }
 }
 if (!worked)
    throw new IOException("Unable to close file" + filename);
Tom Cerul
fonte
Você tentou: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Ao usar sqlite-net , você pode usar SQLiteAsyncConnection.ResetPool(), consulte este problema para obter detalhes.
Uwe Keim

Respostas:

109

Encontrei o mesmo problema há algum tempo enquanto escrevia uma camada de abstração de banco de dados para C # e na verdade nunca descobri qual era o problema. Acabei lançando uma exceção quando você tentou excluir um banco de dados SQLite usando minha biblioteca.

De qualquer forma, esta tarde eu estava examinando tudo de novo e pensei em tentar descobrir por que estava fazendo isso de uma vez por todas, então aqui está o que descobri até agora.

O que acontece quando você chama SQLiteConnection.Close()é que (junto com uma série de verificações e outras coisas) o SQLiteConnectionHandleque aponta para a instância do banco de dados SQLite é descartado. Isso é feito por meio de uma chamada para SQLiteConnectionHandle.Dispose(), no entanto, isso não libera realmente o ponteiro até que o Coletor de lixo do CLR execute alguma coleta de lixo. Como SQLiteConnectionHandlesubstitui a CriticalHandle.ReleaseHandle()função a ser chamada sqlite3_close_interop()(por meio de outra função), isso não fecha o banco de dados.

Do meu ponto de vista, esta é uma maneira muito ruim de fazer as coisas, já que o programador não tem certeza de quando o banco de dados será fechado, mas é assim que foi feito, então acho que temos que conviver com isso por enquanto ou cometer algumas alterações em System.Data.SQLite. Quaisquer voluntários são bem-vindos, infelizmente não tenho tempo para fazê-lo antes do próximo ano.

TL; DR A solução é forçar um GC após sua chamada para SQLiteConnection.Close()e antes de sua chamada para File.Delete().

Aqui está o código de exemplo:

string filename = "testFile.db";
SQLiteConnection connection = new SQLiteConnection("Data Source=" + filename + ";Version=3;");
connection.Close();
GC.Collect();
GC.WaitForPendingFinalizers();
File.Delete(filename);

Boa sorte com isso e espero que ajude

Benjamin Pannell
fonte
1
Sim! Obrigado! Parece que o GC pode precisar de um pouco para fazer seu trabalho.
Tom Cerul
1
Você também pode querer dar uma olhada em C # SQLite, acabei de mover todo o meu código para usá-lo. Claro, se você estiver executando algo crítico de desempenho, então C é provavelmente mais rápido que C #, mas eu sou um fã de código gerenciado ...
Benjamin Pannell
1
Eu sei que isso é antigo, mas obrigado por me poupar um pouco de dor. Esse bug também afeta a compilação do SQLite para Windows Mobile / Compact Framework.
StrayPointer
2
Ótimo trabalho! Resolvido meu problema imediatamente. Em 11 anos de desenvolvimento em C #, nunca precisei usar GC.Collect: este é o primeiro exemplo que sou forçado a fazer isso.
Pilsator de
10
GC.Collect (); funciona, mas System.Data.SQLite.SQLiteConnection.ClearAllPools (); lida com o problema usando a API da biblioteca.
Aaron Hudon
57

Simplesmente GC.Collect()não funcionou para mim.

Tive que adicionar GC.WaitForPendingFinalizers()depois GC.Collect()para prosseguir com a exclusão do arquivo.

Batiati
fonte
5
Isso não é tão surpreendente, GC.Collect()apenas inicia uma coleta de lixo que é assíncrona, então, para ter certeza de que tudo foi limpo, você terá que esperar por isso explicitamente.
ChrisWue
2
Eu experimentei o mesmo, tive que adicionar o GC.WaitForPendingFinalizers (). Isso foi em 1.0.103
Vort3x
18

No meu caso, eu estava criando SQLiteCommandobjetos sem descartá-los explicitamente.

var command = connection.CreateCommand();
command.CommandText = commandText;
value = command.ExecuteScalar();

Envolvi meu comando em uma usingdeclaração e resolveu meu problema.

static public class SqliteExtensions
{
    public static object ExecuteScalar(this SQLiteConnection connection, string commandText)
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = commandText;
            return command.ExecuteScalar();
        }
    }
}

A usinginstrução garante que Dispose seja chamado mesmo se ocorrer uma exceção.

Então, é muito mais fácil executar comandos também.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
fonte
6
Eu recomendo fortemente não engolir exceções como esta
Tom McKearney
16

Tive um problema semelhante, embora a solução do coletor de lixo não o tenha corrigido.

Encontrado descartar SQLiteCommande SQLiteDataReaderobjetos após o uso me salvou usando o coletor de lixo em tudo.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
cartola
fonte
2
Exatamente. Certifique-se de descartar TODOS, SQLiteCommandmesmo se você reciclar uma SQLiteCommandvariável posteriormente.
Bruno Bieri
Isso funcionou para mim. Eu também me certifiquei de descartar todas as transações.
Jay-Nicolas Hackleman
1
Ótimo! Você me economizou um pouco de tempo. Ele corrigiu o erro quando adicionei command.Dispose();a todos os SQLiteCommandque foram executados.
Ivan B
Além disso, certifique-se de liberar (ou seja .Dispose()) outros objetos como SQLiteTransaction, se houver.
Ivan B
13

O seguinte funcionou para mim:

MySQLiteConnection.Close();
SQLite.SQLiteConnection.ClearAllPools()

Mais informações : As conexões são agrupadas pelo SQLite para melhorar o desempenho. Isso significa que quando você chama o método Close em um objeto de conexão, a conexão com o banco de dados ainda pode estar ativa (em segundo plano) para que o próximo método Open se torne mais rápido. você não quer mais uma nova conexão, chamar ClearAllPools fecha todas as conexões que estão ativas em segundo plano e os identificadores de arquivo para o arquivo db são liberados. Então, o arquivo db pode ser removido, excluído ou usado por outro processo.

Arvin
fonte
1
Você poderia acrescentar uma explicação de por que esta é uma boa solução para o problema.
Matas Vaitkevicius
Você também pode usar SQLiteConnectionPool.Shared.Reset(). Isso fechará todas as conexões abertas. Em particular, esta é uma solução se você usar o SQLiteAsyncConnectionque não possui um Close()método.
Lorenzo Polidori
9

Eu estava tendo um problema semelhante, tentei a solução com GC.Collect, mas, conforme observado, pode levar muito tempo para que o arquivo não seja bloqueado.

Eu encontrei uma solução alternativa que envolve o descarte dos SQLiteCommands subjacentes nos TableAdapters, consulte esta resposta para obter informações adicionais.

Edymtt
fonte
você estava certo! Em alguns casos, o simples 'GC.Collect' funcionou para mim, em outros eu tive que descartar quaisquer SqliteCommands associados à conexão antes de chamar GC.Collect ou então não funcionaria!
Eitan HS
1
Chamar Dispose no SQLiteCommand funcionou para mim. Como um comentário à parte - se você está ligando para GC.Collect, você está fazendo algo errado.
Natalie Adams
@NathanAdams ao trabalhar com EntityFramework, não há um único objeto de comando que você possa descartar. Portanto, o próprio EntityFramework ou o SQLite for EF wrapper está fazendo algo errado também.
springy76
Sua resposta deve ser a correta. Muito obrigado.
Ahmed Shamel,
5

Tente isto ... este tenta todos os códigos acima ... funcionou para mim

    Reader.Close()
    connection.Close()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    command.Dispose()
    SQLite.SQLiteConnection.ClearAllPools()

espero que ajude

Bishnu Dev
fonte
1
WaitForPendingFinalizers fez toda a diferença para mim
Todd,
5

Tenho tido o mesmo problema com EF e System.Data.Sqlite.

Para mim, descobri SQLiteConnection.ClearAllPools()e GC.Collect()reduziria a frequência com que o bloqueio de arquivos acontecia, mas ainda acontecia ocasionalmente (cerca de 1% das vezes).

Estive investigando e parece que alguns SQLiteCommands que o EF cria não são descartados e ainda têm sua propriedade Connection definida para a conexão fechada. Tentei descartá-los, mas o Entity Framework lançaria uma exceção durante a próxima DbContextleitura - parece que o EF às vezes ainda os usa após o fechamento da conexão.

Minha solução foi garantir que a propriedade Connection fosse definida para Nullquando a conexão fosse encerrada nesses SQLiteCommands. Isso parece ser suficiente para liberar o bloqueio do arquivo. Estive testando o código abaixo e não vi nenhum problema de bloqueio de arquivo após alguns milhares de testes:

public static class ClearSQLiteCommandConnectionHelper
{
    private static readonly List<SQLiteCommand> OpenCommands = new List<SQLiteCommand>();

    public static void Initialise()
    {
        SQLiteConnection.Changed += SqLiteConnectionOnChanged;
    }

    private static void SqLiteConnectionOnChanged(object sender, ConnectionEventArgs connectionEventArgs)
    {
        if (connectionEventArgs.EventType == SQLiteConnectionEventType.NewCommand && connectionEventArgs.Command is SQLiteCommand)
        {
            OpenCommands.Add((SQLiteCommand)connectionEventArgs.Command);
        }
        else if (connectionEventArgs.EventType == SQLiteConnectionEventType.DisposingCommand && connectionEventArgs.Command is SQLiteCommand)
        {
            OpenCommands.Remove((SQLiteCommand)connectionEventArgs.Command);
        }

        if (connectionEventArgs.EventType == SQLiteConnectionEventType.Closed)
        {
            var commands = OpenCommands.ToList();
            foreach (var cmd in commands)
            {
                if (cmd.Connection == null)
                {
                    OpenCommands.Remove(cmd);
                }
                else if (cmd.Connection.State == ConnectionState.Closed)
                {
                    cmd.Connection = null;
                    OpenCommands.Remove(cmd);
                }
            }
        }
    }
}

Para usar, basta chamar ClearSQLiteCommandConnectionHelper.Initialise();no início do carregamento do aplicativo. Isso manterá uma lista de comandos ativos e definirá sua conexão para Nullquando eles apontarem para uma conexão que está fechada.

Hallupa
fonte
Eu também tive que definir a conexão como null na parte DisposingCommand disso, ou ocasionalmente obteria ObjectDisposedExceptions.
Elliot,
Esta é uma resposta subestimada em minha opinião. Isso resolveu meus problemas de limpeza que eu não conseguia fazer por causa da camada EF. Estou muito feliz em usar isso em vez daquele hack feio do GC. Obrigado!
Jason Tyler,
Se você usar esta solução em um ambiente multithread, a Lista OpenCommands deve ser [ThreadStatic].
Bero
3

Usar GC.WaitForPendingFinalizers()

Exemplo:

Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Sharad Kishor
fonte
3

Teve um problema semelhante. Ligar para o Garbage Collector não me ajudou. Depois, encontrei uma maneira de resolver o problema

O autor também escreveu que fez consultas SELECT ao banco de dados antes de tentar excluí-lo. Eu tenho a mesma situação.

Eu tenho o seguinte código:

SQLiteConnection bc;
string sql;
var cmd = new SQLiteCommand(sql, bc);
SQLiteDataReader reader = cmd.ExecuteReader();
reader.Read();
reader.Close(); // when I added that string, the problem became solved.

Além disso, não preciso fechar a conexão com o banco de dados e chamar o Garbage Collector. Tudo que eu tive que fazer é fechar o leitor que foi criado durante a execução da consulta SELECT

Schullz
fonte
2

Acredito que a chamada para SQLite.SQLiteConnection.ClearAllPools()é a solução mais limpa. Pelo que eu sei, não é adequado chamar manualmente GC.Collect()no ambiente WPF. Embora eu não tenha percebido o problema até ter atualizado para System.Data.SQLite1.0.99.0 em 3/2016

Jona Varque
fonte
2

Talvez você nem precise lidar com GC. Por favor, verifique se tudo sqlite3_prepareestá finalizado.

Para cada um sqlite3_prepare, você precisa de um correspondente sqlite3_finalize.

Se você não finalizar corretamente, sqlite3_closenão fechará a conexão.

João monteiro
fonte
1

Eu estava lutando com um problema semelhante. Que vergonha ... finalmente percebi que o Reader não estava fechado. Por alguma razão, pensei que o Reader será fechado quando a conexão correspondente for fechada. Obviamente, GC.Collect () não funcionou para mim.
Envolver o Reader com a instrução "using: também é uma boa ideia. Aqui está um código de teste rápido.

static void Main(string[] args)
{
    try
    {
        var dbPath = "myTestDb.db";
        ExecuteTestCommand(dbPath);
        File.Delete(dbPath);
        Console.WriteLine("DB removed");
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
    Console.Read();
}

private static void ExecuteTestCommand(string dbPath)
{
    using (var connection = new SQLiteConnection("Data Source=" + dbPath + ";"))
    {
        using (var command = connection.CreateCommand())
        {
            command.CommandText = "PRAGMA integrity_check";
            connection.Open();
            var reader = command.ExecuteReader();
            if (reader.Read())
                Console.WriteLine(reader.GetString(0));

            //without next line database file will remain locked
            reader.Close();
        }
    }   
}
Mike Znaet
fonte
0

Eu estava usando SQLite 1.0.101.0 com EF6 e tendo problemas com o arquivo sendo bloqueado após todas as conexões e entidades descartadas.

Isso piorou com atualizações do EF mantendo o banco de dados bloqueado após a conclusão. GC.Collect () foi a única solução alternativa que ajudou e eu estava começando a me desesperar.

Em desespero, experimentei o ClearSQLiteCommandConnectionHelper de Oliver Wickenden (veja sua resposta de 8 de julho). Fantástico. Todos os problemas de bloqueio desapareceram! Obrigado Oliver.

Tony Sullivan
fonte
Acho que isso deveria ser um comentário em vez de uma resposta
Kevin Wallis
1
Kevin, eu concordo, mas não tive permissão para comentar porque preciso de 50 reputação (aparentemente).
Tony Sullivan
0

Esperar pelo coletor de lixo pode não liberar o banco de dados o tempo todo e isso aconteceu comigo. Quando algum tipo de Exceção ocorre no banco de dados SQLite, por exemplo, tentando inserir uma linha com valor existente para PrimaryKey, ele manterá o arquivo do banco de dados até que você o descarte. O código a seguir captura a exceção SQLite e cancela o comando problemático.

SQLiteCommand insertCommand = connection.CreateCommand();
try {
    // some insert parameters
    insertCommand.ExecuteNonQuery();
} catch (SQLiteException exception) {
    insertCommand.Cancel();
    insertCommand.Dispose();
}

Se você não manipular as exceções de comandos problemáticos, o Garbage Collector não poderá fazer nada a respeito, porque há algumas exceções não manipuladas sobre esses comandos, portanto, não são lixo. Este método de tratamento funcionou bem para mim com a espera pelo coletor de lixo.

Muhammed Kadir
fonte
0

Isso funciona para mim, mas percebi que às vezes os arquivos de diário -wal -shm não são excluídos quando o processo é fechado. Se você deseja que o SQLite remova os arquivos -wal -shm quando todas as conexões forem fechadas, a última conexão fechada DEVE SER não somente leitura. Espero que isso ajude alguém.

Ekalchev
fonte
0

Melhor resposta que funcionou para mim.

dbConnection.Close();
System.Data.SQLite.SQLiteConnection.ClearAllPools();

GC.Collect();
GC.WaitForPendingFinalizers();

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Kazem Fallahi
fonte