System.Data.SQLite Close () nie zwalnia pliku bazy danych

97

Mam problem z zamknięciem bazy danych przed próbą usunięcia pliku. Kod jest po prostu

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

A Delete zgłasza wyjątek, że plik jest nadal używany. Po kilku minutach ponownie wypróbowałem funkcję Delete () w debugerze, więc nie jest to problem z synchronizacją.

Mam kod transakcji, ale w ogóle nie działa przed wywołaniem Close (). Więc jestem prawie pewien, że nie jest to otwarta transakcja. Polecenia sql między otwieraniem a zamykaniem to tylko zaznaczenia.

ProcMon pokazuje mój program i mój program antywirusowy przeglądający plik bazy danych. Nie pokazuje, że mój program zwalnia plik db po wykonaniu funkcji close ().

Visual Studio 2010, C #, System.Data.SQLite w wersji 1.0.77.0, Win7

Widziałem taki błąd sprzed dwóch lat, ale dziennik zmian mówi, że został naprawiony.

Czy jest coś jeszcze, co mogę sprawdzić? Czy jest sposób, aby uzyskać listę otwartych poleceń lub transakcji?


Nowy, działający kod:

 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
źródło
Czy próbowałeś: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Korzystając z sqlite-net , możesz użyć SQLiteAsyncConnection.ResetPool(), zobacz ten problem, aby uzyskać szczegółowe informacje.
Uwe Keim

Odpowiedzi:

113

Napotkałem ten sam problem jakiś czas temu podczas pisania warstwy abstrakcji DB dla C # i nigdy nie udało mi się znaleźć przyczyny problemu. Skończyło się na tym, że rzuciłem wyjątek, gdy próbowałeś usunąć bazę danych SQLite za pomocą mojej biblioteki.

W każdym razie tego popołudnia przeglądałem to wszystko jeszcze raz i pomyślałem, że spróbuję dowiedzieć się, dlaczego robi to raz na zawsze, więc oto, co znalazłem do tej pory.

To, co się dzieje, kiedy wywołujesz, SQLiteConnection.Close()jest takie, że (wraz z wieloma sprawdzeniami i innymi rzeczami), SQLiteConnectionHandleco wskazuje na instancję bazy danych SQLite, zostaje usunięte. Odbywa się to poprzez wywołanie funkcji SQLiteConnectionHandle.Dispose(), jednak w rzeczywistości nie zwalnia to wskaźnika, dopóki moduł zbierający elementy bezużyteczne środowiska CLR nie wykona pewnych operacji czyszczenia pamięci. Ponieważ SQLiteConnectionHandleprzesłania CriticalHandle.ReleaseHandle()funkcję do wywołania sqlite3_close_interop()(za pośrednictwem innej funkcji), nie zamyka to bazy danych.

Z mojego punktu widzenia jest to bardzo zły sposób na robienie rzeczy, ponieważ programista nie jest tak naprawdę pewien, kiedy baza danych zostanie zamknięta, ale tak to zostało zrobione, więc myślę, że musimy na razie z tym żyć lub zatwierdzić kilka zmian w System.Data.SQLite. Zapraszamy do tego wszystkich wolontariuszy, niestety nie mam na to czasu przed przyszłym rokiem.

TL; DR Rozwiązaniem jest wymuszenie GC po wywołaniu SQLiteConnection.Close()i przed wywołaniem File.Delete().

Oto przykładowy kod:

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

Powodzenia i mam nadzieję, że to pomoże

Benjamin Pannell
źródło
1
Tak! Dziękuję Ci! Wygląda na to, że GC może potrzebować trochę, aby wykonać swoją pracę.
Tom Cerul
1
Możesz również spojrzeć na C # SQLite, właśnie przeniosłem cały mój kod do używania go. Oczywiście, jeśli uruchamiasz coś krytycznego dla wydajności, C jest prawdopodobnie szybszy niż C #, ale jestem fanem kodu zarządzanego ...
Benjamin Pannell
1
Wiem, że to stare, ale dziękuję za zaoszczędzenie mi bólu. Ten błąd dotyczy również wersji SQLite dla systemu Windows Mobile / Compact Framework.
StrayPointer
2
Świetna robota! Natychmiast rozwiązałem mój problem. W ciągu 11 lat programowania w C # nigdy nie musiałem używać GC.Collect: To jest pierwszy przykład, do którego jestem zmuszony.
Pilsator
10
GC.Collect (); działa, ale System.Data.SQLite.SQLiteConnection.ClearAllPools (); rozwiązuje problem za pomocą API biblioteki.
Aaron Hudon
57

Po prostu GC.Collect()nie działało dla mnie.

Musiałem dodać GC.WaitForPendingFinalizers()po GC.Collect(), aby kontynuować usuwanie pliku.

Batiati
źródło
5
Nie jest to aż tak zaskakujące, GC.Collect()po prostu uruchamia usuwanie śmieci, które jest asynchroniczne, więc aby upewnić się, że wszystko zostało wyczyszczone, musisz wyraźnie poczekać na to.
ChrisWue,
2
Doświadczyłem tego samego, musiałem dodać GC.WaitForPendingFinalizers (). To było w 1.0.103
Vort3x
18

W moim przypadku tworzyłem SQLiteCommandobiekty bez ich jawnego usuwania.

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

Zawinąłem polecenie w usingoświadczeniu i rozwiązało to mój problem.

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

usingOświadczenie zapewnia, że nazywa się wyrzucić, nawet jeśli wystąpi wyjątek.

Wtedy też dużo łatwiej jest wykonywać polecenia.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
źródło
6
Bardzo odradzam połykanie wyjątków takich jak ten
Tom McKearney
17

Miałem podobny problem, chociaż rozwiązanie odśmiecania go nie rozwiązało.

Znalezione utylizacja SQLiteCommandi SQLiteDataReaderprzedmioty po użyciu w ogóle uratowały mnie przy użyciu garbage collectora.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
kulka
źródło
2
Dokładnie. Upewnij się, że pozbędziesz się WSZYSTKICH, SQLiteCommandnawet jeśli SQLiteCommandpóźniej ponownie wykorzystasz zmienną.
Bruno Bieri
To zadziałało dla mnie. Zadbałem również o pozbycie się wszelkich transakcji.
Jay-Nicolas Hackleman
1
Świetny! Zaoszczędziłeś mi sporo czasu. Naprawił błąd, gdy dodałem command.Dispose();do każdego SQLiteCommand, co zostało wykonane.
Ivan B
Upewnij się również, że .Dispose()zwalniasz (tj. ) Inne obiekty, takie jak SQLiteTransaction, jeśli takie masz.
Ivan B
14

Pracowały dla mnie:

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

Więcej informacji : Połączenia są gromadzone w puli przez SQLite w celu zwiększenia wydajności, co oznacza, że ​​gdy wywołasz metodę Close na obiekcie połączenia, połączenie z bazą danych może nadal być aktywne (w tle), dzięki czemu następna metoda Open stanie się szybsza. nie chcesz już nowego połączenia, wywołanie ClearAllPools zamyka wszystkie połączenia, które są aktywne w tle, a uchwyty do pliku db zostają zwolnione. Następnie plik db może zostać usunięty, skasowany lub użyty przez inny proces.

Arvin
źródło
1
Czy mógłbyś dodać wyjaśnienie, dlaczego jest to dobre rozwiązanie problemu.
Matas Vaitkevicius,
Możesz także użyć SQLiteConnectionPool.Shared.Reset(). Spowoduje to zamknięcie wszystkich otwartych połączeń. W szczególności jest to rozwiązanie, jeśli używasz, SQLiteAsyncConnectionktóre nie ma Close()metody.
Lorenzo Polidori
9

Miałem podobny problem, próbowałem go rozwiązać GC.Collect ale jak wspomniano, może minąć dużo czasu, zanim plik nie zostanie zablokowany.

Znalazłem alternatywne rozwiązanie, które obejmuje pozbycie się bazowych SQLiteCommands w TableAdapters, zobacz tę odpowiedź, aby uzyskać dodatkowe informacje.

edymtt
źródło
miałeś rację! W niektórych przypadkach działało dla mnie proste „GC.Collect”, w innych musiałem pozbyć się wszelkich SqliteCommands skojarzonych z połączeniem przed wywołaniem GC.Collect, bo inaczej to nie zadziała!
Eitan HS
1
Wywołanie Dispose w SQLiteCommand zadziałało dla mnie. Na marginesie - jeśli dzwonisz do GC.Collect, robisz coś złego.
Natalie Adams
@NathanAdams podczas pracy z EntityFramework nie ma ani jednego obiektu polecenia, którego można by kiedykolwiek pozbyć. Więc albo sam EntityFramework, albo SQLite for EF wrapper też robi coś złego.
springy76
Twoja odpowiedź powinna być prawidłowa. Wielkie dzięki.
Ahmed Shamel
5

Spróbuj tego ... ten próbuje wszystkich powyższych kodów ... zadziałał dla mnie

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

Mam nadzieję, że to pomoże

Bishnu Dev
źródło
1
WaitForPendingFinalizers zrobiło dla mnie różnicę
Todd
5

Miałem ten sam problem z EF i System.Data.Sqlite.

Dla mnie znalazłem SQLiteConnection.ClearAllPools()i GC.Collect()zmniejszyłem częstotliwość blokowania pliku, ale czasami zdarzało się to (około 1% czasu).

Badałem i wydaje się, że niektóre SQLiteCommandpliki, które tworzy EF, nie są usuwane i nadal mają właściwość Connection ustawioną na zamknięte połączenie. Próbowałem je usunąć, ale Entity Framework zgłosiłby wyjątek podczas następnego DbContextodczytu - wydaje się, że EF czasami nadal używa ich po zamknięciu połączenia.

Moim rozwiązaniem było upewnienie się, że właściwość Connection jest ustawiona na, Nullgdy połączenie zostanie zamknięte na tych SQLiteCommanddyskach. Wydaje się, że to wystarczy, aby zwolnić blokadę pliku. Testowałem poniższy kod i po kilku tysiącach testów nie widziałem żadnych problemów z blokowaniem plików:

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);
                }
            }
        }
    }
}

Aby użyć wystarczy zadzwonić ClearSQLiteCommandConnectionHelper.Initialise();na początku ładowania aplikacji. Spowoduje to zachowanie listy aktywnych poleceń i ustawi ich połączenie, Nullgdy wskażą połączenie, które jest zamknięte.

Hallupa
źródło
Musiałem również ustawić połączenie na null w części DisposingCommand tego lub czasami otrzymywałem ObjectDisposedExceptions.
Elliot
Moim zdaniem jest to niedoceniana odpowiedź. Rozwiązało to moje problemy z czyszczeniem, których nie mogłem zrobić samodzielnie z powodu warstwy EF. Bardzo się cieszę, że mogę to wykorzystać do tego brzydkiego hacka GC. Dziękuję Ci!
Jason Tyler
Jeśli używasz tego rozwiązania w środowisku wielowątkowym, lista OpenCommands powinna mieć wartość [ThreadStatic].
Bero
4

Posługiwać się GC.WaitForPendingFinalizers()

Przykład:

Con.Close();  
GC.Collect();`
GC.WaitForPendingFinalizers();
File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Sharad Kishor
źródło
3

Miałem podobny problem. Dzwonienie do Garbage Collectora mi nie pomogło. LAter znalazłem sposób na rozwiązanie problemu

Autor napisał również, że wykonał zapytania SELECT do tej bazy danych przed próbą jej usunięcia. Mam taką samą sytuację.

Mam następujący kod:

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.

Nie muszę też zamykać połączenia z bazą danych i dzwonić do Garbage Collectora. Wystarczyło zamknąć czytnik, który powstał podczas wykonywania zapytania SELECT

Schullz
źródło
2

Uważam, że wezwanie SQLite.SQLiteConnection.ClearAllPools()jest najczystszym rozwiązaniem. O ile wiem, ręczne wywoływanie GC.Collect()w środowisku WPF nie jest właściwe . Chociaż nie zauważyłem problemu, dopóki nie zaktualizowałem do System.Data.SQLite1.0.99.0 w 3/2016

Jona Varque
źródło
2

Może wcale nie musisz mieć do czynienia z GC. Proszę sprawdzić, czy wszystko sqlite3_preparejest sfinalizowane.

Do każdego sqlite3_preparepotrzebujesz korespondenta sqlite3_finalize.

Jeśli nie zakończysz poprawnie, sqlite3_closenie zamyka połączenia.

João Monteiro
źródło
1

Zmagałem się z podobnym problemem. Wstyd mi ... Wreszcie zdałem sobie sprawę, że Reader nie jest zamknięty. Z jakiegoś powodu myślałem, że Czytnik zostanie zamknięty po zamknięciu odpowiedniego połączenia. Oczywiście GC.Collect () nie działał dla mnie.
Dobrym pomysłem jest również umieszczenie w Czytniku instrukcji using: Oto kod szybkiego testu.

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
źródło
0

Używałem SQLite 1.0.101.0 z EF6 i miałem problem z zablokowaniem pliku po usunięciu wszystkich połączeń i jednostek.

Sytuacja pogorszyła się wraz z aktualizacjami z EF, które blokowały bazę danych po ich zakończeniu. GC.Collect () było jedynym obejściem, które pomogło i zacząłem rozpaczać.

W desperacji wypróbowałem ClearSQLiteCommandConnectionHelper Olivera Wickendena (patrz jego odpowiedź z 8 lipca). Fantastyczny. Wszystkie problemy z blokowaniem zniknęły! Dzięki Oliver.

Tony Sullivan
źródło
Myślę, że to powinien być komentarz zamiast odpowiedzi
Kevin Wallis
1
Kevin, zgadzam się, ale nie pozwolono mi komentować, ponieważ potrzebuję 50 punktów reputacji (podobno).
Tony Sullivan
0

Czekanie na Garbage Collector może nie wypuszczać bazy danych przez cały czas i to mi się przydarzyło. Kiedy jakiś rodzaj wyjątku wystąpi w bazie danych SQLite, na przykład próbując wstawić wiersz z istniejącą wartością dla PrimaryKey, plik bazy danych będzie przechowywany do momentu jego usunięcia. Poniższy kod wychwytuje wyjątek SQLite i anuluje problematyczne polecenie.

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

Jeśli nie poradzisz sobie z wyjątkami dla problematycznych poleceń, Garbage Collector nie może nic z nimi zrobić, ponieważ istnieją nieobsłużone wyjątki dotyczące tych poleceń, więc nie są śmieciami. Ta metoda obsługi działała dobrze w przypadku czekania na śmieciarkę.

Muhammed Kadir
źródło
0

U mnie to działa, ale zauważyłem, że czasami pliki dziennika -wal -shm nie są usuwane po zamknięciu procesu. Jeśli chcesz, aby SQLite usuwał pliki -wal -shm, gdy wszystkie połączenia są zamknięte, ostatnie zamknięte połączenie MUSI BYĆ nie tylko do odczytu. Mam nadzieję, że to komuś pomoże.

ekalchev
źródło
0

Najlepsza odpowiedź, która mi pomogła.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
kazem fallahi
źródło