web-dev-qa-db-ja.com

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

ファイルを削除する前にデータベースを閉じることができません。コードはただ

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

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

トランザクションコードはありますが、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);
81
Tom Cerul

C#のDB抽象化レイヤーを作成しているときに、同じ問題に少し前に遭遇しましたが、実際に問題が何であるかを見つけることはできませんでした。私のライブラリを使用してSQLite DBを削除しようとすると、例外がスローされました。

とにかく、今日の午後、私はそれをすべてもう一度調べて、なぜそれをきっぱりとやっているのかを見つけ出そうと考えたので、ここに私が見つけたものがあります。

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

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

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

サンプルコードは次のとおりです。

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

それで頑張って、それが役立つことを願っています

90

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

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

55
Batiati

私の場合、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
16
Nate

次は私のために働いた:

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

詳細:パフォーマンスを向上させるために、SQLiteによって接続がプールされます。つまり、接続オブジェクトでCloseメソッドを呼び出すと、データベースへの接続が(バックグラウンドで)生きているため、次のOpenメソッド新しい接続が不要になったことがわかった場合、ClearAllPoolsを呼び出すと、バックグラウンドで動作しているすべての接続が閉じられ、dbファイルへのファイルハンドルが解放されます。その後、dbファイルが削除される場合があります。削除されるか、別のプロセスによって使用されます。

11
Arvin

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

使用後にSQLiteCommandおよびSQLiteDataReaderオブジェクトを破棄することにより、ガベージコレクターを使用することで節約できました。

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

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

TableAdaptersで基になるSQLiteCommandsを破棄する代替ソリューションが見つかりました。詳細については、 this answer を参照してください。

9
edymtt

同様の問題がありました。ガベージコレクターに電話しても助けにはなりませんでした。その後、問題を解決する方法を見つけました

著者はまた、そのデータベースを削除する前に、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クエリの実行中に作成されたリーダーを閉じるだけでした。

3
Schullz

これを試してみてください...これは上記のすべてを試してみますcodes ...

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

役立つことを願っています

3
Bishnu Dev

GC.WaitForPendingFinalizers()を使用します

例:

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

たぶん、GCを扱う必要はまったくないでしょう。すべてsqlite3_prepareが確定します。

sqlite3_prepare、特派員が必要sqlite3_finalize

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

2
João Monteiro

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

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

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

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

_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();を呼び出します。これにより、アクティブなコマンドのリストが保持され、閉じられた接続をポイントするときにConnectionがNullに設定されます。

2
Hallupa

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

1
Jona Varque

私は同様の問題に苦労していました。私を恥ずかしく思います... Readerが閉じられていないことに気づきました。何らかの理由で、対応する接続​​が閉じられるとReaderが閉じられると考えていました。明らかに、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();
        }
    }   
}
1
Mike Znaet

私はEF6でSQLite 1.0.101.0を使用していましたが、すべての接続とエンティティが破棄された後にファイルがロックされる問題がありました。

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

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

0
Tony Sullivan

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

0
ekalchev

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

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

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

0
Muhammed Kadir