System.Data.SQLite Close()がデータベースファイルを解放しない


96

ファイルを削除しようとする前にデータベースを閉じるのに問題があります。コードは

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

そして、削除はファイルがまだ使用中であるという例外をスローします。数分後にデバッガーでDelete()を再試行したので、タイミングの問題ではありません。

トランザクションコードがありますが、Close()呼び出しの前に実行されません。だから私はそれがオープンな取引ではないと確信しています。openとcloseの間のSQLコマンドは、単に選択するだけです。

ProcMonは、私のプログラムと私のアンチウイルスがデータベースファイルを見ているところを示しています。close()の後でdbファイルを解放するプログラムは表示されません。

Visual Studio 2010、C#、System.Data.SQLiteバージョン1.0.77.0、Win7

私はこのように2年前のバグを見ましたが、変更ログはそれが修正されたと言っています。

他に確認できることはありますか?開いているコマンドまたはトランザクションのリストを取得する方法はありますか?


新しい、機能するコード:

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

試してみましたか:myconnection.Close(); myconnection.Dispose(); ?
UGEEN 2013年

1
使用する場合はsqliteのネットを、あなたが使用することができますSQLiteAsyncConnection.ResetPool()参照して、この問題を詳細については。
Uwe Keim 2016年

回答:


111

C#のDB抽象化レイヤーを作成しているときに同じ問題が発生しましたが、実際に問題が何であるかを知ることができませんでした。私のライブラリを使用してSQLite DBを削除しようとすると、例外が発生しました。

とにかく、今日の午後、私はもう一度それをじっくりと見ていて、なぜそれが何度もそれをしているのかを調べてみようと思ったので、ここに私がこれまでに見つけたものがあります。

呼び出し時に何が起こるかSQLiteConnection.Close()SQLiteConnectionHandle、SQLiteデータベースインスタンスを指す(いくつかのチェックなどとともに)が破棄されることです。これはへの呼び出しを通じて行われSQLiteConnectionHandle.Dispose()ますが、CLRのガベージコレクターがガベージコレクションを実行するまで、実際にはポインターは解放されません。(別の関数を介して)呼び出す関数をSQLiteConnectionHandleオーバーライドするため、これはデータベースを閉じません。CriticalHandle.ReleaseHandle()sqlite3_close_interop()

私の観点からは、データベースが閉じられたときにプログラマーが実際に確実ではないため、これは物事を行うには非常に悪い方法です。 System.Data.SQLiteに対するいくつかの変更。ボランティアは大歓迎ですが、残念ながら私は来年までにそうする時間がありません。

TL; DR 解決策は、への呼び出しの後、呼び出しのSQLiteConnection.Close()前にGCを強制することFile.Delete()です。

これがサンプルコードです:

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

頑張ってください。それが役に立てば幸いです


1
はい!ありがとうございました!GCが作業を完了するには少し時間が必要なようです。
Tom Cerul

1
C#SQLiteを確認することもできます。コードをすべて使用するように移動しました。もちろん、パフォーマンスクリティカルなものを実行している場合、CはおそらくC#よりも高速ですが、私はマネージコードのファンです...
Benjamin Pannell

1
これは古いことはわかっていますが、痛みを軽減してくれてありがとう。このバグは、SQLiteのWindows Mobile / Compact Frameworkビルドにも影響します。
StrayPointer 2013

2
すごい仕事!私の問題をすぐに解決しました。11年間のC#開発では、GC.Collectを使用する必要がありませんでした。これが、私が強制的に使用する最初の例です。
ピルセーター2014年

10
GC.Collect(); 動作しますが、System.Data.SQLite.SQLiteConnection.ClearAllPools(); ライブラリのAPIを使用して問題を処理します。
アーロンフドン

57

GC.Collect()うまくいきませんでした。

ファイルの削除を続行するには、GC.WaitForPendingFinalizers()後で追加GC.Collect()する必要がありました。


5
これは驚くべきことではありません、 GC.Collect()非同期のガベージコレクションを開始するだけなので、すべてがクリーンアップされたことを確認するには、明示的に待機する必要があります。
ChrisWue

2
私も同じことを経験し、GC.WaitForPendingFinalizers()を追加する必要がありました。これは1.0.103でした
Vort3x

18

私の場合、SQLiteCommand明示的に破棄せずにオブジェクトを作成していました。

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

コマンドをusingステートメントで囲み、問題を修正しました。

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ステートメントにより、例外が発生した場合でもDisposeが確実に呼び出されます。

そうすれば、コマンドの実行もずっと簡単になります。

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

6
私はこのような例外を飲み込まないように強くお勧めします
トムマッカーニー2013

17

ガベージコレクターソリューションはそれを修正しませんでしたが、同様の問題がありました。

使用後の廃棄SQLiteCommandSQLiteDataReaderオブジェクトの発見により、ガベージコレクターを使用することがまったくなくなりました。

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

2
丁度。後で変数SQLiteCommandをリサイクルする場合でも、必ずすべてを破棄してくださいSQLiteCommand
Bruno Bieri

これでうまくいきました。また、取引は必ず破棄しました。
ジェイニコラハックルマン

1
すごい!あなたはかなりの時間を節約してくれました。実行command.Dispose();したものすべてに追加したときのエラーを修正しましたSQLiteCommand
Ivan B

また、.Dispose()SQLiteTransactionのような他のオブジェクトがある場合は、必ず解放(つまり)してください。
Ivan B

13

以下は私のために働きました:

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

詳細:接続は、パフォーマンスを向上させるためにSQLiteによってプールされます。つまり、接続オブジェクトでCloseメソッドを呼び出すと、データベースへの接続がまだ(バックグラウンドで)存続している可能性があるため、次のOpenメソッドが高速になります。新しい接続はもう必要ありません。ClearAllPoolsを呼び出すと、バックグラウンドで有効なすべての接続が閉じられ、dbファイルへのファイルハンドルが解放されます。次に、dbファイルが削除、削除、または別のプロセスで使用される可能性があります。


1
これが問題の良い解決策である理由を説明してください。
Matas Vaitkevicius 14

も使用できますSQLiteConnectionPool.Shared.Reset()。これにより、開いているすべての接続が閉じます。特に、メソッドSQLiteAsyncConnectionがないClose()メソッドを使用する場合の解決策です。
ロレンツォポリドリ

9

同様の問題がありましたが、解決策を試しましたGC.Collectが、前述のように、ファイルがロックされなくなるまでに長い時間がかかる場合があります。

SQLiteCommandTableAdaptersの基になるの破棄を含む代替ソリューションを見つけました。詳細については、この回答を参照してください。


あなたは正しかった!単純な「GC.Collect」が機能する場合もあれば、GC.Collectを呼び出す前に接続に関連付けられたSqliteCommandsを破棄しなければならない場合もありました。そうしないと機能しません。
Eitan HS

1
SQLiteCommandでDisposeを呼び出すとうまくいきました。余談ですが、GC.Collectを呼び出している場合、何か間違ったことをしています。
ナタリーアダムス

@NathanAdamsがEntityFrameworkを使用する場合、処分できる単一のコマンドオブジェクトはありません。そのため、EntityFramework自体またはSQLite for EFラッパーのどちらかが何らかの間違った動作をしています。
springy76 2014

あなたの答えは正しいものでなければなりません。どうもありがとう。
Ahmed Shamel 2017

5

これを試してください...これは上記のコードをすべて試します ...私にとってはうまくいきました

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

それが役に立てば幸い


1
WaitForPendingFinalizersは私にとって大きな違いをもたらしました
Todd

5

私はEFとで同じ問題を抱えていSystem.Data.Sqliteます。

私にとっては、ファイルロックが発生する頻度を見つけSQLiteConnection.ClearAllPools()GC.Collect()削減しましたが、それでも時折発生します(約1%の時間)。

私は調査しておりSQLiteCommand、EFが作成する一部のは破棄されておらず、接続プロパティが閉じられた接続に設定されているようです。これらを破棄しようとしましたが、Entity Frameworkは次のDbContext読み取り中に例外をスローしました。接続が閉じられた後もEFがまだそれらを使用しているようです。

私の解決策はNull、接続がこれらSQLiteCommandので閉じるときにConnectionプロパティが確実に設定されるようにすることでした。これはファイルロックを解除するのに十分なようです。私は以下のコードをテストしており、数千回のテストの後にファイルロックの問題を確認していません。

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

使用ClearSQLiteCommandConnectionHelper.Initialise();するには、アプリケーションのロードの開始時に呼び出します。これにより、アクティブなコマンドのリストが保持され、Null閉じられた接続をポイントしたときにそれらの接続が設定されます。


また、このDisposingCommand部分で接続をnullに設定する必要がありました。そうしないと、ObjectDisposedExceptionsが時々発生します。
Elliot

これは私の意見では過小評価されている答えです。EFレイヤーが原因で自分で実行できなかったクリーンアップの問題が解決されました。醜いGCハックでこれを使ってとても幸せです。ありがとうございました!
Jason Tyler

このソリューションをマルチスレッド環境で使用する場合、OpenCommandsリストは[ThreadStatic]である必要があります。
ベロ

3

使用する GC.WaitForPendingFinalizers()

例:

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

3

同様の問題がありました。ガベージコレクターを呼び出しても役に立ちませんでした。後で問題を解決する方法を見つけました

著者はまた、データベースを削除する前に、そのデータベースに対してSELECTクエリを実行したと書いています。私も同じ状況です。

私は次のコードを持っています:

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.

また、データベース接続を閉じてガベージコレクターを呼び出す必要もありません。SELECTクエリの実行中に作成されたリーダーを閉じるだけです。


2

への呼び出しSQLite.SQLiteConnection.ClearAllPools()は最もクリーンなソリューションだと思います。私の知る限りGC.Collect()、WPF環境で手動で呼び出すことは適切ではありません。しかし、System.Data.SQLite2016年3月に1.0.99.0にアップグレードするまで、問題に気づきませんでした


2

たぶん、GCを扱う必要はまったくありません。すべてsqlite3_prepareが確定しているか確認してください。

それぞれにsqlite3_prepare、特派員が必要sqlite3_finalizeです。

正しくファイナライズしsqlite3_closeないと、接続を閉じません。


1

私は同様の問題に苦しんでいました。恥ずかしい... リーダーが閉じていないことにようやく気づきました。どういうわけか、対応する接続​​が閉じられるとリーダーも閉じられると思っていました。明らかに、GC.Collect()は機能しませんでした。
リーダーを "using:ステートメントでラップすることも良いアイデアです。これは簡単なテストコードです。

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を使用していて、すべての接続とエンティティが破棄された後にファイルがロックされるという問題がありました。

これは、データベースが完了した後にデータベースをロックしたままにするEFからの更新により悪化しました。GC.Collect()が役立つ唯一の回避策であり、私は絶望し始めていました。

必死になって、私はOliver WickendenのClearSQLiteCommandConnectionHelperを試しました(7月8日の彼の回答を参照してください)。素晴らしい。ロックの問題はすべてなくなりました。オリバーありがとう。


私はこれは答えではなくコメントであるべきだと思います
Kevin Wallis

1
ケビン、私は同意しますが、(どうやら)50の評判が必要なため、コメントすることは許可されませんでした。
トニーサリバン

0

ガベージコレクターを待っていると、データベースが常に解放されるとは限りません。SQLKeyデータベースで何らかのタイプの例外が発生した場合、たとえばPrimaryKeyの既存の値で行を挿入しようとすると、データベースファイルは破棄されるまで保持されます。次のコードはSQLite例外をキャッチし、問題のあるコマンドをキャンセルします。

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

問題のあるコマンドの例外を処理しない場合、ガベージコレクターはそれらについて何も処理できません。これらのコマンドにはいくつかの未処理の例外があるため、ガベージではありません。この処理方法は、ガベージコレクターを待っている私にとってはうまくいきました。


0

これは私にとってはうまくいきますが、プロセスが閉じられたときにジャーナルファイル-wal -shmが削除されないことがあります。すべての接続が閉じているときにSQLiteが-wal -shmファイルを削除するようにしたい場合は、最後に閉じた接続を読み取り専用にしないでください。これが誰かを助けることを願っています。


0

私にとってうまくいった最良の答え。

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.