-
-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance support for deleting resource with incoming optional 1-to-1 relationship when using DeleteBehavior.ClientSetNull #1118
Comments
Related to #502, which solved the simpler cases. |
One could argue it's better to fail in this case instead of executing unexpected extra queries. I think it matches the intent of the API developer who has set delete behavior to We can always add a configurable threshold to fail instead if this turns out to fetch too much data. Note however that the API consumer executes this call for a reason, so it means then the API will receive preceding requests to set all existing related keys to |
In response to updated issue description: Changed this issue from Leaving this open to possibly address in the future. I've pushed a branch that fixes the simple cases, but it requires a lot more testing and it is a high-risk change. Until then, we should update the documentation to warn about this inconvenient default mapping convention. Even with a fix released, this default is going to result in lots of unneeded extra queries. Perhaps we can point users in the right direction by adding a custom convention (new in EF 7) that overrides the EF Core default convention (unless turned off via a setting). Similarly, we could possibly do that for identifying foreign keys too. |
DESCRIPTION
When trying to delete a resource that has an incoming optional one-to-one relationship, the operation may fail using EF Core default mapping conventions when such a related record exists.
This is because optional relationships in EF Core have the delete behavior
ClientSetNull
by default, instead ofSetNull
. That means that instead of delegating the side effects to the database, EF Core tries to handle it by sending multiple SQL statements. That only works when all related resources of all incoming relationships are loaded in the change tracker upfront (which is expensive and not handled very well by JADNC).The reason for this odd default is poor support in SQL Server, as explained here and here.
As demonstrated in #1205, these restrictions do not apply when using PostgreSQL. Therefore the general advice is to map optional one-to-one relationships with
.OnDelete(DeleteBehavior.SetNull)
. This is simpler and more efficient.STEPS TO REPRODUCE
Consider the next model:
This results in the next SQL to create the database:
Then seed the database with the following data:
Now when trying to delete the order:
It fails with the next exception:
EXPECTED BEHAVIOR
Order 1 is deleted successfully, which results in setting
Customer.FirstOrderId
,Customer.LastOrderId
andShoppingBasket.CurrentOrder
tonull
for all affected records.ACTUAL BEHAVIOR
JsonApiDotNetCore does not load all affected records into the EF Core change tracker upfront so that EF Core would be able to produce the right set of
UPDATE
statements:The current behavior is that JsonApiDotNetCore loops over the JSON:API relationships in Order, which only matches Customer in this case. It then concludes that it does not need to side-load the related customer because the foreign key is configured as
ON DELETE CASCADE
. That logic is insufficient because it fails to take records into account whereCustomer.FirstOrder
,Customer.LastOrder
andShoppingBasket.CurrentOrder
match the ID of the order to delete.To fix this:
Order
is insufficient, because that would not findCustomer.FirstOrder
,Customer.LastOrder
andShoppingBasket.CurrentOrder
. So it needs to scan the full model and find all navigations pointing to an order.ON DELETE
behavior. Note it should not abort on required relationships, because there may not be any matching records so the operation can still succeed.SELECT
query must be produced to obtain the IDs of the related records. In this example, three shopping baskets exist, but only two of them are linked to our order. So it should only fetch the IDs of those two. In a production database, these numbers are usually a lot higher, which makes the distinction important.The text was updated successfully, but these errors were encountered: