System.Data.SQLite Close () tidak merilis file database

97

Saya mengalami masalah saat menutup database saya sebelum mencoba menghapus file. Kode itu adil

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

Dan Hapus memunculkan pengecualian bahwa file tersebut masih digunakan. Saya telah mencoba kembali Delete () di debugger setelah beberapa menit, jadi ini bukan masalah waktu.

Saya memiliki kode transaksi tetapi tidak berjalan sama sekali sebelum panggilan Close (). Jadi saya cukup yakin ini bukan transaksi terbuka. Perintah sql antara buka dan tutup hanya memilih.

ProcMon menunjukkan program saya dan antivirus saya melihat file database. Itu tidak menunjukkan program saya merilis file db setelah close ().

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

Saya melihat bug berumur dua tahun seperti ini tetapi changelog mengatakan itu sudah diperbaiki.

Apakah ada hal lain yang bisa saya periksa? Apakah ada cara untuk mendapatkan daftar perintah atau transaksi yang terbuka?


Baru, kode yang berfungsi:

 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
sumber
Apakah Anda mencoba: myconnection.Close (); myconnection.Dispose (); ?
UGEEN
1
Saat menggunakan sqlite-net , Anda dapat menggunakan SQLiteAsyncConnection.ResetPool(), lihat masalah ini untuk detailnya.
Uwe Keim

Jawaban:

113

Mengalami masalah yang sama beberapa waktu yang lalu saat menulis lapisan abstraksi DB untuk C # dan saya tidak pernah benar-benar menemukan apa masalahnya. Saya baru saja melempar pengecualian ketika Anda mencoba menghapus SQLite DB menggunakan perpustakaan saya.

Bagaimanapun, sore ini saya melihat semuanya lagi dan berpikir saya akan mencoba dan mencari tahu mengapa melakukannya sekali dan untuk semua, jadi inilah yang saya temukan sejauh ini.

Apa yang terjadi ketika Anda memanggil SQLiteConnection.Close()adalah (bersama dengan sejumlah pemeriksaan dan hal-hal lain) SQLiteConnectionHandleyang menunjuk ke contoh database SQLite dibuang. Ini dilakukan melalui panggilan ke SQLiteConnectionHandle.Dispose(), namun ini tidak benar-benar melepaskan penunjuk sampai Kolektor Sampah CLR melakukan beberapa pengumpulan sampah. Karena SQLiteConnectionHandlemengganti CriticalHandle.ReleaseHandle()fungsi yang akan dipanggil sqlite3_close_interop()(melalui fungsi lain) ini tidak menutup database.

Dari sudut pandang saya, ini adalah cara yang sangat buruk untuk melakukan sesuatu karena programmer sebenarnya tidak yakin kapan database ditutup, tapi begitulah cara melakukannya, jadi saya kira kita harus menerimanya untuk saat ini, atau berkomitmen sedikit perubahan pada System.Data.SQLite. Semua sukarelawan dipersilakan untuk melakukannya, sayangnya saya kehabisan waktu sebelum tahun depan.

TL; DR Solusinya adalah dengan memaksa GC setelah panggilan Anda ke SQLiteConnection.Close()dan sebelum panggilan Anda ke File.Delete().

Berikut ini contoh kode:

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

Semoga berhasil, dan saya harap ini membantu

Benjamin Pannell
sumber
1
Iya! Terima kasih! Sepertinya GC mungkin perlu sedikit menyelesaikan pekerjaannya.
Tom Cerul
1
Anda mungkin juga ingin melihat C # SQLite, saya baru saja memindahkan semua kode saya untuk menggunakannya. Tentu saja, jika Anda menjalankan sesuatu kinerja yang kritis maka C mungkin lebih cepat daripada C #, tapi saya penggemar kode terkelola ...
Benjamin Pannell
1
Saya tahu ini sudah tua, tapi terima kasih telah menyelamatkan saya dari rasa sakit. Bug ini juga memengaruhi build Windows Mobile / Compact Framework dari SQLite.
StrayPointer
2
Kerja bagus! Memecahkan masalah saya segera. Dalam 11 tahun pengembangan C #, saya tidak pernah perlu menggunakan GC. Kumpulkan: Sekarang ini adalah contoh pertama saya terpaksa melakukannya.
Pilsator
10
GC.Collect (); berfungsi, tetapi System.Data.SQLite.SQLiteConnection.ClearAllPools (); menangani masalah menggunakan API perpustakaan.
Aaron Hudon
57

Hanya GC.Collect()saja tidak berhasil untukku.

Saya harus menambahkan GC.WaitForPendingFinalizers()setelah GC.Collect()untuk melanjutkan dengan penghapusan file.

Batiati
sumber
5
Ini tidak mengherankan, GC.Collect()hanya memulai pengumpulan sampah yang tidak sinkron jadi untuk memastikan semua telah dibersihkan, Anda harus menunggunya secara eksplisit.
ChrisWue
2
Saya mengalami hal yang sama, harus menambahkan GC.WaitForPendingFinalizers (). Ini terjadi di 1.0.103
Vort3x
18

Dalam kasus saya, saya membuat SQLiteCommandobjek tanpa secara eksplisit membuangnya.

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

Saya membungkus perintah saya dalam sebuah usingpernyataan dan itu memperbaiki masalah saya.

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

The usingPernyataan memastikan bahwa Buang disebut bahkan jika terjadi pengecualian.

Maka jauh lebih mudah untuk menjalankan perintah juga.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed
Nate
sumber
6
Saya sangat menyarankan agar tidak menelan pengecualian seperti ini
Tom McKearney
17

Punya masalah serupa, meskipun solusi pengumpul sampah tidak memperbaikinya.

Ditemukan pembuangan SQLiteCommanddan SQLiteDataReaderobjek setelah digunakan menyelamatkan saya menggunakan pengumpul sampah sama sekali.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();
peluru
sumber
2
Persis. Pastikan Anda membuang SETIAP SQLiteCommandbahkan jika Anda mendaur ulang SQLiteCommandvariabel nanti.
Bruno Bieri
Ini berhasil untuk saya. Saya juga memastikan untuk membuang semua transaksi.
Jay-Nicolas Hackleman
1
Bagus! Anda menghemat banyak waktu saya. Itu memperbaiki kesalahan ketika saya menambahkan command.Dispose();ke setiap SQLiteCommandyang dieksekusi.
Ivan B
Selain itu, pastikan Anda melepaskan (yaitu .Dispose()) objek lain seperti SQLiteTransaction, jika Anda punya.
Ivan B
14

Yang berikut berhasil untuk saya:

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

Info selengkapnya : Koneksi dikumpulkan oleh SQLite untuk meningkatkan kinerja, artinya saat Anda memanggil metode Tutup pada objek koneksi, koneksi ke database mungkin masih hidup (di latar belakang) sehingga metode Buka berikutnya menjadi lebih cepat. Anda tidak menginginkan koneksi baru lagi, memanggil ClearAllPools akan menutup semua koneksi yang hidup di latar belakang dan file handle (s?) ke file db akan dirilis. Kemudian file db dapat dihapus, dihapus atau digunakan oleh proses lain.

Arvin
sumber
1
Bisakah Anda menambahkan penjelasan mengapa ini adalah solusi yang baik untuk masalah tersebut.
Matas Vaitkevicius
Anda juga bisa menggunakan SQLiteConnectionPool.Shared.Reset(). Ini akan menutup semua koneksi yang terbuka. Secara khusus, ini adalah solusi jika Anda menggunakan SQLiteAsyncConnectionyang tidak memiliki Close()metode.
Lorenzo Polidori
9

Saya mengalami masalah yang sama, saya sudah mencoba solusinya GC.Collecttetapi, seperti yang disebutkan, bisa memakan waktu lama sebelum file menjadi tidak terkunci.

Saya telah menemukan solusi alternatif yang melibatkan pembuangan yang mendasari SQLiteCommanddi TabelAdapters, lihat jawaban ini untuk informasi tambahan.

edymtt
sumber
kamu benar! Dalam beberapa kasus, 'GC.Collect' sederhana bekerja untuk saya, Di kasus lain saya harus membuang SqliteCommands yang terkait dengan koneksi sebelum memanggil GC.Collect atau tidak akan berfungsi!
Eitan HS
1
Memanggil Buang di SQLiteCommand berhasil untuk saya. Sebagai komentar samping - jika Anda menelepon GC.Collect, Anda melakukan sesuatu yang salah.
Natalie Adams
@NathanAdams saat bekerja dengan EntityFramework, tidak ada satu pun objek perintah yang dapat Anda buang. Jadi EntityFramework itu sendiri atau SQLite for EF wrapper juga melakukan kesalahan.
springy76
Jawaban Anda harus benar. Terima kasih banyak.
Ahmed Shamel
6

Saya mengalami masalah yang sama dengan EF dan System.Data.Sqlite.

Bagi saya, saya menemukan SQLiteConnection.ClearAllPools()dan GC.Collect()akan mengurangi seberapa sering penguncian file akan terjadi tetapi kadang-kadang masih terjadi (Sekitar 1% dari waktu).

Saya telah menyelidiki dan tampaknya beberapa SQLiteCommandyang EF buat tidak dibuang dan properti Koneksi mereka masih disetel ke koneksi tertutup. Saya mencoba membuang ini tetapi Entity Framework kemudian akan mengeluarkan pengecualian selama DbContextpembacaan berikutnya - tampaknya EF terkadang masih menggunakannya setelah koneksi ditutup.

Solusi saya adalah memastikan properti Connection diatur ke Nullsaat koneksi ditutup pada SQLiteCommands ini . Ini sepertinya cukup untuk membuka kunci file. Saya telah menguji kode di bawah ini dan tidak melihat masalah kunci file apa pun setelah beberapa ribu pengujian:

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

Untuk menggunakan panggil saja ClearSQLiteCommandConnectionHelper.Initialise();pada awal pemuatan aplikasi. Ini kemudian akan menyimpan daftar perintah aktif dan akan mengatur Koneksi Nullmereka ketika mereka menunjuk ke koneksi yang ditutup.

Hallupa
sumber
Saya juga harus mengatur koneksi ke null di bagian DisposingCommand ini, atau saya kadang-kadang akan mendapatkan ObjectDisposedExceptions.
Elliot
Ini adalah jawaban yang diremehkan menurut saya. Ini memecahkan masalah pembersihan saya yang tidak dapat saya lakukan sendiri karena lapisan EF. Sangat senang menggunakan ini untuk peretasan GC yang jelek itu. Terima kasih!
Jason Tyler
Jika Anda menggunakan solusi ini dalam lingkungan multithread, Daftar OpenCommands harus [ThreadStatic].
Bero
5

Coba ini ... yang ini mencoba semua kode di atas ... berhasil untuk saya

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

Semoga membantu

Bishnu Dev
sumber
1
WaitForPendingFinalizers membuat semua perbedaan bagi saya
Todd
4

Menggunakan GC.WaitForPendingFinalizers()

Contoh:

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

Punya masalah serupa. Memanggil Pengumpul Sampah tidak membantu saya. LAter Saya menemukan cara untuk memecahkan masalah

Penulis juga menulis bahwa dia melakukan kueri SELECT ke database itu sebelum mencoba menghapusnya. Saya memiliki situasi yang sama.

Saya memiliki kode berikut:

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.

Selain itu, saya tidak perlu menutup koneksi database dan memanggil Pengumpul Sampah. Yang harus saya lakukan adalah menutup pembaca yang dibuat saat menjalankan kueri SELECT

Schullz
sumber
2

Saya percaya panggilan ke SQLite.SQLiteConnection.ClearAllPools()adalah solusi terbersih. Sejauh yang saya tahu, tidak tepat untuk memanggil secara manual GC.Collect()di lingkungan WPF. Meskipun demikian, saya tidak menyadari masalahnya sampai saya meningkatkan ke System.Data.SQLite1.0.99.0 pada 3/2016

Jona Varque
sumber
2

Mungkin Anda tidak perlu berurusan dengan GC sama sekali. Tolong, periksa apakah semua sqlite3_preparesudah selesai.

Untuk masing-masing sqlite3_prepare, Anda membutuhkan seorang koresponden sqlite3_finalize.

Jika Anda tidak menyelesaikan dengan benar, sqlite3_closetidak akan menutup koneksi.

João Monteiro
sumber
1

Saya berjuang dengan masalah yang sama. Malu pada saya ... Saya akhirnya menyadari bahwa Reader tidak ditutup. Untuk beberapa alasan saya berpikir bahwa Pembaca akan ditutup saat koneksi terkait ditutup. Jelas, GC.Collect () tidak berhasil untuk saya.
Membungkus Pembaca dengan "menggunakan: pernyataan juga merupakan ide yang bagus. Ini adalah kode tes cepat.

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
sumber
0

Saya menggunakan SQLite 1.0.101.0 dengan EF6 dan mengalami masalah dengan file yang terkunci setelah semua koneksi dan entitas dibuang.

Ini menjadi lebih buruk dengan pembaruan dari EF yang menjaga basis data terkunci setelah mereka selesai. GC.Collect () adalah satu-satunya solusi yang membantu dan saya mulai putus asa.

Dalam keputusasaan, saya mencoba ClearSQLiteCommandConnectionHelper dari Oliver Wickenden (lihat jawabannya tanggal 8 Juli). Fantastis. Semua masalah penguncian hilang! Terima kasih Oliver.

Tony Sullivan
sumber
Saya pikir ini harus menjadi komentar, bukan jawaban
Kevin Wallis
1
Kevin, saya setuju, tetapi saya tidak diizinkan berkomentar karena saya membutuhkan 50 reputasi (tampaknya).
Tony Sullivan
0

Menunggu Pengumpul Sampah mungkin tidak merilis database sepanjang waktu dan itu terjadi pada saya. Ketika beberapa jenis Exception terjadi dalam database SQLite misalnya mencoba memasukkan baris dengan nilai yang ada untuk PrimaryKey itu akan menahan file database sampai Anda membuangnya. Kode berikut menangkap pengecualian SQLite dan membatalkan perintah yang bermasalah.

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

Jika Anda tidak menangani pengecualian perintah yang bermasalah daripada Pengumpul Sampah tidak dapat berbuat apa-apa karena ada beberapa pengecualian yang tidak tertangani tentang perintah ini sehingga tidak menjadi sampah. Metode penanganan ini bekerja dengan baik untuk saya dengan menunggu pemulung.

Muhammed Kadir
sumber
0

Ini berfungsi untuk saya tetapi saya perhatikan terkadang file jurnal -wal -shm tidak dihapus ketika proses ditutup. Jika Anda ingin SQLite menghapus file -wal -shm ketika semua koneksi ditutup, koneksi terakhir ditutup HARUS non-readonly. Semoga ini bisa membantu seseorang.

ekalchev
sumber
0

Jawaban terbaik yang berhasil untuk saya.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
kazem fallahi
sumber