Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite connections not closed in nested queries #356

Open
ivashin opened this issue Mar 23, 2015 · 0 comments
Open

SQLite connections not closed in nested queries #356

ivashin opened this issue Mar 23, 2015 · 0 comments

Comments

@ivashin
Copy link

ivashin commented Mar 23, 2015

I'm using BLToolkit 4.2 together with System.Data.SQLite 1.0.93. I've noticed that sometimes DB file remains locked after running complex queries. After some debugging I've found that an SQLiteConnection remains open. Further debugging showed that:

  • When BLToolkit executes a subquery, it clones the root DataContextInfo (see QueryContext.GetDataContext()) which in turn clones DbManager.
  • DbManager clones connection via DataProvider (see DataProviderBase.CloneConnection())
  • System.Data.SQLite's SQLiteConnection.Clone() creates a new connection and opens it if the original connection is open:
public SQLiteConnection(SQLiteConnection connection)
  : this(connection.ConnectionString, connection.ParseViaFramework)
{
  if (connection.State == ConnectionState.Open)
  {
    Open();
  • Nested DbManager's _closeConnection flag is never set because connection is already open and OpenConnection() is not executed.
  • When the root DbManager is disposed it correctly disposes its own connection and the nested DbManager, but the cloned connection is not closed/disposed as _closeConnection == false (see DbManager.Close()).

Consequently, new connection was created in DbManager, but was not disposed in it.
The following small sample demonstrates this behavior:

Program.cs
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using BLToolkit.Data;
using BLToolkit.Data.DataProvider;

namespace BLToolkitSQLiteTest
{
    class Program
    {
        static void Main(string[] args)
        {
            // Configuration
            var dataProvider = new SQLiteDataProvider();
            DbManager.AddDataProvider(dataProvider);

            // Clean up and init DB
            if (File.Exists("test.db"))
                File.Delete("test.db");

            using (var db = new DbManager(dataProvider, GetConnectionString("test.db")))
            {
                using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("BLToolkitSQLiteTest.init.sql"))
                using (var reader = new StreamReader(stream))
                {
                    var query = reader.ReadToEnd();
                    db.SetCommand(query).ExecuteNonQuery();
                }
            }

            using (var db = new DbManager(dataProvider, GetConnectionString("test.db")))
            {
                // Execute complex query
                var table1 = db.GetTable<Asset_Type>();
                var table2 = db.GetTable<Asset_Type>();
                var result = table1.GroupJoin(table2, t1 => t1.Type_Id, t2 => t2.Type_Id,
                    (t1, t2) => t1.Type_Name + "; " + String.Join(", ", t2.Select(_ => _.Type_Name))).ToList();
            }

            try
            {
                // Check if file is locked
                File.Delete("test.db");
                Console.WriteLine("OK");
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR");
                Console.WriteLine(e);
            }
            Console.ReadKey();
        }

        protected static string GetConnectionString(string path)
        {
            return "Data Source=" + path + ";Version=3;Compress=True;foreign keys=true;";
        }
    }

    public class Asset_Type
    {
        public int Type_Id { get; set; }
        public string Type_Name { get; set; }
    }
}
init.sql
CREATE TABLE Asset_Type ( 
    Type_Id   INTEGER PRIMARY KEY NOT NULL,
    Type_Name TEXT    NOT NULL UNIQUE 
);
INSERT INTO [Asset_Type] ([Type_Id], [Type_Name]) VALUES (0, 'Test1');
INSERT INTO [Asset_Type] ([Type_Id], [Type_Name]) VALUES (1, 'Test2');

My workaround was to check if connection is already open after cloning and set DbManager._closeConnection flag if it is:

public virtual DbManager Clone()
{
...
    if (_connection != null)
    {
        clone._connection = CloneConnection();
        if (clone._connection.State == ConnectionState.Open)
            clone._closeConnection = true;
    }

    return clone;
}

This helped with my issue but I am not sure if it may affect other scenarios/data provider types, and I'm not able to run unit test properly as I don't have proper environment with different databases configured. Please advise if this is an acceptable solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant