外键约束问题(分层数据)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了外键约束问题(分层数据)相关的知识,希望对你有一定的参考价值。
我正在使用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的新手,似乎我错过了一些东西。请指导我。
答案
我有一个猜测(突然!!!)如何使FK在演示样本中工作。我立刻试了一下。而WOW,它适用于最新的Dapper / SQLite。
当然,我已经在很多年前完成了原始项目,但我相信对原始行为的清晰描述可能对将来有所帮助。
非工作约束的原因是Dapper在执行查询时保留连接的状态和标志。因此,当连接最初关闭时,如原始样本中那样,它将在命令执行之前再次打开,然后在完成后关闭。任何pragma都会丢失。
如果通过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;")
以上是关于外键约束问题(分层数据)的主要内容,如果未能解决你的问题,请参考以下文章