外键约束问题(分层数据)

时间:2013-09-25 23:09:42

标签: .net sqlite foreign-keys cascade

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

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的新手,似乎我错过了一些东西。请指导我。

2 个答案:

答案 0 :(得分:0)

我有一个猜测(突然!!!)如何在演示样本中使FK工作。我立刻试了一下。而WOW,它适用于最新的Dapper / SQLite。

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

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

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

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

作为封闭连接的替代方案,您应该添加&#34; PRAGMA foreign_keys = ON;&#34;语句到每个查询:

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

答案 1 :(得分:0)

对我来说,在每个语句上添加 PRAGMA foreign_keys = ON; 不起作用。

但根据 DmitryG 的解决方案:

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

这奏效了。