How to enable foreign key cascade delete by default in SQLite? - c#

SQLite v3.7.5
Is there a way to enable SQLite Foreign Keys with cascade delete enabled by default?
Given the following example:
CREATE TABLE [Parent] (
[ParentId] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[Name] VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE [Child] (
[ChildId] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[ParentId] INTEGER NOT NULL,
[Name] VARCHAR(50) NOT NULL,
FOREIGN KEY(ChildId) REFERENCES Child(ParentId) ON DELETE CASCADE
);
The only way I've been able to enable the cascade delete is to execute the PRAGMA foreign_keys = true command before a transaction:
using( var conn = new SQLiteConnection( _conn ) )
{
conn.Open();
var pragma = new SQLiteCommand( "PRAGMA foreign_keys = true;", conn );
pragma.ExecuteNonQuery();
var cmd = new SQLiteCommand( "Delete from Parent where ParentId = 1", conn );
cmd.ExecuteNonQuery();
}
Is there a setting on the database level that can be configured rather than having to call the pragma command before each transaction?
I've seen the triggers to enable cascade deletes, but am looking for something that would simply enable the PRAGMA foreign_keys = true at the database level.

System.Data.SQLite 1.0.66 doesn't have it, but in the repository version of it, they have updated to sqlite 3.7.4 and have made a new connection string attribute "Foreign Keys". Who knows when this will be released officially? So you could set this in your connection string. The project lives here now:
http://system.data.sqlite.org/index.html/doc/trunk/www/index.wiki

No, not even with compile-time options.
The only way, so far, is to use pragma foreign_keys=on at run time. The particular danger is that every application that touches the database has to do that.
If a specific application doesn't run that pragma statement, it can insert data that will violate foreign key constraints, and other applications won't know it. That is, turning on foreign keys doesn't warn you of existing data that violates the constraint.

No, there is no way currently (for backward compatibility). See the link you provided under (2):
Foreign key constraints are disabled by default (for backwards compatibility), so must be enabled separately for each database connection separately.
However, this might be changed in the future:
(Note, however, that future releases of SQLite might change so that foreign key constraints enabled by default. Careful developers will not make any assumptions about whether or not foreign keys are enabled by default but will instead enable or disable them as necessary.)

Related

npgsql returns table schema without primary key

I'm trying to find a universal way of getting the column with the primary key inside a given table. I've tested the following code out
SqlCommand cmd = new SqlCommand(sql, conn);
var reader = cmd.ExecuteReader(CommandBehavior.KeyInfo);
var schemaTable = reader.GetSchemaTable();
This code works perfectly fine and all is good, I can find the column with the primary key easily from it for any SQL Server database, but when I try to use the Npgsql library on a PostgreSQL database, it labels all the columns (whether a column is the primary key or not) inside a given table as false.
Has anybody faced this issue before?
PS: I know that there are ways to get what I want using a query hack (INFORMATION_SCHEMA I mean), but I don't want to walk that road.
Updated
Postgres Version: 14.5, NpgSQL: 6.0.6
Example code
CREATE TABLE accounts (
user_id serial PRIMARY KEY,
username VARCHAR ( 50 ) UNIQUE NOT NULL
);
Whether I'm using a primary, or an identity it's still giving me false, on the other hand, in the below table the isKey column is marked true, but I'm in need of the isIdentity column to be marked true, any potential fixes?

Microsoft Sync Framework unique index error

I use the MS Sync Framework to sync my SQL Server instance with a local SQL CE file to make it possible working offline with my Windows app.
I use GUIDs as keys. On my table I have a unique index on 2 columns: user_id and setting_id:
usersettings table
------------------
id PK -> I also tried it without this column. Same result
user_id FK
setting_id FK
value
Now I do the following:
I create a new record in this table in both databases - SQL Server and SQL CE with the same user_id and setting_id.
This should work and merge the data together since this can happen in real life. But I get an error when syncing saying the unique key constraint led to an error. The key pair already exists in the table.
A duplicate value cannot be inserted into a unique index. [ Table name = user_settings,Constraint name = unique_userid_settingid ]
Why can't MS sync handle that? It should not try to insert the key pair again. It should update the value if needed.
The issue is if you add the same key pair to different copies of the table, they get different IDs (GUIDs) as primary keys in this usersettings table.
As this is simply a many-to-many table between Users and Settings, there is no need to have that ID as a PK (or even a column at all).
Instead, just use a concatenated key of the two FKs e.g.,
CREATE TABLE [dbo].[usersettings](
[user_id] [UNIQUEIDENTIFIER] NOT NULL,
[setting_id] [UNIQUEIDENTIFIER] NOT NULL,
[value] [varchar](50) NOT NULL,
CONSTRAINT [PK_usersettings] PRIMARY KEY CLUSTERED ([user_id] ASC, [setting_id] ASC) );
Of course, include appropriate field settings (e.g., if you use VARCHARs to store the IDs) and relevant FKs.
As the rows inserted should now be identical on the two copies, it should merge fine.
If you must have a single column as a unique identifier for the table, you could make it meaningful e.g.,
the PK (ID) becomes a varchar (72)
it gets filled with CONCAT(user_ID, setting_id)
As the User_ID and Setting_ID are FKs, you should already have them generated so concatenating them should be easy enough.
Do you get the error during sync, then it should appear as a conflict, that you must solve in code.
https://learn.microsoft.com/en-us/previous-versions/sql/synchronization/sync-framework-2.0/bb734542(v=sql.105)
I also see this in the manual: By default, the following objects are not copied to the client database: FOREIGN KEY constraints, UNIQUE constraints, DEFAULT constraints, and the SQL Server ROWGUIDCOL property. This indicates poor support for your scenario
I suggest you remove the unique constraint from the device table.

C# with SQLite and foreign key

I want to implement a patients data base for our software, and have an issue with the foreign key statement. I am using the latest SQLite DLLs, with C#.
When I try to run below code:
dbConnection = "Data Source=SQLiteMyDatabase.db;foreign keys=true;";
if (connections == 0)
{
cnn = new SQLiteConnection(dbConnection);
cnn.Open();
this.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS patients ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL;");
this.ExecuteNonQuery("CREATE TABLE IF NOT EXISTS images ( FOREIGN KEY (patientID) REFERENCES patients(id), nameRed VARCHAR(20) NOT NULL PRIMARY KEY;");
}
I get the error:
SQLite error near "FOREIGN": syntax error
Why does this happen?
In order to create a foreign key, you must first create the column:
CREATE TABLE IF NOT EXISTS images (
nameRed VARCHAR(20) NOT NULL PRIMARY KEY,
patientID INT,
FOREIGN KEY (patientID) REFERENCES patients(id)
);
Please note:
I moved the primary key column (nameRed) first in the list of columns created, but that's just convention. It could go second.
You use the data type VARCHAR and SQlite will accept that, but be aware that it gets mapped to type TEXT, the maximum length will be ignored, and you will be able to store other data types in the column. SQLite is a bit funny when it comes to data types (it has a very well thought-out system but it's quite different from most other SQL databases).
To make things more confusing, you are allowed to declare the PRIMARY KEY as a property of the column (although you can also do it after the list of columns as with the FOREIGN KEY).

Entity Framework bug, insert command generation

I am working with PostgreSql DB using Entity Framework:
When I add new item into DB it generates strange code:
INSERT INTO (SELECT "person_contact"."person_id" AS "person_id",
"person_contact"."contact_id" AS "contact_id"
FROM "public"."person_contact" AS "person_contact")
("person_id","contact_id")
VALUES (cast(141792 as int8),cast(289406040 as int8))
So it add
SELECT "person_contact"."person_id" AS "person_id",
"person_contact"."contact_id" AS "contact_id"
FROM "public"."person_contact" AS "person_contact"
instead of table name "public"."person_contact"
How to resolve this Entity Framework bug ???
UPD: Same issue when I try to delete "person_contact" entry. In delete statement instead of table name - select query.
There are several ways to try and fix this:
Firstly, it could be that your model has become corrupt. You could try deleting the model and recreating it. Also see my answer to this question: SQL Server foreign keys messing with entity framework model
Secondly, you say that it only happens with this table. Is there anything special about this table.
Thirdly, you could try a different .net connector for ProgressSQL, see: http://www.devart.com/dotconnect/entityframework.html
These are listed in the order that I would try them.
Most likely you forgot to create primary key on this table.
I've had the same problem and the solution in my case was very simple. The problem was that I had a column named "id", but I forgot to make it Primary Key. The moment I set it as Primary Key everything was OK.
It is very strange, because EF, normaly won't import table without primary key, but when you have column named "id" it assumes that it is a primary key.
The structure of my table was:
*DROP TABLE IF EXISTS "public"."fact_season_tickets";
CREATE TABLE "public"."fact_season_tickets" (
"id" int8 DEFAULT nextval('fact_season_tickets_id_seq'::regclass) NOT NULL,
"season_ticket_id" int8 NOT NULL,
"date_key" int4 NOT NULL,
"station_id" int4 NOT NULL,
"amount" numeric(18,2) DEFAULT 0 NOT NULL,
"status" int4 NOT NULL
)
WITH (OIDS=FALSE)*
The generated by NpgSql INSERT statement was:
*INSERT INTO (SELECT "fact_season_tickets"."id",
"fact_season_tickets"."season_ticket_id",+
"fact_season_tickets"."date_key",
"fact_season_tickets"."station_id",
"fact_season_tickets"."amount",
"fact_season_tickets"."status"
FROM "public"."fact_season_tickets" AS "fact_season_tickets")
("season_ticket_id","date_key","station_id","amount","status")
VALUES (510::int8,20150630,2,18.00::numeric,1)
RETURNING "id"*
The solution was just creating a primary key:
*ALTER TABLE "public"."fact_season_tickets" ADD PRIMARY KEY ("id");*

SubSonic not recognizing SQLite foreign keys

I'm using SubSonic 3.0.0.3 and I can't seem to get the ActiveRecord .tt files to recognize and generate code for the foreign keys and relationships in my SQLite database.
I think it generates everything else just fine, but after looking at other snippets online it looks like there should be more generated code than just single classes in ActiveRecord.cs and Structs.cs for each of my tables. Looking inside Structs.cs, IsForeignKey is always false for every column, even the ones I have a foreign key defined for. Additionally, each Foreign Keys region is empty within each generated ActiveRecord class.
I'm using VS2008 with references to SubSonic 3.0.0.3, System.Data.SQLite 1.0.66.0, and System.Data.SQLite.Linq 2.0.38.0 in my project. I created the database using SQLite Expert Personal 3.1.0.2076. I made some dummy tables to try to test out how SubSonic handles one:many and many:many relationships. Here's the DDL SQLite Expert spits out for my small database:
CREATE TABLE [Person] (
[PersonID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[PersonName] TEXT NOT NULL,
[PersonAge] INT NOT NULL
);
CREATE TABLE [Group] (
[GroupID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[GroupName] TEXT NOT NULL,
[GroupDescription] TEXT NOT NULL
);
CREATE TABLE [Dog] (
[DogID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[PersonID] INT NOT NULL CONSTRAINT [DogPersonFK] REFERENCES [Person]([PersonID]) ON DELETE CASCADE ON UPDATE CASCADE,
[DogName] TEXT NOT NULL);
CREATE TABLE [GroupPersons] (
[GroupID] INTEGER NOT NULL CONSTRAINT [GroupPersonToGroupFK] REFERENCES [Group]([GroupID]) ON DELETE CASCADE ON UPDATE CASCADE,
[PersonID] INTEGER NOT NULL CONSTRAINT [GroupPersonToPersonFK] REFERENCES [Person]([PersonID]) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT [sqlite_autoindex_GroupPersons_1] PRIMARY KEY ([GroupID], [PersonID]));
I know foreign keys are enabled and work in the database - SQLite Expert says they're on and when I change data in one place, like Person's PersonID, it does indeed change that PersonID in the Dog and GroupPersons tables. I've tried re-adding the database to the project, 'running custom tool' to get the .tt files to execute again, and even deleting them and adding them back. I can get a simple project to build that perform simple querying and insertions, however I tried just now to change the primary key of a single Person, Dog, or Group and x.Save() but System.Data.SQLite threw an exception for all three, saying SQLite error near "WHERE":syntax error. at the Save().
Any suggestions for what I should try to do next?
It seems that the FKTables attribute for each table is not assigned in the file "SQLite.ttinclude". So i add some lines of code and managed to generate foreign key code :
After line 16 (var schema = conn.GetSchema("COLUMNS");), insert :
var schemaForeignKeys = conn.GetSchema("FOREIGNKEYS");
After line 29 (tbl.Name = row["TABLE_NAME"].ToString();), insert :
tbl.FKTables = new List<FKTable>();
var foreignKeyTables = schemaForeignKeys.Select("TABLE_NAME='" + tbl.Name + "'");
foreach (var foreignKeyTable in foreignKeyTables) {
FKTable foreignKey = new FKTable();
foreignKey.ThisTable = foreignKeyTable["TABLE_NAME"].ToString();
foreignKey.ThisColumn = foreignKeyTable["FKEY_FROM_COLUMN"].ToString();
foreignKey.OtherTable = foreignKeyTable["FKEY_TO_TABLE"].ToString();
foreignKey.OtherColumn = foreignKeyTable["FKEY_TO_COLUMN"].ToString();
foreignKey.OtherClass = CleanUp(foreignKey.OtherTable);
foreignKey.OtherQueryable = foreignKey.OtherClass;
tbl.FKTables.Add(foreignKey);
}
And after line 53 (col.IsNullable=row["IS_NULLABLE"].ToString()=="True";), insert :
col.IsForeignKey = tbl.FKTables.Any(x => x.ThisColumn == col.Name);
This is for generate the foreign key code.
Moreover, you have maybe encounter a problem when you have to delete a record which has a column to be a foreign key in an other table ? For exemple :
Person(Id, Name)
Dog(Id, #PersonId)
If you have set the #PersonId foreign key on-delete action to "SET TO NULL", this won't work because foreign key support is disabled by default in SQLite 3.6.23.1 (version used by Data.SQLite 1.0.66.0).
To enable foreign key support, you have to execute this command with each connection :
PRAGMA foreign_keys = ON;
Now, this is not supported by Data.SQLite, but it will (in version 1.0.67.0, http://sqlite-dotnet2.cvs.sourceforge.net/viewvc/sqlite-dotnet2/SQLite.NET/System.Data.SQLite/SQLiteConnection.cs?r1=1.80&r2=1.81).
So you have to wait for the release or you can (like me) download the Data.SQLite source and compile the last version. It work great for me.
Good luck.
And sorry for my english :)
I'm trying to reason this. There seems to be two issues at hand:
Subsonic not recognising your foreign keys
The x.Save() function sending that error message.
SQLite will be enforcing referential integrity on its own, so while Subsonic does not see the foreign references, SQLite does, and that's why your updates go through. Subsonic does not drive SQLite, it is driving itself, which is fine.
I'm attempting to learn what SubSonic is and does. In the mean time, I have this hypothesis: the table definitions are not parsed correctly. If x.Save() is uses automatically generated SQL, it could be that the two issues are really just one.
To validate (or invalidate) this hypothesis, could you try defining the tables thus, giving the foreign keys as table attributes, not attributes of specific fields:
CREATE TABLE [Dog] (
[DogID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[PersonID] INTEGER NOT NULL,
[DogName] TEXT NOT NULL,
FOREIGN KEY ([PersonID]) REFERENCES [Person]([PersonID]) ON DELETE CASCADE ON UPDATE CASCADE);
CREATE TABLE [GroupPersons] (
[GroupID] INTEGER NOT NULL,
[PersonID] INTEGER NOT NULL,
FOREIGN KEY ([GroupID]) REFERENCES [Group]([GroupID]) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ([PersonID]) REFERENCES [Person]([PersonID]) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY ([GroupID], [PersonID]));

Categories

Resources