Question

I'm using SQLite (v1.0.88.0) and Dapper to store some hierarchical data via additional closure table. I've enabled foreign_keys support in SQLite, but it does not work at all to me.
Here is the minimal sample code that demonstrates a couple of my issues:

using System.Data.SQLite;
using System.IO;
using Dapper;

class Program {
    static string db = "test.db";
    static void Main(string[] args) {
        if(!File.Exists(db))
            SQLiteConnection.CreateFile(db);
        using(SQLiteConnection c = new SQLiteConnection("Data Source=" + db)) {
            string initializationQuery =
                "PRAGMA foreign_keys = ON;" +  // enable FK
                "DROP TABLE IF EXISTS Departments;" +
                "DROP TABLE IF EXISTS Departments_treePath;" +
                "CREATE TABLE IF NOT EXISTS Departments (ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT);" +
                "CREATE TABLE IF NOT EXISTS Departments_treePath (ancestor INTEGER, descendant INTEGER, level INTEGER, " +
                "PRIMARY KEY (ancestor, descendant)," +
                "CONSTRAINT ancestor_ref FOREIGN KEY(ancestor) REFERENCES Departments(ID) ON DELETE CASCADE," +
                "CONSTRAINT descendant_ref FOREIGN KEY(descendant) REFERENCES Departments(ID) ON DELETE CASCADE);";
            c.Execute(initializationQuery);

            long idA = AddNode(c, 0, "A"); // ID=1
            long idB = AddNode(c, idA, "B"); // ID=2
            long idC = AddNode(c, idB, "C"); // ID=3

            // 1) It works , but it should not, because there is no ID=7 (FK fails)
            c.Execute("INSERT INTO Departments_treePath (ancestor,descendant) VALUES (7,7)");
            // 2) It works, but as far as i can see from SQLite DataBase Browser it does not delete all the references within the Departments_treePath table (cascade delete fails)
            c.Execute("DELETE FROM Departments WHERE ID=@id;", new { id = idC });
        }
    }
    static long AddNode(SQLiteConnection c, long ancestorID, string name) {
        string query = "BEGIN;" +
                       "INSERT OR ROLLBACK INTO Departments (Name) VALUES(@Name);" +
                       "CREATE TEMP TABLE _ID AS SELECT last_insert_rowid() AS id;" +
                       "INSERT INTO Departments_treePath (ancestor, descendant, level) " +
                       "SELECT t.ancestor, (SELECT id FROM _ID), t.level + 1 FROM Departments_treePath AS t " +
                       "WHERE t.descendant = @ancestor " +
                       "UNION ALL SELECT id , id, 0 FROM _ID;" +
                       "SELECT id FROM _ID; DROP TABLE _ID;" +
                       "END;";
        return System.Linq.Enumerable.First(c.Query<long>(query, new { ancestor = ancestorID, Name = name }));
    }
}

I' new in SQL/SQLite and it seems i've missing something. Please guide me.

Was it helpful?

Solution

I have got a guess(suddenly!!!) of how to make FK work in the demonstrated sample. And I have tried it immediately. And WOW, it works for me with latest Dapper/SQLite.

Of course, I have completed the original project many years ago, but I believe the clear description of the original behavior can be helpful to someone in future.

The reason for non-working constraints is that fact that Dapper preserve connection's state and flags when executing queries. So, when the connection is initially closed, as in original sample, it will be opened again before command execution and then closed after completion. And any pragmas will be lost.

If you make the connection open via the c.Open() all the settings will be preserved between command executions (and that is why all the things works for @CL in console). So this is the first and the simplest solution:

using(SQLiteConnection c = new SQLiteConnection("Data Source=" + db).OpenAndReturn()) {
    // ... commands
}

As an alternatives for closed connection, you should either add "PRAGMA foreign_keys = ON;" statement into each query:

c.Execute("PRAGMA foreign_keys = ON; INSERT INTO Departments_treePath (ancestor,descendant) VALUES (7,7)");

or enforce the FK constraints at the connection level:

SQLiteConnection c = new SQLiteConnection("Data Source=" + db + ";foreign keys=true;")

OTHER TIPS

For me, adding PRAGMA foreign_keys = ON; on each statement did not work.

But as per DmitryG's solutions :

SQLiteConnection c = new SQLiteConnection("Data Source=" + db + ";foreign keys=true;")

This worked.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top