THE TABLES:
Shop
Product
Category
THE RELATIONSHIPS:
(Shop) 1 <---> n (Categories)
(Shop) 1 <---> n (Products)
(Categories) n <---> n (Products)
THE CASCADE DELETES:
Shop ---> Categories ... I defined this using fluent API
Shop ---> Products ... I defined this using fluent API
Categories <---> Products ... EF 4.1 automatically defines cascade for "Category_Product" join table
THE PROBLEM:
Above results in a "multiple" cascade deletion path exception.
POTENTIAL FIXES:
Remove the ManyToManyConvention, but that means I must manually perform deletes for every join table in the system, which is impractical.
I can remove the cascade delete from Shop->Category or Shop->Products. But then I'll probably have lots of orphaned records.
How are you folks dealing with this problem?
THANKS
This is not a problem of entity framework but the problem of SQL server. I don't think that exception actually means circular cascade delete. It more probably means multiple cascade delete paths because join table records can be deleted from both categories and products side because of cascading from shop. SQL server doesn't allow this because it requires some more complex (and slow) algorithms to correctly compute which records and when have to be deleted when cascading.
Simply you must break this and it will really mean that you will have to manually delete all related records (either categories or products) before you delete shop. This will require stored procedure (or direct SQL DELETE command) otherwise you will have to load all of them first and delete them one by one.
Edit:
As you pointed in the comment this can be also solved by adding BEFORE DELETE trigger which will delete related records if exists as replacement of one cascade path.
Related
I am looking for help with database design for a small project I am working on.
In short what I am trying to achieve is to have say the following tables:
Paddocks
Paddock ID
Paddock Name
Paddock Size
etc.
Cattle
Herd ID
Herd Name
Number of cows
Current Paddock
Cattle_Movements
Herd Name
Current Paddock
New Paddock
Date
etc.
I was hoping to have the 'Cattle_Movements' table be like a summary of all movements of a herd of cattle. And when a herd is moved from one paddock to another it would update the 'Current Paddock' field in the 'Cattle' table.
At this stage I am trying to workout the relationships, queries and high level process that I will need to implement.
Any help will be greatly appreciated.
Start by setting up a normalized table structure. make the tables below and hook them together with the relationships tool on the ribbon under database tools-relationships. To create a relationship drag the primary key from one table to the corresponding and same named foreign key in the table that will be the many side of the relationship. In the pop up make sure to check the enforce referential integrity, cascade update, and cascade delete checkboxes.
I've highlighted the two "Many to Many Relationships" in this normalization. HerdsPaddocks is a more generic name for the CattleMovements Table. There are other possible normalized table structures, but the subtle differences are beyond the scope of this answer. When you are ready, look up table normalization and Many to Many Relationships.
Next, Close the Relationships tool, select a table from the sidebar and on the ribbon under Create hit create form. Do this for all the tables. Now we have a working database but you need to learn how to use it. So play!
Below I gave some play suggestions, but just play with everything until you figure out how to use the forms to (add, search, edit) cows, herds, and paddocks. Also learn why you should delete the primary keys from all the forms and how to replace the foreign keys like CattleType in the Cattle Table with the user friendly CattleTypeDesscription from the CattleTypes Table.
Start with the Herds table and enter some random herds. (pro tip: never add data to the table directly except when playing the error rate is too high). Then Open the herds form where you can browse and edit the herds.
Play tips: In the Table Herds HerdID is both an autonumber and a primary key. It behaves differently from the other columns. Check it out. after that delete the HerdID textbox from the form and see what happens(a good thing). How do you add herds using the Herds Form?
Moving on to playing with the Cattle form, first make sure to add a few CattleTypes to the CattleTypes table. Then at some point, try replacing the CattleTypeID in the Cattle Form with the CattleTypeDescription: https://btabdevelopment.com/how-to-change-a-text-box-to-a-combo-box-wont-work-with-data-access-page/
Also, note the Cattle Form has a subform allowing you to simultaneously assign/edit cattle assignments to herds.
Once you are comfortable adding and editing data, play with the query editor. For instance, to get how many cows are currently in Paddock holds10cattle (my dummy data).
query 1 showing the relevant data
results from query1:
Query 2 getting really close:
Query 3: which gives the number 2:
'Query 3 SQL from SQL pane of query designer
SELECT Count(Cattle.CowName) AS CountOfCowName
FROM Paddocks INNER JOIN ((Herds INNER JOIN (Cattle INNER JOIN CattleHerds ON Cattle.CattleID = CattleHerds.CattleID) ON Herds.HerdID = CattleHerds.HerdID) INNER JOIN HerdsPaddocks ON Herds.HerdID = HerdsPaddocks.HerdID) ON Paddocks.PaddockID = HerdsPaddocks.PaddockID
GROUP BY Herds.HerdName, Paddocks.PaddockName, HerdsPaddocks.HerdPaddockEndDate, HerdsPaddocks.HerdPaddockStartDate
HAVING (((Paddocks.PaddockName)="holds10cattle") AND ((HerdsPaddocks.HerdPaddockEndDate) Is Null) AND ((HerdsPaddocks.HerdPaddockStartDate)<Now()))
ORDER BY HerdsPaddocks.HerdPaddockStartDate;
Next Steps could include the specific paddock with a parameter and using the query in a report.
In SQL Server 2014, I'm trying to add CASCADE DELETING on 3 FK. If I add a Cascade Delete in one relationship, it works fine. If I add more Cascade Deletes, it doesn't work (Cycle detected error message).
In the above diagram, you can see the Users table, and a Tasks table ("Tareas" in spanish). So, what I need to acomplish is when the user is deleted, I need to set the marked field in Tasks to NULL.
This is something common in a database, so I thought there is a way to handle this.
In my case, most of my tables have a pair of fields holding the UserId of the user that Created or Modified the record. So, I need to solve this pattern to apply it several places.
CASCADE DELETE means that in your situation if you delete a User, then SQL Server will delete any attached Tasks too. That is, entire rows will be deleted. Apart from issues such as unexpected losses of data, loss of referential integrity or the potential of infinitely recursive deletions, this behaviour is not what you want anyway. You have stated you only want to mark the associate User columns in your Tasks table to null.
As a suggestion, have you considered implementing a TRIGGER? Something like this (haven't tested this, treat it as pseudo-code!):
CREATE TRIGGER DeleteTareasTrigger
ON Users
BEFORE DELETE AS
BEGIN
UPDATE t
SET t.CreadaPor = NULL
FROM DELETED as d
INNER JOIN Tareas t ON d.UserID = t.CreadaPor
UPDATE t
SET t.ModifcadaPor = NULL
FROM DELETED as d
INNER JOIN Tareas t ON d.UserID = t.ModifcadaPor
END
GO
Or as another approach, add a bit field on the User table to indicate whether the person is active/deleted.
I would like to ask if how do you deal with foreign keys?
Do you cascade delete, or just mark it as deleted but its there?
Here is my sample:
Users(table) 1 ------ * Transactions(Table that has userId)
1
|
|
*
Items(table) 1 ------ * TransactionItems(Table That has ItemId)
(this scenario is for sales transactions )
Do if I delete a user that is being used in a transaction all transactions that have that
user id will be deleted and that is not ok of course..
The simple answer might be to not allow the user of the application to delete a user record that is being referenced. So this means that you must not allow cascade delete right?
So, if I use cascade delete on the relationship between Transactions and TransactionItems
would that be okay? Since its not being referenced.
I would add a bit or bool field to the Users and Items tables called "Active." Set it TRUE for all records. When a User or Item needs to be "deleted," set the bit to FALSE. Change all your queries in your application to filter the Users and Items tables WHERE Active = 'TRUE' so that the application only sees "non-deleted" Users or Items.
This will preserve the userId for things such as historical reports (you can join the Transactions table to the Users table and still match on all usersId's in the Transactions table), but allows for a logical "deletion" from the perspective of your application.
The same applies for itemId in the TransactionItems table; your join to the Items table will still match on all itemId's.
For the relationship of Transactions to TransactionItems, since no two Transactions records can relate to the same TransactionItems record, you can set the relationship to Cascade Delete, so that when a record from the Transacations table is deleted, all related TransactionsItems records are deleted, as well. (Any Items related to those deleted TransactionItems would still remain.)
hi all i am getting a problem while i attenpting to delete a row from parent table, actuall i have created 2 table custmer and account.i make cutomer id(primary key) in customer and customer id (forigen key ) in account.after created it i have filled data inside both table.
at this point while i am trying to delete a row from first table (customer ) then it give failure message is that it can't be deleted bcs it is refrenced as forigen key some thing like that............but while we delete row from account table then it's delete sucess fully.
.......i want to function like that if i delete a row from parent table(customer) then its in child table that row which has same customer id (account table) is delete automatically............
watch out on the cascade deletes! a user will accidentally click on the application's little trash can icon and delete the customer, and then all the cascades will remove every trace of that customer, orders, invoices, payments, history, etc from your database. After the user call you to tell you about their little mistake, you'll have to restore a backup and try to pull the info back into the database.
I would look into "soft deletes" where you only change the customer's status from "active" to "inactive". the rows is not deleted, preserving all foreign key data. This allows reports to run on the data, because it still exists, as well as easy an "undo".
Soft deletes are not the end all only way to go, it is a business decision on how to handle this, purge the data or mark it inactive. That is only something you can decide, because I don't know your application or business logic. I just thought that I would offer it as an alternative.
You need to set up the foreign key with on delete cascade to achieve this.
For SQL Server 2008 see the article Cascading Referential Integrity Constraints
Edit Just to add a somewhat redundant health warning you should be aware that adding on delete cascade will mean that when you delete the row from the parent table associated rows from the child table will be deleted. However as this is exactly the behaviour you state that you want I can't see that would be an issue.
I am using SQlServer 2008, and an extract of some datatables is displayed below:
Users
Id (PK)
UserItems
UserId (PK)
ItemId (PK) - (Compound key of 2 columns)
...
UserItemVotes
UserId (PK)
ItemId (PK)
VoterId (PK) - (Compound key of 3 columns)
I have the following relationships defined:
User.Id -> UserItems.UserId
(UserItems.UserId, UserItems.ItemId) -> (UserItemVotes.UserId, UserItemVotes.ItemId)
UserId.Id -> UserItemVotes.VoterId
Now, I am having a problem when turning on cascading deletes. When adding the 3rd relationship I receive the error "...may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints."
I do not really want to do this, ideally if a user is deleted I would like to remove their useritem and/or their votes.
Is this a bad design? Or is there a way to get behaviour I want from SQL Server?
The approved answer is not a good answer. The scenario described is not bad design, nor is it "risky" to rely on the database to do its job.
The original question describes a perfectly valid scenario, and the design is well thought-out. Clearly, deleting a user should delete both the user's items (and any votes on them), and delete the user's votes on any item (even items belonging to other users). It is reasonable to ask the database to perform this cascading delete when the user record is deleted.
The problem is that SQL Server can't handle it. Its implementation of cascading deletes is deficient.
"UserItems.ItemId -> UserItemVotes.UserId"
This one seems extremely suspect.
I would lead toward bad design. While most DBMSs can manage cascading deletes, it is risky to use this built in functionality. Your scenario is a perfect example of why these types of things are often managed in application code. There you can determine exactly what needs to be deleted and in what order.