1

我正在使用 SQLite (v1.0.88.0) 和 Dapper 通过附加的闭包表来存储一些分层数据。我foreign_keys在 SQLite 中启用了支持,但它对我来说根本不起作用。
这是演示我的几个问题的最小示例代码:

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

我是 SQL/SQLite 的新手,我似乎遗漏了一些东西。请指导我。

4

2 回答 2

2

我有一个猜测(突然!!!)如何使 FK 在演示的示例中工作。我已经立即尝试了。哇,它适用于我最新的 Dapper/SQLite。

当然,我多年前已经完成了原始项目,但我相信对原始行为的清晰描述可以对将来的某人有所帮助。

非工作约束的原因是 Dapper在执行查询时保留了连接的状态和标志。因此,当连接最初关闭时,如原始示例中一样,它将在命令执行之前再次打开,然后在完成后关闭。并且任何编译指示都会丢失。

如果您通过打开连接,c.Open()所有设置将在命令执行之间保留(这就是为什么所有东西都适用于控制台中的@CL)。所以这是第一个也是最简单的解决方案:

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

作为关闭连接的替代方案,您应该添加“PRAGMA foreign_keys = ON;” 在每个查询中声明:

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

或在连接级别强制执行FK 约束:

SQLiteConnection c = new SQLiteConnection("Data Source=" + db + ";foreign keys=true;")
于 2017-12-21T15:19:32.200 回答
0

对我来说,添加PRAGMA foreign_keys = ON;每条语句都行不通。

但根据 DmitryG 的解决方案:

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

这行得通。

于 2021-07-17T22:34:05.547 回答