System.Data.SQLite Close () veritabanı dosyasını yayınlamıyor


97

Dosyayı silme girişiminden önce veritabanımı kapatırken sorun yaşıyorum. Kod sadece

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

Ve Sil, dosyanın hala kullanımda olduğuna dair bir istisna atar. Birkaç dakika sonra hata ayıklayıcıdaki Delete () öğesini yeniden denedim, bu nedenle bu bir zamanlama sorunu değil.

İşlem kodum var ama Close () çağrısından önce hiç çalışmıyor. Bu yüzden açık bir işlem olmadığından oldukça eminim. Open ve close arasındaki sql komutları sadece seçimlerdir.

ProcMon, programımı ve antivirüsümü veritabanı dosyasına bakarken gösteriyor. Close () işleminden sonra db dosyasını serbest bırakan programımı göstermiyor.

Visual Studio 2010, C #, System.Data.SQLite sürüm 1.0.77.0, Win7

Bunun gibi iki yıllık bir böcek gördüm ama değişiklik günlüğü düzeltildiğini söylüyor.

Kontrol edebileceğim başka bir şey var mı? Açık komutların veya işlemlerin bir listesini almanın bir yolu var mı?


Yeni, çalışan 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);

Denediniz mi: myconnection.Close (); myconnection.Dispose (); ?
UGEEN

1
Kullanırken sqlite-net , kullanabilirsiniz SQLiteAsyncConnection.ResetPool(), bkz bu sorunu detayları için.
Uwe Keim

Yanıtlar:


113

Bir süre önce aynı problemle C # için bir DB soyutlama katmanı yazarken karşılaştım ve sorunun ne olduğunu asla bulamadım. Kitaplığımı kullanarak bir SQLite DB'yi silmeye çalıştığınızda bir istisna oluşturdum.

Her neyse, bu öğleden sonra her şeye tekrar bakıyordum ve neden bunu bir kez ve tümüyle yaptığını bulmaya çalışacağımı düşündüm, işte şimdiye kadar bulduğum şey.

Aradığınızda ne olur SQLiteConnection.Close()(bir dizi kontrol ve diğer şeylerle birlikte) SQLiteConnectionHandleSQLite veritabanı örneğini işaret eden şey elden çıkarılır. Bu, bir çağrı yoluyla yapılır SQLiteConnectionHandle.Dispose(), ancak bu, CLR'nin Çöp Toplayıcı bazı çöp toplama gerçekleştirene kadar işaretçiyi gerçekten serbest bırakmaz. Yana SQLiteConnectionHandlegeçersiz kılma CriticalHandle.ReleaseHandle()çağrısına fonksiyonu sqlite3_close_interop()(başka işlevi aracılığıyla) bu kadar yakın veritabanını değil.

Benim bakış açıma göre, programcı veri tabanının ne zaman kapatılacağından emin olmadığından, bu işleri yapmanın çok kötü bir yolu, ancak bu şekilde yapıldı, sanırım şimdilik onunla yaşamalıyız ya da System.Data.SQLite için birkaç değişiklik. Herhangi bir gönüllü bunu yapabilir, ne yazık ki önümüzdeki yıldan önce bunu yapmak için zamanım kalmadı.

TL; DR Çözüm, aramanızdan sonra ve aramanızdan SQLiteConnection.Close()önce bir GC zorlamaktır File.Delete().

İşte örnek kod:

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

Bol şans ve umarım yardımcı olur


1
Evet! Teşekkür ederim! Görünüşe göre GC işini bitirmek için biraz ihtiyaç duyabilir.
Tom Cerul

1
Ayrıca C # SQLite'a da bakmak isteyebilirsiniz, tüm kodumu kullanmaya başladım. Tabii ki, bir şey performans kritik ardından C çalıştırıyorsanız muhtemelen daha hızlı C # daha, ama yönetilen kod hayranıyım ... duyuyorum
Benjamin Pannell

1
Bunun eski olduğunu biliyorum, ama beni biraz acıdan kurtardığın için teşekkürler. Bu hata, SQLite'ın Windows Mobile / Compact Framework yapısını da etkiler.
StrayPointer

2
Harika iş! Sorunumu hemen çözdüm. 11 yıllık C # geliştirme sürecinde GC.Collect kullanma ihtiyacım hiç olmadı: Şimdi bu, bunu yapmak zorunda olduğum ilk örnek.
Pilsator

10
GC.Collect (); çalışır, ancak System.Data.SQLite.SQLiteConnection.ClearAllPools (); kitaplığın API'sini kullanarak sorunu ele alır.
Aaron Hudon

57

Sadece GC.Collect()benim için çalışmadı.

Dosya silme işlemine devam etmek için GC.WaitForPendingFinalizers()sonra eklemek zorunda kaldım GC.Collect().


5
Bu o kadar da şaşırtıcı değil, GC.Collect()sadece eşzamansız bir çöp toplama işlemi başlatır, bu nedenle her şeyin temizlendiğinden emin olmak için açıkça beklemeniz gerekir.
ChrisWue

2
Ben de aynı deneyimi yaşadım, GC.WaitForPendingFinalizers () eklemek zorunda kaldım. Bu, 1.0.103'te oldu
Vort3x

18

Benim durumumda, SQLiteCommandaçıkça elden çıkarmadan nesneler yaratıyordum .

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

Komutumu bir usingifadeye sardım ve sorunumu çözdü.

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

usingİfadesi imha Özel bir durum oluştuğunda dahi denir sağlar.

O zaman komutları yürütmek de çok daha kolay.

value = connection.ExecuteScalar(commandText)
// Command object created and disposed

6
Çok böyle yutma istisnalar karşı tavsiye
Tom McKearney

17

Çöp toplayıcı çözümü sorunu çözmemiş olsa da benzer bir sorun vardı.

Kullanıldıktan sonra bulunan bertaraf SQLiteCommandve SQLiteDataReadernesneler çöp toplayıcıyı kullanarak beni kurtardı.

SQLiteCommand command = new SQLiteCommand(sql, db);
command.ExecuteNonQuery();
command.Dispose();

2
Kesinlikle. Daha sonra SQLiteCommandbir SQLiteCommanddeğişkeni geri dönüştürseniz bile HER ŞEYİ attığınızdan emin olun .
Bruno Bieri

Bu benim için çalıştı. Ayrıca tüm işlemleri elden çıkarmayı da sağladım.
Jay-Nicolas Hackleman

1
Harika! Bana epey zaman kazandırdın. Yürütülen command.Dispose();her birine eklediğimde hatayı düzeltti SQLiteCommand.
Ivan B

Ayrıca, .Dispose()varsa SQLiteTransaction gibi diğer nesneleri serbest bıraktığınızdan (yani ) emin olun .
Ivan B

14

Aşağıdakiler benim için çalıştı:

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

Daha fazla bilgi : Bağlantılar, performansı artırmak için SQLite tarafından havuzlanır.Bu, bir bağlantı nesnesinde Close yöntemini çağırdığınızda, veritabanına bağlantı hala canlı olabilir (arka planda), böylece bir sonraki Open yöntemi daha hızlı hale gelir. artık yeni bir bağlantı istemiyorsanız, ClearAllPools'u çağırmak arka planda canlı olan tüm bağlantıları kapatır ve db dosyasına giden dosya tanıtıcı (lar?) 'ı serbest bırakılır.


1
Bunun soruna neden iyi bir çözüm olduğuna bir açıklama ekler misiniz?
Matas Vaitkevicius

Ayrıca kullanabilirsiniz SQLiteConnectionPool.Shared.Reset(). Bu, tüm açık bağlantıları kapatacaktır. Özellikle, SQLiteAsyncConnectionbir Close()yöntemi olmayan kullanıyorsanız, bu bir çözümdür .
Lorenzo Polidori

9

Benzer bir sorun yaşıyordum, çözümü ile denedim, GC.Collectancak belirtildiği gibi dosyanın kilitlenmemesi uzun zaman alabilir.

SQLiteCommandTableAdapters'ta temel alınan URL'lerin atılmasını içeren alternatif bir çözüm buldum , ek bilgi için bu yanıta bakın .


haklıydın! Bazı durumlarda basit 'GC.Collect' benim için işe yaradı, bazılarında ise GC.Collect'i çağırmadan önce bağlantıyla ilişkili tüm SqliteCommand'leri elden çıkarmak zorunda kaldım, yoksa işe yaramaz!
Eitan HS

1
SQLiteCommand'de Dispose çağırmak benim için çalıştı. Bir kenara yorum olarak - GC.Collect'i arıyorsanız, yanlış bir şey yapıyorsunuz demektir.
Natalie Adams

@NathanAdams EntityFramework ile çalışırken elden çıkarabileceğiniz tek bir komut nesnesi yoktur. Yani ya EntityFramework kendisi ya da EF sarmalayıcısı için SQLite da bir şeyler yanlış yapıyor.
springy76

Cevabınız doğru cevap olmalıdır. Çok teşekkürler.
Ahmed Shamel

6

EF ile aynı sorunu yaşıyorum ve System.Data.Sqlite.

Benim için dosya kilitlemesinin ne sıklıkta olacağını buldum SQLiteConnection.ClearAllPools()ve GC.Collect()azaltacaktım ama yine de ara sıra olacaktı (yaklaşık% 1 oranında).

Araştırıyorum ve görünen o ki SQLiteCommand, EF'nin oluşturduğu bazı dosyalar elden çıkarılmamış ve hala Bağlantı özellikleri kapalı bağlantıya ayarlanmış. Bunları elden çıkarmayı denedim, ancak Entity Framework sonraki DbContextokuma sırasında bir istisna atacaktı - EF bazen bağlantı kapandıktan sonra hala kullanıyor gibi görünüyor.

Benim çözümüm, bağlantı özelliğinin Nullbu SQLiteCommande- postalarda bağlantı kapandığında ayarlanmasını sağlamaktı . Bu, dosya kilidini açmak için yeterli görünüyor. Aşağıdaki kodu test ediyorum ve birkaç bin testten sonra herhangi bir dosya kilit sorunu görmedim:

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

Kullanmak için sadece ClearSQLiteCommandConnectionHelper.Initialise();uygulama yükünün başlangıcında arayın . Bu, daha sonra aktif komutların bir listesini tutacak ve Nullkapalı bir bağlantıya işaret ettiklerinde Bağlantılarını olarak ayarlayacaktır .


Ayrıca DisposingCommand bölümünde bağlantıyı null olarak ayarlamam gerekiyordu, yoksa ara sıra ObjectDisposedExceptions alıyordum.
Vae

Bence bu küçümsenmemiş bir cevap. EF katmanından dolayı kendi başıma yapamadığım temizleme sorunlarımı çözdü. Bunu o çirkin GC hackinde kullanmaktan çok mutluyum. Teşekkür ederim!
Jason Tyler

Bu çözümü çok iş parçacıklı bir ortamda kullanırsanız, OpenCommands Listesi [ThreadStatic] olmalıdır.
Bero

5

Bunu dene ... bu yukarıdaki tüm kodları deniyor ... benim için çalıştı

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

umarım yardımcı olur


1
WaitForPendingFinalizers benim için büyük fark yarattı
Todd

4

Kullanım GC.WaitForPendingFinalizers()

Misal:

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

3

Benzer bir sorun vardı. Çöp Toplayıcı'yı aramak bana yardımcı olmadı. LAter Sorunu çözmenin bir yolunu buldum

Yazar ayrıca bu veritabanını silmeye çalışmadan önce SELECT sorguları yaptığını yazdı. Ben de aynı duruma sahibim.

Takip koduna sahibim:

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.

Ayrıca, veritabanı bağlantısını kapatmam ve Çöp Toplayıcıyı aramam gerekmiyor. Tek yapmam gereken, SELECT sorgusunu çalıştırırken oluşturulan okuyucuyu kapatmaktı.


2

Aramanın SQLite.SQLiteConnection.ClearAllPools()en temiz çözüm olduğuna inanıyorum . Bildiğim kadarıyla GC.Collect()WPF ortamında manuel olarak arama yapmak uygun değil . Yine de, 3/ System.Data.SQLite2016'da 1.0.99.0'a yükselene kadar sorunu fark etmedim


2

Belki GC ile hiç uğraşmanıza gerek yoktur. Lütfen her sqlite3_prepareşeyin tamamlanıp tamamlanmadığını kontrol edin .

Her biri sqlite3_prepareiçin bir muhabire ihtiyacınız var sqlite3_finalize.

Doğru şekilde sonlandırmazsanız sqlite3_close, bağlantıyı kapatmayacaktır.


1

Ben de benzer bir sorunla mücadele ediyordum. Yazıklar olsun bana ... Sonunda Reader'ın kapatılmadığını anladım . Bazı nedenlerden dolayı, ilgili bağlantı kapatıldığında Reader'ın kapatılacağını düşünüyordum. Açıkçası, GC.Collect () benim için çalışmadı.
Reader'ı "using: deyimiyle sarmak da iyi bir fikirdir. İşte hızlı bir test kodu.

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

0

SQLite 1.0.101.0'ı EF6 ile kullanıyordum ve tüm bağlantılar ve varlıklar atıldıktan sonra dosyanın kilitlenmesi ile ilgili sorun yaşıyordum.

Bu, tamamlandıktan sonra veritabanını kilitli tutan EF güncellemelerinde daha da kötüleşti. GC.Collect () yardımcı olan tek geçici çözümdü ve umutsuzluğa kapılmaya başlıyordum.

Umutsuzluk içinde, Oliver Wickenden'in ClearSQLiteCommandConnectionHelper'ını denedim (8 Temmuz'daki cevabına bakın). Fantastik. Tüm kilitleme sorunları ortadan kalktı! Teşekkürler Oliver.


Bence bu bir cevap yerine bir yorum olmalı
Kevin Wallis

1
Kevin, katılıyorum, ama yorum yapmama izin verilmedi çünkü 50 itibara ihtiyacım var (görünüşe göre).
Tony Sullivan

0

Çöp Toplayıcı beklemek veritabanını her zaman serbest bırakmayabilir ve bu benim başıma geldi. SQLite veritabanında bir tür İstisna oluştuğunda, örneğin PrimaryKey için mevcut değeri olan bir satır eklemeye çalışırken, veritabanı dosyasını siz atana kadar tutar. Aşağıdaki kod SQLite istisnasını yakalar ve sorunlu komutu iptal eder.

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

Sorunlu komutların istisnalarını ele almazsanız, Çöp Toplayıcı bunlar hakkında hiçbir şey yapamaz çünkü bu komutlarla ilgili bazı işlenmemiş istisnalar vardır, bu nedenle bunlar gereksiz değildir. Bu işleme yöntemi, çöp toplayıcıyı beklerken benim için iyi çalıştı.


0

Bu benim için çalışıyor, ancak bazen -wal -shm günlük dosyalarının işlem kapatıldığında silinmediğini fark ettim. SQLite'in -wal -shm dosyalarını tüm bağlantılar kapalıyken kaldırmasını istiyorsanız, kapatılan son bağlantı salt okunur OLMAMALIDIR. Umarım bu birine yardımcı olur.


0

Benim için çalışan en iyi cevap.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.