SQL may cause cycles or multiple cascade paths - c#

I edited the whole question because i managed to find what triggers the error so i just leave the SQL approach since it's easy to test
may cause cycles or multiple cascade paths. Specify ON DELETE NO
ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY
constraints.
DROP TABLE dbo.ProductionUnits
CREATE TABLE ProductionUnits
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(50)
)
DROP TABLE Cells
CREATE TABLE Cells
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(10),
ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
DROP TABLE CheckLists
CREATE TABLE CheckLists
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(20),
CellId INT FOREIGN KEY REFERENCES Cells(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
DROP TABLE CheckListGroups
CREATE TABLE CheckListGroups
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(20),
CheckListId INT FOREIGN KEY REFERENCES CheckLists(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
DROP TABLE CheckListGroupItems
CREATE TABLE CheckListGroupItems
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(20),
CheckListGroupId INT FOREIGN KEY REFERENCES CheckListGroups(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
DROP TABLE Shifts
CREATE TABLE Shifts
(
Id INT PRIMARY KEY IDENTITY,
StartTime TIME,
EndTime TIME,
ShiftDescription NVARCHAR(20),
ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
DROP TABLE ProductionRecords
CREATE TABLE ProductionRecords
(
Id INT PRIMARY KEY IDENTITY,
CreatedOn DATETIME,
CreatedBy NVARCHAR(50),
ModifiedOn DATETIME,
ModifiedBy NVARCHAR(50)
)
DROP TABLE dbo.Referencias
CREATE TABLE Referencias
(
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(15),
)
DROP TABLE dbo.CheckListRecords
CREATE TABLE CheckListRecords
(
Id INT PRIMARY KEY IDENTITY,
Value NVARCHAR(3),
ReferenciaId INT FOREIGN KEY REFERENCES Referencias(Id) ON DELETE CASCADE ON UPDATE CASCADE,
ShiftId INT FOREIGN KEY REFERENCES Shifts(Id) ON DELETE CASCADE ON UPDATE CASCADE,
CheckListGroupItemId INT FOREIGN KEY REFERENCES dbo.CheckListGroupItems(Id) ON DELETE CASCADE ON UPDATE CASCADE,
ProductionRecordsId INT FOREIGN KEY REFERENCES ProductionRecords(Id) ON DELETE CASCADE ON UPDATE CASCADE
)
So the problem is when I add
CheckListGroupItemId INT FOREIGN KEY REFERENCES dbo.CheckListGroupItems(Id) ON DELETE CASCADE ON UPDATE CASCADE,
But i need this to know which item belongs to but this works if i delete the ON DELETE CASCADE
I'm not very experienced to SQL when it comes to foreign keys and handling cascade paths-
What should I do in this situation?

There are two paths to deleting CheckListRecords when you delete a ProductionUnits row. This is what it's complaining about.
ProductionUnits ->
Cells ->
CheckLists ->
CheckListGroups ->
CheckListGroupItems ->
CheckListRecords
...and...
ProductionUnits ->
Shifts ->
CheckListRecords
Bummer - you can't have that...an there's no real simple way to get around it :-(
It's a common occurrence. But, have hope - there's a reasonable way to deal with it...that isn't too awful. When you get a message like that, work your way backwards from the table it's griping about...and when you find multiple ON DELETE CASCADE columns in the delete path that point to the same table, you've found your culprit.
Before I suggest a solution, a couple of observations: for one thing, you've got Ids that are identity-generated. So, On Update Cascade probably shouldn't be specified in such a world because the IDs can't readily change (unless you're behaving very badly somewhere).
Another minor best-practice kinda note: specify the schema when creating the tables:
CREATE TABLE dbo.ProductionUnits
(
-- etc.
)
...and always use the schema when referring to the table in views and procedures.
Okay - so how to deal with the conflict? What you might consider doing is removing the foreign key from CheckListRecords to Shifts and instead, implement a delete trigger on Shifts that deletes the affected CheckListRecords rows. A long time ago...before there was declarative referential integrity, you had to deal with managing the relationships with triggers. It still comes in handy in places like this.
Assuming you've added the schema to the table declarations, it would be something roughly like:
create trigger [Shifts.Delete.Trigger] on dbo.Shifts for delete as
begin
set nocount on;
delete dbo.CheckListRecords where ShiftId in ( select Id from deleted );
end
See, that wasn't so bad, eh?
If you just have to update Shifts.Id for some reason, it gets a bit more complicated. You would have to have an alternate key on the Shifts table in order to implement the logic...preferably a column that you never update or at least one that is not updated when the Id gets changed. Maybe the StartTime column? You could add a uniqueidentifier column if you don't have a candidate key. For example:
CREATE TABLE Shifts
(
Id INT PRIMARY KEY IDENTITY,
StartTime TIME,
EndTime TIME,
ShiftDescription NVARCHAR(20) ,
ProductionUnitId INT FOREIGN KEY REFERENCES ProductionUnits(Id) ON DELETE CASCADE ON UPDATE CASCADE,
RowId UNIQUEIDENTIFIER
CONSTRAINT [Shifts.RowId.Default] DEFAULT ( NEWID() )
CONSTRAINT [Shifts.RowId.Unique] UNIQUE
)
Most of your views and logic would just completely ignore this column. You don't even need to let EF see it. With how I declared it, it gets set automatically on insert (similar to identity columns).
The only code that uses RowId is the update trigger:
create trigger [Shifts.Update.Trigger] on dbo.Shifts for update as
begin
set nocount on;
if update( Id ) --> just skip everything if the Id did not change
begin
update dbo.CheckListRecords
set ShiftId = i.Id
from
inserted i
inner join
deleted d
on
i.RowId = d.RowId
inner join
dbo.CheckListRecords c
on
c.ShiftId = d.Id
end
end
It's not super horrible...but not ideal. Really, if you don't have any reason to update the ID columns (and you really really shouldn't), you can skip all of this update trigger and extra column nonsense.
I think, if you have trigger-based referential integrity, you have to tell EF about it...but EF is not the way of my people...so I can't help with that particular point. But I'm almost certain it's a fairly simple declaration when you describe your data model to EF.

Related

Asp.Net Core Entity Framework Migration Error: index needed in a foreign key constraint [duplicate]

I need to ALTER my existing database to add a column. Consequently I also want to update the UNIQUE field to encompass that new column. I'm trying to remove the current index but keep getting the error MySQL Cannot drop index needed in a foreign key constraint
CREATE TABLE mytable_a (
ID TINYINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
UNIQUE(Name)
) ENGINE=InnoDB;
CREATE TABLE mytable_b (
ID TINYINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
UNIQUE(Name)
) ENGINE=InnoDB;
CREATE TABLE mytable_c (
ID TINYINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
UNIQUE(Name)
) ENGINE=InnoDB;
CREATE TABLE `mytable` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`AID` tinyint(5) NOT NULL,
`BID` tinyint(5) NOT NULL,
`CID` tinyint(5) NOT NULL,
PRIMARY KEY (`ID`),
UNIQUE KEY `AID` (`AID`,`BID`,`CID`),
KEY `BID` (`BID`),
KEY `CID` (`CID`),
CONSTRAINT `mytable_ibfk_1` FOREIGN KEY (`AID`) REFERENCES `mytable_a` (`ID`) ON DELETE CASCADE,
CONSTRAINT `mytable_ibfk_2` FOREIGN KEY (`BID`) REFERENCES `mytable_b` (`ID`) ON DELETE CASCADE,
CONSTRAINT `mytable_ibfk_3` FOREIGN KEY (`CID`) REFERENCES `mytable_c` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB;
mysql> ALTER TABLE mytable DROP INDEX AID;
ERROR 1553 (HY000): Cannot drop index 'AID': needed in a foreign key constraint
You have to drop the foreign key. Foreign keys in MySQL automatically create an index on the table (There was a SO Question on the topic).
ALTER TABLE mytable DROP FOREIGN KEY mytable_ibfk_1 ;
Step 1
List foreign key ( NOTE that its different from index name )
SHOW CREATE TABLE <Table Name>
The result will show you the foreign key name.
Format:
CONSTRAINT `FOREIGN_KEY_NAME` FOREIGN KEY (`FOREIGN_KEY_COLUMN`) REFERENCES `FOREIGN_KEY_TABLE` (`id`),
Step 2
Drop (Foreign/primary/key) Key
ALTER TABLE <Table Name> DROP FOREIGN KEY <Foreign key name>
Step 3
Drop the index.
If you mean that you can do this:
CREATE TABLE mytable_d (
ID TINYINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(255) NOT NULL,
UNIQUE(Name)
) ENGINE=InnoDB;
ALTER TABLE mytable
ADD COLUMN DID tinyint(5) NOT NULL,
ADD CONSTRAINT mytable_ibfk_4
FOREIGN KEY (DID)
REFERENCES mytable_d (ID) ON DELETE CASCADE;
> OK.
But then:
ALTER TABLE mytable
DROP KEY AID ;
gives error.
You can drop the index and create a new one in one ALTER TABLE statement:
ALTER TABLE mytable
DROP KEY AID ,
ADD UNIQUE KEY AID (AID, BID, CID, DID);
A foreign key always requires an index. Without an index enforcing the constraint would require a full table scan on the referenced table for every inserted or updated key in the referencing table. And that would have an unacceptable performance impact.
This has the following 2 consequences:
When creating a foreign key, the database checks if an index exists. If not an index will be created. By default, it will have the same name as the constraint.
When there is only one index that can be used for the foreign key, it can't be dropped. If you really wan't to drop it, you either have to drop the foreign key constraint or to create another index for it first.
Because you have to have an index on a foreign key field you can just create a simple index on the field 'AID'
CREATE INDEX aid_index ON mytable (AID);
and only then drop the unique index 'AID'
ALTER TABLE mytable DROP INDEX AID;
I think this is easy way to drop the index.
set FOREIGN_KEY_CHECKS=0; //disable checks
ALTER TABLE mytable DROP INDEX AID;
set FOREIGN_KEY_CHECKS=1; //enable checks
drop the index and the foreign_key in the same query like below
ALTER TABLE `your_table_name` DROP FOREIGN KEY `your_index`;
ALTER TABLE `your_table_name` DROP COLUMN `your_foreign_key_id`;
Dropping FK is tedious and risky. Simply create the new index with new columns and new index name, such as AID2. After the new Unique Index is created, you can drop the old one with no issue. Or you can use the solution given above to incorporate the "drop index, add unique index" in the same alter table command. Both solutions will work
In my case I dropped the foreign key and I still could not drop the index. That was because there was yet another table that had a foreign key to this table on the same fields. After I dropped the foreign key on the other table I could drop the indexes on this table.
If you are using PhpMyAdmin sometimes it don't show the foreign key to delete.
The error code gives us the name of the foreign key and the table where it was defined, so the code is:
ALTER TABLE your_table DROP FOREIGN KEY foreign_key_name;
You can show Relation view in phpMyAdmin and first delete foreign key. After this you can remove index.
You can easily check it with DBeaver. Example:
As you can see there are 3 FKs but only 2 FK indexes. There is no index for FK_benefCompanyNumber_beneficiaries_benefId as UK index provide uniqueness for that FK.
To drop that UK you need to:
DROP FK_benefCompanyNumber_beneficiaries_benefId
DROP UK
CREATE FK_benefCompanyNumber_beneficiaries_benefId
The current most upvoted answer is not complete.
One needs to remove all the foreign keys whose "source" column is also present in the UNIQUE KEY declaration.
So in this case, it is not enough to remove mytable_ibfk_1 for the error to go away, mytable_ibfk_2 and mytable_ibfk_3 must be deleted as well.
This is the complete answer:
ALTER TABLE mytable DROP FOREIGN KEY mytable_ibfk_1;
ALTER TABLE mytable DROP FOREIGN KEY mytable_ibfk_2;
ALTER TABLE mytable DROP FOREIGN KEY mytable_ibfk_3;
Its late now but I found a solution which might help somebody in future.
Just go to table's structure and drop foreign key from foreign keys list. Now you will be able to delete that column.

create table that is many to many between 2 tables in different databases

So i have 2 databases. DB1 and DB2. My 'discountCode' table is in DB1 and my 'AspnetUser' table is in DB2. I want to do a many to many table in DB1 between 'discountCode' table and 'AspnetUser' table so i can se that a user already have used a specific discountcode so the user cant use it twice.
I tried doing a normal many to many table between them and it went like this:
CREATE TABLE [dbo].[DiscountUser] (
[DiscountId] INT NOT NULL,
[UserId] nvarchar(128) NOT NULL,
[LanguageId] INT NOT NULL,
CONSTRAINT [PK_DiscountUser] PRIMARY KEY CLUSTERED ([DiscountId] ASC, [UserId] ASC),
CONSTRAINT [FK_DiscountUser_Discount_DiscountId] FOREIGN KEY ([DiscountId]) REFERENCES [dbo].[DiscountCode] ([Id]) ON DELETE CASCADE,
CONSTRAINT [FK_DiscountUser_AspNetUsers_Id] FOREIGN KEY ([UserId]) REFERENCES [db2].dbo.[AspNetUsers] ([id]) ON DELETE CASCADE,
CONSTRAINT [FK_DiscountUser_Language] FOREIGN KEY ([LanguageId]) REFERENCES [dbo].[Language] ([Id])
I then want to update my entity framework modeldesign so i can include this new many to many table in my c# project. So it is important that however i fix this problem i will still be able to use this many to many table like any other entity i have.
First i thought that i would put all the tables in the same database untill i asked about this problem to a friend. My friend did not have much time. All he said to me was do a join in your c# code to bring the 2 tables together. Just ask stackoverflow and they will help you how. So here i am. How do i do a join in m y c# code or is there maybe a better way to get around my problem?
You can't have foreign keys between databases - the error is pretty clear about that. But you can still join them together in a query, you just won't get protection that having them joined by a foreign key gives (e.g. without a foreign key, you can end up with orphaned entries with no data in the foreign table).
If you can have them in a single database that would be best.

Entity Framework “Update model from Database, table becomes relation

I'm trying to create a table from within visual studio and update my .edmx file inside Visual Studio by right-clicking the file and selecting Update Model from Database.
My table looks like this:
CREATE TABLE [dbo].[tableName] (
[UserId] UNIQUEIDENTIFIER NOT NULL,
[CategoryId] INT NOT NULL,
CONSTRAINT [PK_responsibleUser] PRIMARY KEY NONCLUSTERED ([UserId], [pkID] ASC),
CONSTRAINT [FK_responsibleUser_user] FOREIGN KEY ([UserId]) REFERENCES [dbo].[users] ([UserId]) ON DELETE CASCADE,
CONSTRAINT [FK_categoryResponsibleUser_category] FOREIGN KEY ([CategoryId]) REFERENCES [dbo].[categories] ([CategoryId]) ON DELETE CASCADE
);
This table contains two foreign keys to the tables i want to link. After I run the script "Update Model from Database", a relation between the two tables pops up.
Now Heres the problem: I need EF to generate an instance of my class in Visual studio like this:
public virtual DbSet<tableName> TableIWantToBeGeneratedByEF { get; set; }
As I said, with the current mapping it just creates a relation between two tables. How do I alter my query when creating the table for this to happen? Can this be achieved while using two foreign keys as the primary key or what?
Just open the model designed, select all (ctrl + A), press 'delete' button to delete them, and finally right click and select Update Model from Database. This will force EF to re-generate all model.
(Or just delete all referenced tables so that they are also regenerated)
Almost forgot. It seems that it was the compound primary keys that caused the problem. I changed so both my foreign keys just acted like foreign keys and not as a combined primary key, then created a columnn named Id instead with datatype INT Identity.
Ran the script "Update Model from Database" in .edmx designer and boom, there it was.

Deleting multiple tables from SQL Server 2008 using Datalist C#

This may seem a common question but I googled to find the right answer that can fix my problem and failed to do so.
I have multiple tables connected to each other by ProductID and I wish to delete all data from them when the product from main table has been deleted. i.e.
Products : ProductID - Vender - Description
ProductRatings : ProductID - Rating - VisitorsCount
ProductComments : ProductID - VisitorName - Comment
I read that for such situation a SQL trigger is used but I have no idea about it besides I might be mentioning my DataSource in ASCX.CS file in some cases and in some cases I might simply use SqlDatasoruce in ASCX file. Is there any query or stored procedure that can be used?
The easiest way to do this is to implement a foreign key relationship to ProductID and set on delete cascade. This is a general idea:
create table ProductRatings
(
ProductID int not null
foreign key references Products(ProductID) on delete cascade,
Rating int not null,
VisitorsCount int not null
)
What that does is when you delete a primary key value from the Products table, that causes SQL Server to delete all records that have a foreign key constraint to that primary key value. If you do this with your ProductComments table as well, problem solved. No need to explicitly call a DELETE on any records in the referencing tables.
And if you aren't using referential integrity...you should.
EDIT: this also holds true for UPDATEs on the primary key. You just need to specify on update cascade, and the foreign key references will update as the primary key did to ensure RI.

When deleting a record that is referenced in another table, how do I know when to stop?

I have the following table structure in my database:
create table Encargado(
ID int primary key,
Nombre varchar(300),
)
create table Area(
ID int primary key,
Nombre varchar(300),
Jefe int foreign key references Encargado(ID)
)
create table Carrera(
ID int primary key,
Nombre varchar(300),
Area int foreign key references Area(ID)
)
create table Formacion(
ID int primary key,
Grado varchar(300),
Lugar varchar(300)
)
create table Docente(
ID int primary key,
Nombre varchar(300),
Carrera int foreign key references Carrera(ID),
Formacion int foreign key references Formacion(ID),
Horario varchar(300)
)
create table Evaluacion(
ID int primary key,
Docente int foreign key references Docente(ID),
Evaluador varchar(300),
Secuencia int,
Pizarra int,
Audiovisual int,
Letra int,
Voz int,
GestosVocabulario int,
Ejemplificacion int,
Respuestas int,
DominioEscenico int,
Participacion int,
Observacion varchar(4000),
Materias varchar(3000),
Valido bit
)
create table Seguimiento(
ID int primary key,
Docente int foreign key references Docente(ID),
Modulo int,
Semestre int,
Ano int,
Fecha datetime,
Hora datetime,
OrdenSecuencia bit,
OrdenSecuenciaObservacion varchar(200),
PortafolioAlumno bit,
PortalofioAlumnoObservacion varchar(200),
AspectosParaEntrevista varchar(3000),
Conclusiones varchar(3000),
Evaluador varchar(300),
DirectorDeArea int foreign key references Encargado(ID),
EncargadoControl int foreign key references Encargado(ID),
)
Say I want to delete an Area, how would I do this? I would need to also delete all Carreras and also all the Docentes.
public void Delete(Area area)
{
db.Carreras.DeleteAllOnSubmit(area.Carreras);
//I'm stuck here. Is this what I should be doing?
}
Can someone suggest how to handle this?
I'm using C# and Linq-to-SQL. I feel I may have dug myself into a hole by using this table structure or perhaps that's one of the downfalls of a relational database? :\
I wouldn't handle this on the Linq-to-SQL side, I'd use cascading deletes on the database side if you truly want to delete all the child records.
For example, with Oracle you can add a "ON DELETE CASCADE" clause to your create table statements, refer to this link.
The cascading delete will handle deleting all the records from the child tables, all with a single delete operation. The beauty of this approach is that no matter where you perform the operation, albeit via Linq-To-SQL, JAVA, ROR, PHP, etc, the logic is centralized in the DB so it works the same way no matter who does the delete.
It should depend on how you want to handle your foreign key relationship. i.e. deleting foreign references or leaving them in case they have other entries dependent on them, etc.
See referential integrity http://msdn.microsoft.com/en-us/library/ms186973.aspx
So, in the end you should probably let the DB handle it.
Say I want to delete an Area, how
would I do this? I would need to also
delete all Carreras and also all the
Docentes.
On the face of it, it seems you want to change the declarative referential integrity (DRI) action for ON DELETE from the default NO ACTION (i.e. prevent the referenced row from being deleted) to CASCADE (i.e. also delete the rows in the referening table).
Note that such logic usually suggests that the referening column (e.g. Carrera.Area) should be defined as NOT NULL.
For example:
CREATE TABLE Carrera
(
...
Area INTEGER NOT NULL
REFERENCES Area (ID)
ON DELETE CASCADE
);
CREATE TABLE Docente
(
...
Carrera INTEGER NOT NULL
REFERENCES Carrera (ID)
ON DELETE CASCADE,
...
);
However, looking deeper we see that
Evaluacion REFERENCES Docente
Seguimiento REFERENCES Docente
You need to consider whether these too require the ON DELETE CASCADE DRI action.
Furthermore:
Seguimiento REFERENCES Encargado -- twice
Area REFERENCES Encargado
In other words, you have a potential cycle here. Even if your DBMS would allow ON DELETE CASCADE DRI actions on all these (SQL Server, for example, would not) you should consider managing the logic by 'manually' removing rows.
Something else to consider, seeing all those seemingly NULLable columns (but how can you primary key columns be nullable...?) you could consider the ON DELETE SET NULL DRI action. Personally, I would clarify the design by removing the NULLable columns are creating new relationship tables but that could involve a lot of work :)
Have you considered a "logical delete" instead of a physical delete?
Logical deletes make sense when you want to keep historical access to data (for reports or queries) even after they have become obsolete.
Example: your school used to teach Latin, and have a number of professors teaching it, and a number of students enrolled.
Next year, Latin gets removed from the available courses. One of the professors retires, the others go on with other courses. Students still need to prove they got a vote in Latin, even if this will not be part of future offerings.
Solution: add a boolean flag to the Course table (Active=Y/N) and adapt your program so that it excludes Courses (or professors, or anything else) having "Active=N" from queries that must return what is "live", and keep them in for historical reports.

Categories