ASP.NET Core 6中SQLite备份文件锁定,上传Azure Blob失败
问题原因分析
问题出在SQLite的连接池机制上。尽管你通过using块确保了SqliteConnection被正确释放,但SQLite默认启用连接池:当你Dispose连接时,它不会立即关闭底层的文件句柄,而是将连接放回连接池供后续复用。这就导致备份文件仍然被连接池占用,因此上传时抛出文件无法访问的异常。
解决方案
方法1:禁用备份连接的连接池(最简单直接)
在备份数据库的连接字符串中添加Pooling=false,这样Dispose连接时会直接关闭文件句柄,不会将连接放回连接池。
修改后的代码:
public void CreateBackup() { string backupPath = string.Empty; // 为备份连接添加Pooling=false参数 var backupConnectionString = $"{configuration.GetConnectionString("BackupDatabase")};Pooling=false"; using (var originalConnection = new SqliteConnection(configuration.GetConnectionString("Database"))) using (var backupConnection = new SqliteConnection(backupConnectionString)) { originalConnection.Open(); originalConnection.BackupDatabase(backupConnection); backupPath = backupConnection.DataSource; } var blobClient = new BlobClient(configuration.GetConnectionString("AzureBackup"), "tanusitvanytar", $"database-{DateTime.Now:yyyy-MM-dd}"); blobClient.Upload(backupPath); }
方法2:备份到临时文件并自动清理(避免长期占用本地文件)
如果不想修改连接池配置,可以先备份到临时文件,上传完成后立即删除临时文件,同时确保临时文件的连接禁用连接池:
public void CreateBackup() { string tempBackupPath = Path.GetTempFileName(); try { using (var originalConnection = new SqliteConnection(configuration.GetConnectionString("Database"))) using (var backupConnection = new SqliteConnection($"Data Source={tempBackupPath};Pooling=false")) { originalConnection.Open(); backupConnection.Open(); originalConnection.BackupDatabase(backupConnection); } // 上传临时文件到Azure Blob var blobClient = new BlobClient(configuration.GetConnectionString("AzureBackup"), "tanusitvanytar", $"database-{DateTime.Now:yyyy-MM-dd}"); using (var fileStream = File.OpenRead(tempBackupPath)) { blobClient.Upload(fileStream); } } finally { // 无论上传成功与否,都删除临时文件 if (File.Exists(tempBackupPath)) { File.Delete(tempBackupPath); } } }
方法3:备份到内存流(完全避免本地文件操作)
跳过本地文件,直接将数据库备份到内存流,再上传到Azure Blob,彻底消除文件占用问题:
public void CreateBackup() { using (var originalConnection = new SqliteConnection(configuration.GetConnectionString("Database"))) { originalConnection.Open(); // 创建内存中的备份连接 using (var backupConnection = new SqliteConnection("Data Source=:memory:")) { backupConnection.Open(); originalConnection.BackupDatabase(backupConnection); // 将内存中的数据库写入临时文件流(SQLite暂不支持直接备份到Stream,需中转) string tempPath = Path.GetTempFileName(); try { using (var tempConn = new SqliteConnection($"Data Source={tempPath};Pooling=false")) { tempConn.Open(); backupConnection.BackupDatabase(tempConn); } // 读取临时文件流并上传 var blobClient = new BlobClient(configuration.GetConnectionString("AzureBackup"), "tanusitvanytar", $"database-{DateTime.Now:yyyy-MM-dd}"); using (var stream = File.OpenRead(tempPath)) { blobClient.Upload(stream); } } finally { if (File.Exists(tempPath)) File.Delete(tempPath); } } } }
注意事项
- 若使用
Microsoft.Data.Sqlite,可以调用SqliteConnection.ClearAllPools()强制清空所有连接池,但这会影响所有正在使用的SQLite连接,仅在确认无其他活跃连接时使用。 - 建议优先使用方法1或方法2,代码简洁且能有效解决问题。
内容的提问来源于stack exchange,提问作者Akos Nagy
相关产品推荐
相关产品推荐

