Generate custom EF6 code first migration SQL after foreign key constraints - sql-server

I'm adding custom SQL to an EF6 code first migration. The SQL adds delete triggers to various tables. It's generating correct syntax and getting output as expected.
But the location of the output is giving me trouble. Code first migrations put all the foreign key constraints at the bottom of the generated SQL. When creating the constraints for a join table (used in a many-to-many association), the ON DELETE CASCADE clause is added. All fine and dandy, except when I've added a delete trigger to one of the tables participating in that relationship -- cascade delete isn't allowed coming from a table with a delete trigger on it.
Ideally, I'd like to do one of two things -- either move my SQL to the bottom of the script (where I can yank the cascade deletes off the constraints after they're added) or somehow tell EF not to cascade delete certain particular join tables, while leaving the default behavior in place for the more common case where the cascade deletes are the correct thing to do.
Anyone have a clue as to how this can be done?
Thanks.

Related

Self foreign key may cause cycles or multiple cascade paths [duplicate]

In SQL Server 2005 I just struck the infamous error message:
Introducing FOREIGN KEY constraint XXX on table YYY may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Now, StackOverflow has several topics about this error message, so I've already got the solution (in my case I'll have to use triggers), but I'm curious as to why there is such a problem at all.
As I understand it, there are basically two scenarios that they want to avoid - a cycle and multiple paths. A cycle would be where two tables have cascading foreign keys to each other. OK, a cycle can span several tables too, but this is the basic case and will be easier to analyze.
Multiple paths would be when TableA has foreign keys to TableB and TableC, and TableB also has a foreign key to TableC. Again - this is the minimum basic case.
I cannot see any problems that would arise when a record would get deleted or updated in any of those tables. Sure, you might need to query the same table multiple times to see which records need updating/deleting, but is that really a problem? Is this a performance issue?
In other SO topics people go as far as to label using cascades as "risky" and state that "resolving cascade paths is a complex problem". Why? Where is the risk? Where is the problem?
You have a child table with 2 cascade paths from the same parent: one "delete", one "null".
What takes precedence? What do you expect afterwards? etc
Note: A trigger is code and can add some intelligence or conditions to a cascade.
The reason we forbid using cascade delete has to do with performance and locking. Yes it's not so bad when you delete one record but sooner or later you will need to delete a large group of records and your database will comes to a standstill.
If you are deleting enough records, SQL Server might escalate to a table lock and no one can do anything with the table until it is finished.
We recently moved one of our clients to his own server. As part of the deal we also then had to delete all of that client's records form our original server. Deleting all his information in batches (so as not to cause problems with other users) took a couple of months. If we had cascade delete set up, the database would have been inaccessible to the other clients for a long time as millions of records were deleted in one transaction and hundreds of tables were locked until the transaction was done.
I could also see a scenario where a deadlock might have occured in using cascade delete because we have no control over the order the cascade path would have taken and our database is somewhat denormalized with clientid appearing in most tables. So if it locked the one table that had a foreign key also to a third table as well as the client table that was in a differnt path, it possibly couldn't check that table in order to delete from the third table because this is all one transaction and the locks wouldn't be released until it was done. So possibly it wouldn't have let us set up cascade deletes if it saw the possibility of creating deadlocks in the transaction.
Another reason to avoid cascading deletes is that sometimes the existence of a child record is reason enough not to delete the parent record. For instance, if you have a customer table and that customer has had orders in the past, you would not want to delete him and lose the information on the actual order.
Consider a table of employees:
CREATE TABLE Employee
(
EmpID INTEGER NOT NULL PRIMARY KEY,
Name VARCHAR(40) NOT NULL,
MgrID INTEGER NOT NULL REFERENCES Employee(EmpID) ON DELETE CASCADE
);
INSERT INTO Employees( 1, "Bill", 1);
INSERT INTO Employees( 23, "Steve", 1);
INSERT INTO Employees(234212, "Helen", 23);
Now suppose Bill retires:
DELETE FROM Employees WHERE Name = "Bill";
Ooooppps; everyone just got sacked!
[We can debate whether the details of the syntax are correct; the concept stands, I think.]
I think the problem is that when you make one path "ON DELETE CASCADE" and the other "ON DELETE RESTRICT", or "NO ACTION" the outcome (the result) is unpredicable. It depends on which delete-trigger (this is also a trigger, but one you don't have to build yourself) will be executed first.
I agree with that cascades being "risky" and should be avoided. (I personally prefer cascading the changes manually rather that having sql server automatically take care of them). This is also because even if sql server deleted millions of rows, the output would still show up as
(1 row(s) affected)
I think whether or not to use a ON DELETE CASCADE option is a question of the business model you are implementing. A relationship between two business objects could be either a simple "association", where both ends of the relationship are related, but otherwise independent objects the lifecycle of which are different and controlled by other logic. There are, however, also "aggregation" relationships, where one object might actually be seen as the "parent" or "owner" of a "child" or "detail" object. There is the even stronger notion of a "composition" relationship, where an object solely exists as a composition of a number of parts.
In the "association" case, you usually won't declare an ON DELETE CASCADE constraint. For aggregations or compositions, however, the ON DELETE CASCADE helps you mapping your business model to the database more accurately and in a declarative way.
This is why it annoys me that MS SQL Server restricts the use of this option to a single cascade path. If I'm not mistaken, many other widely used SQL database systems do not impose such restrictions.

Trigger/Constraints for deleting table hierarchy

I have the following table structure which stores some results of my application. Each hierarchy level has a data row attached which again consists of multiple values. A data row is exclusively assigned to a single table of the hierarchy tables, not many in parallel. My goal is to delete the Parent including all hierarchy levels, the data and values.
My first attempt was to add ON DELETE CASCADE constraints everywhere but ON DELETE CASCADE does not work with a diamond structure. It results in:
Introducing FOREIGN KEY constraint 'FK_Data_Hierarchy2' on table 'Data' may cause cycles or cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other KEY constraints.
Then my second attempt was to remove the cascade constraint on the Data table and add a INSTEAD OF DELETE trigger to Hierarchy 1-3 which deletes the Data entries before the own rows. But also there the SQL Server complains that you cannot add such a trigger due to the ON DELETE CASCADE which is there for the hierarchy nesting.
Cannot create INSTEAD OF DELETE or INSTEAD OF UPDATE TRIGGER 'TR_Hierarchy1_Delete' on table 'dbo.Hierarchy'. This is because the table has a FOREIGN KEY with cascading DELETE or UPDATE.
My last plan would be to remove all ON DELETE CASCADE constraints and delete the whole tree manually in a trigger. But this is quite a challenge do it in an optimized fashion.
What is really the best solution for this job? Iterating over the rows using cursors? (possibly slow due to iteration)
Use combined joins to select the affected rows for every individual table? (possibly slow due to redundant joining the whole hierarchy)
Or does somebody know a trick how to delete over multiple tables using a result set?

Can SQL Server tell you what paths it has found when it can't create a foreign key?

I'm trying to introduce a foreign key with ON DELETE SET NULL into a database with a complex schema.
The script I'm using has this command (names changed):
ALTER TABLE a
ADD CONSTRAINT FK_a_b
FOREIGN KEY (b_id)
REFERENCES b (b_id)
ON DELETE SET NULL;
When I run this script I get the following error message:
Introducing FOREIGN KEY constraint 'FK_a_b' on table 'b' may cause
cycles or multiple cascade paths.
I can't see what the multiple paths are, is there some way I can get SQL Server to list them?
You can check dependencies as outlined here:
https://msdn.microsoft.com/en-us/library/ms190624.aspx
When you use the T-SQL as shown, you get only one level, but if you use SSMS, it will cascade down multiple levels.
There's no built-in functionality just for that purpose. I suppose that with recursive CTE you could write a query on sys.foreign_keys and couple of other system views, that would list cascade paths.
But simplest solution is to do that with Database Diagrams. Start by adding the table that's not letting you add the constraint, then right click and select "Add Related Tables". Then select all the tables and add related tables again, and so on. To keep the diagram clean, in the process unselect or remove tables that you know are not part of the problem.
After you're done, you'll be able to see cycles/multi cascades: they will be chains of 1-N (direcion matters) or 1-1 relations starting and ending on the same table, or leading from one table to another through different paths.
You could use code similar to below to exlude tables that don't have any FK constraints with update/delete actions other than NO_ACTION (0). Building on that you could also exlude tables with only one FK constraint of this type.
select
object_name(parent_object_id),
*
from sys.foreign_keys fk
where 1=2
or delete_referential_action <> 0
or update_referential_action <> 0
I know it may be time-consuming especially for big databases, and you have to know your db well, but that's my best shot for now :).

SQL Server Foreign Key cause cycles or multiple cascade paths

I'm having problems adding a cascade delete onto a foreign key in SQL Server. Table A has three columns. Column 1 and 2 in Table A are foreign key look ups to the same column in Table B. I want a delete of a row in Table B to cascade a delete on a row on Table A based on these foreign keys.
The other column in Table A has a foreign key lookup to table C. If a row in table C is deleted then I want the corresponding cell to be set to null in Table A.
When I add in these constraints I am thrown the error:
Introducing FOREIGN KEY constraint 'FK_RDU_TODELIVERABLEUNITREF' on table 'RelatedDeliverableUnit' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
I am a little stuck with this, Oracle seems perfectly happy with this logic. I am adding in these constraints using Liquibase. I think the error is down to my logic and not syntax but for completeness here is the liquidbase script that manages the foreign keys:
<addForeignKeyConstraint constraintName="FK_RDU_FROMDELIVERABLEUNITREF" baseTableName="relatedDeliverableUnit"
baseColumnNames="FROMDELIVERABLEUNITREF" referencedTableName="DELIVERABLEUNIT" referencedColumnNames="DELIVERABLEUNITREF" onDelete="CASCADE"/>
<addForeignKeyConstraint constraintName="FK_RDU_TODELIVERABLEUNITREF" baseTableName="relatedDeliverableUnit"
baseColumnNames="TODELIVERABLEUNITREF" referencedTableName="DELIVERABLEUNIT" referencedColumnNames="DELIVERABLEUNITREF" onDelete="CASCADE"/>
<addForeignKeyConstraint constraintName="FK_RDU_RELATIONSHIPREF"
baseTableName="relatedDeliverableUnit" baseColumnNames="RELATIONSHIPREF" referencedTableName="RELATIONSHIPTYPES" referencedColumnNames="RELATIONSHIPREF" onDelete="SET NULL"/>
Thanks in advance for any help
I can't find corresponding documentation for later versions, but the SQL Server 2000 BOL addresses this issue:
The series of cascading referential actions triggered by a single DELETE or UPDATE must form a tree containing no circular references. No table can appear more than once in the list of all cascading referential actions that result from the DELETE or UPDATE. The tree of cascading referential actions must not have more than one path to any given table. Any branch of the tree is terminated when it encounters a table for which NO ACTION has been specified or is the default.
And later versions haven't changed this. You're falling foul of this:
The tree of cascading referential actions must not have more than one path to any given table
The only way I know of to accomplish this is to implement one of the cascades between B and A using an INSTEAD OF trigger, rather than using ON DELETE....
The relation between tables A and C shouldn't be impacted by any of this.
(2008 BOL)

Cascade Delete Use Case

I am pretty new to Business Analysis. I have to write requirements that show both (for now) cascade delete (for two tables) and the rest of the tables will delete explicitly.
I need some guidance for how to write the requirements for cascade deletion.
Delete child entities on parent
deletion.
Delete collection members if collection entity is deleted.
Actually it is hard to understand the task without context and also it smells like university/colledge homework (we had one very similar to this).
Use the ON DELETE CASCADE option to specify whether you want rows deleted in a child table when corresponding rows are deleted in the parent table. If you do not specify cascading deletes, the default behavior of the database server prevents you from deleting data in a table if other tables reference it.
If you specify this option, later when you delete a row in the parent table, the database server also deletes any rows associated with that row (foreign keys) in a child table. The principal advantage to the cascading-deletes feature is that it allows you to reduce the quantity of SQL statements you need to perform delete actions.
For example, the all_candy table contains the candy_num column as a primary key. The hard_candy table refers to the candy_num column as a foreign key. The following CREATE TABLE statement creates the hard_candy table with the cascading-delete option on the foreign key:
CREATE TABLE all_candy
(candy_num SERIAL PRIMARY KEY,
candy_maker CHAR(25));
CREATE TABLE hard_candy
(candy_num INT,
candy_flavor CHAR(20),
FOREIGN KEY (candy_num) REFERENCES all_candy
ON DELETE CASCADE)
Because ON DELETE CASCADE is specified for the dependent table, when a row of the all_candy table is deleted, the corresponding rows of the hard_candy table are also deleted. For information about syntax restrictions and locking implications when you delete rows from tables that have cascading deletes, see Considerations When Tables Have Cascading Deletes.
Source: http://publib.boulder.ibm.com/infocenter/idshelp/v10/index.jsp?topic=/com.ibm.sqls.doc/sqls292.htm
You don't write use cases for functionality - that is the reason why it is hard to properly answer your question - we don't know the actor who interacts with the system and of course we know nothing about the system, so we cannot tell you how to write description of their interactions.
You should write your use cases first and from them derive the functionality.

Resources