System.Data.SQLite Close () không giải phóng tệp cơ sở dữ liệu


96

Tôi đang gặp sự cố khi đóng cơ sở dữ liệu của mình trước khi cố gắng xóa tệp. Mã chỉ là

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

Và Delete ném ra một ngoại lệ rằng tệp vẫn đang được sử dụng. Tôi đã thử lại Delete () trong trình gỡ lỗi sau vài phút, vì vậy đây không phải là vấn đề về thời gian.

Tôi có mã giao dịch nhưng nó hoàn toàn không chạy trước lệnh gọi Close (). Vì vậy, tôi khá chắc chắn đó không phải là một giao dịch mở. Các lệnh sql giữa mở và đóng chỉ là các lựa chọn.

ProcMon hiển thị chương trình của tôi và phần mềm chống vi-rút của tôi đang xem xét tệp cơ sở dữ liệu. Nó không hiển thị chương trình của tôi giải phóng tệp db sau khi đóng ().

Visual Studio 2010, C #, System.Data.SQLite phiên bản 1.0.77.0, Win7

Tôi đã thấy một lỗi hai năm tuổi giống như thế này nhưng bảng thay đổi cho biết nó đã được sửa.

Có điều gì khác tôi có thể kiểm tra không? Có cách nào để lấy danh sách các lệnh hoặc giao dịch đang mở không?


Mới, mã hoạt động:

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

Bạn đã thử: myconnection.Close (); myconnection.Dispose (); ?
UGEEN

1
Khi sử dụng sqlite-net , bạn có thể sử dụng SQLiteAsyncConnection.ResetPool(), xem vấn đề này để biết chi tiết.
Uwe Keim

Câu trả lời:


111

Đã gặp vấn đề tương tự một thời gian trước khi viết một lớp trừu tượng DB cho C # và tôi thực sự chưa bao giờ tìm hiểu vấn đề là gì. Tôi vừa mới đưa ra một ngoại lệ khi bạn cố gắng xóa một SQLite DB bằng thư viện của tôi.

Dù sao, chiều nay tôi đã xem lại tất cả và nghĩ rằng tôi sẽ cố gắng tìm hiểu tại sao nó lại hoạt động như vậy một lần và mãi mãi, vì vậy đây là những gì tôi đã tìm thấy cho đến nay.

Điều gì xảy ra khi bạn gọi SQLiteConnection.Close()là (cùng với một số kiểm tra và những thứ khác) SQLiteConnectionHandletrỏ đến cá thể cơ sở dữ liệu SQLite bị loại bỏ. Điều này được thực hiện thông qua một cuộc gọi đến SQLiteConnectionHandle.Dispose(), tuy nhiên điều này không thực sự giải phóng con trỏ cho đến khi Bộ thu gom rác của CLR thực hiện một số thu thập rác. Vì SQLiteConnectionHandleghi đè CriticalHandle.ReleaseHandle()hàm để gọi sqlite3_close_interop()(thông qua một hàm khác), điều này không đóng cơ sở dữ liệu.

Theo quan điểm của tôi, đây là một cách làm rất tệ vì lập trình viên không thực sự chắc chắn khi nào cơ sở dữ liệu bị đóng, nhưng đó là cách nó đã được thực hiện vì vậy tôi đoán chúng ta phải sống với nó bây giờ, hoặc cam kết một vài thay đổi đối với System.Data.SQLite. Mọi tình nguyện viên đều được hoan nghênh làm như vậy, rất tiếc là tôi đã hết thời gian làm việc này trước năm sau.

TL; DR Giải pháp là buộc một GC sau cuộc gọi đến SQLiteConnection.Close()và trước cuộc gọi của bạn tới File.Delete().

Đây là mã mẫu:

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

Chúc may mắn với nó, và tôi hy vọng nó sẽ hữu ích


1
Đúng! Cảm ơn bạn! Có vẻ như GC có thể cần một chút để hoàn thành công việc của mình.
Tom Cerul

1
Bạn cũng có thể muốn xem C # SQLite, tôi vừa chuyển tất cả mã của mình sang sử dụng nó. Tất nhiên, nếu bạn đang chạy một cái gì đó hiệu suất C sau đó quan trọng có lẽ là nhanh hơn so với C #, nhưng tôi là một fan hâm mộ của mã số quản lý ...
Benjamin Pannell

1
Tôi biết điều này là cũ, nhưng cảm ơn vì đã cứu tôi một phần đau đớn. Lỗi này cũng ảnh hưởng đến bản dựng Windows Mobile / Compact Framework của SQLite.
StrayPointer

2
Công việc tuyệt vời! Giải quyết vấn đề của tôi ngay lập tức. Trong 11 năm phát triển C #, tôi chưa bao giờ có nhu cầu sử dụng GC.Collect: Đây là ví dụ đầu tiên tôi buộc phải làm như vậy.
Pilsator

10
GC.Collect (); hoạt động, nhưng System.Data.SQLite.SQLiteConnection.ClearAllPools (); giải quyết vấn đề bằng cách sử dụng API của thư viện.
Aaron Hudon

57

Chỉ GC.Collect()không làm việc cho tôi.

Tôi đã phải thêm GC.WaitForPendingFinalizers()sau GC.Collect()để tiếp tục xóa tệp.


5
Điều này không có gì đáng ngạc nhiên, GC.Collect()chỉ cần bắt đầu một bộ sưu tập rác không đồng bộ, vì vậy để đảm bảo tất cả đã được dọn sạch, bạn phải đợi nó một cách rõ ràng.
ChrisWue,

2
Tôi cũng gặp phải trường hợp tương tự, phải thêm GC.WaitForPendingFinalizers (). Đây là trong 1.0.103
Vort3x

18

Trong trường hợp của tôi, tôi đã tạo SQLiteCommandcác đối tượng mà không xử lý chúng một cách rõ ràng.

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

Tôi gói lệnh của mình trong một usingtuyên bố và nó đã khắc phục sự cố của tôi.

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

Câu usinglệnh đảm bảo rằng Dispose được gọi ngay cả khi một ngoại lệ xảy ra.

Sau đó, việc thực thi các lệnh cũng dễ dàng hơn rất nhiều.

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

6
Tôi rất khuyên bạn nên chống ngoại lệ nuốt như thế này
Tom McKearney

17

Gặp sự cố tương tự, mặc dù giải pháp thu gom rác không khắc phục được.

Tìm thấy việc vứt bỏ SQLiteCommandSQLiteDataReadercác đồ vật sau khi sử dụng đã giúp tôi sử dụng công cụ thu gom rác.

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

2
Chính xác. Đảm bảo bạn loại bỏ MỌI SQLiteCommandngay cả khi bạn tái chế một SQLiteCommandbiến sau này.
Bruno Bieri

Điều này đã làm việc cho tôi. Tôi cũng đảm bảo hủy bỏ bất kỳ giao dịch nào.
Jay-Nicolas Hackleman,

1
Tuyệt quá! Bạn đã tiết kiệm cho tôi kha khá thời gian. Nó đã sửa lỗi khi tôi thêm command.Dispose();vào mọi SQLiteCommandthứ đã được thực thi.
Ivan B

Ngoài ra, hãy đảm bảo rằng bạn phát hành (tức là .Dispose()) các đối tượng khác như SQLiteTransaction, nếu bạn có.
Ivan B

13

Những điều sau đây đã làm việc cho tôi:

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

Thông tin thêm : Các kết nối được SQLite tổng hợp lại để cải thiện hiệu suất. Điều đó có nghĩa là khi bạn gọi phương thức Đóng trên một đối tượng kết nối, kết nối với cơ sở dữ liệu có thể vẫn còn tồn tại (trong nền) để phương thức Mở tiếp theo trở nên nhanh hơn. bạn không muốn có kết nối mới nữa, việc gọi ClearAllPools sẽ đóng tất cả các kết nối đang tồn tại trong nền và (các) tệp xử lý để tệp db được giải phóng. Sau đó tệp db có thể bị xóa, xóa hoặc sử dụng bởi một quá trình khác.


1
Bạn có thể vui lòng giải thích thêm tại sao đây là giải pháp tốt cho vấn đề.
Matas Vaitkevicius

Bạn cũng có thể sử dụng SQLiteConnectionPool.Shared.Reset(). Điều này sẽ đóng tất cả các kết nối đang mở. Đặc biệt, đây là một giải pháp nếu bạn sử dụng SQLiteAsyncConnectionmà không có Close()phương pháp.
Lorenzo Polidori

9

Tôi đang gặp sự cố tương tự, tôi đã thử giải pháp GC.Collectnhưng, như đã lưu ý, có thể mất nhiều thời gian trước khi tệp không bị khóa.

Tôi đã tìm thấy một giải pháp thay thế liên quan đến việc loại bỏ các phần bên dưới SQLiteCommandtrong TableAdapters, hãy xem câu trả lời này để biết thêm thông tin.


bạn đa đung! Trong một số trường hợp, 'GC.Collect' đơn giản phù hợp với tôi, Trong những trường hợp khác, tôi phải loại bỏ mọi lệnh SqliteCommand liên quan đến kết nối trước khi gọi GC.Collect, nếu không nó sẽ không hoạt động!
Eitan HS

1
Gọi Dispose trên SQLiteCommand phù hợp với tôi. Như một nhận xét sang một bên - nếu bạn đang gọi cho GC.Collect thì bạn đang làm sai.
Natalie Adams

@NathanAdams khi làm việc với EntityFramework, không có một đối tượng lệnh nào mà bạn có thể loại bỏ. Vì vậy, bản thân EntityFramework hoặc SQLite cho trình bao bọc EF cũng đang làm sai.
springy76

Câu trả lời của bạn phải là một trong những chính xác. Cảm ơn rất nhiều.
Ahmed Shamel

5

Hãy thử cái này ... cái này thử tất cả các trên ... phù hợp với tôi

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

Hy vọng điều đó sẽ giúp


1
WaitForPendingFinalizers đã tạo ra tất cả sự khác biệt cho tôi
Todd

5

Tôi đã gặp vấn đề tương tự với EF và System.Data.Sqlite.

Đối với tôi, tôi đã tìm thấy SQLiteConnection.ClearAllPools()GC.Collect()sẽ giảm tần suất xảy ra khóa tệp nhưng nó vẫn thỉnh thoảng xảy ra (Khoảng 1% thời gian).

Tôi đang điều tra và có vẻ như một số SQLiteCommandmà EF tạo ra không được xử lý và vẫn có thuộc tính Kết nối của chúng được đặt thành kết nối đóng. Tôi đã thử loại bỏ những thứ này nhưng Entity Framework sau đó sẽ ném ra một ngoại lệ trong lần DbContextđọc tiếp theo - có vẻ như EF đôi khi vẫn sử dụng chúng sau khi kết nối đóng.

Giải pháp của tôi là đảm bảo thuộc tính Kết nối được đặt thành Nullkhi kết nối đóng trên các SQLiteCommands này . Điều này dường như là đủ để giải phóng khóa tệp. Tôi đã thử nghiệm đoạn mã dưới đây và không thấy bất kỳ sự cố khóa tệp nào sau vài nghìn lần kiểm tra:

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

Để sử dụng, chỉ cần gọi ClearSQLiteCommandConnectionHelper.Initialise();khi bắt đầu tải ứng dụng. Thao tác này sau đó sẽ giữ một danh sách các lệnh đang hoạt động và sẽ đặt Kết nối của chúng thành Nullkhi chúng trỏ đến một kết nối bị đóng.


Tôi cũng phải đặt kết nối thành null trong phần DisposingCommand của điều này, nếu không đôi khi tôi sẽ nhận được ObjectDisposedExceptions.
Elliot

Đây là một câu trả lời được đánh giá thấp theo quan điểm của tôi. Nó đã giải quyết được các vấn đề về dọn dẹp của tôi mà tôi không thể tự thực hiện vì lớp EF. Rất vui khi sử dụng điều này trong vụ hack GC xấu xí đó. Cảm ơn bạn!
Jason Tyler

Nếu bạn sử dụng giải pháp này trong môi trường đa luồng, Danh sách OpenCommands phải là [ThreadStatic].
Bero

3

Sử dụng GC.WaitForPendingFinalizers()

Thí dụ:

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

3

Gặp vấn đề tương tự. Gọi Người thu gom rác đã không giúp tôi. CUỐI CÙNG, tôi đã tìm ra cách để giải quyết vấn đề

Tác giả cũng đã viết rằng anh ấy đã thực hiện các truy vấn SELECT tới cơ sở dữ liệu đó trước khi cố gắng xóa nó. Tôi cũng có tình huống tương tự.

Tôi có mã sau:

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.

Ngoài ra, tôi không cần phải đóng kết nối cơ sở dữ liệu và gọi Garbage Collector. Tất cả những gì tôi phải làm là đóng trình đọc được tạo trong khi thực hiện truy vấn SELECT


2

Tôi tin rằng cuộc gọi tới SQLite.SQLiteConnection.ClearAllPools()là giải pháp sạch sẽ nhất. Theo như tôi biết thì việc gọi thủ công GC.Collect()trong môi trường WPF là không đúng . Mặc dù, tôi không nhận thấy sự cố cho đến khi tôi đã nâng cấp lên System.Data.SQLite1.0.99.0 vào tháng 3/2016


2

Có lẽ bạn không cần phải giao dịch với GC. Vui lòng kiểm tra xem tất cả đã sqlite3_preparehoàn thành chưa.

Đối với mỗi sqlite3_prepare, bạn cần một phóng viên sqlite3_finalize.

Nếu bạn không hoàn thành chính xác, sqlite3_closesẽ không đóng kết nối.


1

Tôi đã đấu tranh với vấn đề tương tự. Thật xấu hổ cho tôi ... cuối cùng tôi nhận ra rằng Reader vẫn chưa bị đóng. Vì lý do nào đó, tôi nghĩ rằng Trình đọc sẽ bị đóng khi kết nối tương ứng bị đóng. Rõ ràng, GC.Collect () không hoạt động với tôi.
Gói Trình đọc bằng câu lệnh "using: cũng là một ý kiến ​​hay. Đây là mã kiểm tra nhanh.

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

Tôi đang sử dụng SQLite 1.0.101.0 với EF6 và gặp sự cố tệp bị khóa sau khi xử lý tất cả các kết nối và thực thể.

Điều này trở nên tồi tệ hơn với các bản cập nhật từ EF giữ cơ sở dữ liệu bị khóa sau khi chúng hoàn thành. GC.Collect () là giải pháp duy nhất hữu ích và tôi bắt đầu tuyệt vọng.

Trong tuyệt vọng, tôi đã thử ClearSQLiteCommandConnectionHelper của Oliver Wickenden (xem câu trả lời của anh ấy vào ngày 8 tháng 7). Tuyệt diệu. Tất cả các vấn đề khóa đã biến mất! Cảm ơn Oliver.


Tôi nghĩ rằng đây phải là một bình luận thay vì một câu trả lời
Kevin Wallis

1
Kevin, tôi đồng ý, nhưng tôi không được phép bình luận vì tôi cần 50 danh tiếng (hình như).
Tony Sullivan

0

Chờ đợi Trình thu gom rác có thể không phát hành cơ sở dữ liệu mọi lúc và điều đó đã xảy ra với tôi. Khi một số loại Ngoại lệ xảy ra trong cơ sở dữ liệu SQLite, ví dụ cố gắng chèn một hàng có giá trị hiện có cho PrimaryKey, nó sẽ giữ tệp cơ sở dữ liệu cho đến khi bạn loại bỏ nó. Mã sau bắt ngoại lệ SQLite và hủy lệnh có vấn đề.

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

Nếu bạn không xử lý các ngoại lệ của các lệnh có vấn đề hơn Garbage Collector không thể làm gì với chúng vì có một số ngoại lệ không được xử lý về các lệnh này để chúng không phải là rác. Phương pháp xử lý này đã hoạt động tốt đối với tôi khi chờ người thu gom rác.


0

Điều này phù hợp với tôi nhưng tôi nhận thấy đôi khi các tệp tạp chí -wal -shm không bị xóa khi quá trình đóng. Nếu bạn muốn SQLite loại bỏ các tệp -wal -shm khi tất cả kết nối đóng, kết nối cuối cùng đã đóng PHẢI KHÔNG đọc. Hy vọng điều này sẽ giúp một ai đó.


0

Câu trả lời tốt nhất phù hợp với tôi.

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

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

File.Delete(Environment.CurrentDirectory + "\\DATABASENAME.DB");
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.