Correct way to disable Cascade Delete

Hi,

I've been building my first few Radzen LOB apps and have encountered the bizarre choice for the default Cascade Delete behaviour in EF Core. I've read through the various posts on how to disable it but find that the Radzen app's behaviour produces unexpected behaviour with some settings. I'd like to highlight the issues with some of those settings and get feedback, as well as confirm whether the workaround is actually the correct way to do this in Radzen.

Information

  • Windows 10
  • Radzen version 2.50.14
  • EF Core version 3.1.4

App Setup

  1. Set up a SQL DB with two basic entities, a Product and a Subscription. A Subscription is required to have a product through a Foreign Key constraint and is marked as not null.
  2. Set up a basic Radzen Blazor App with "Enable cascade delete" unchecked during the data import.
  3. Create a .partial.cs file for the DataContext and specify the OnModelBuilding method to change the delete behaviour to Restrict, since this seems to be the appropriate one:
partial void OnModelBuilding(ModelBuilder builder)
{
	var cascadeFKs = builder.Model.GetEntityTypes()
		.SelectMany(t => t.GetForeignKeys())
		.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);

	foreach (var fk in cascadeFKs)
		fk.DeleteBehavior = DeleteBehavior.Restrict;
}
  1. Run the app and create a product, and then create a subscription associated with that product.

Steps to reproduce issues:

  1. Run the app from a fresh start. The existing products are listed.
  2. Create a new product. The product is created, as expected.
  3. List the subscriptions.
  4. Return to the products list and attempt to delete the associated product. As expected, the delete fails with the error "Unable to delete Product".
  5. Attempt to create a new Product, this unexpectedly fails with the error "Unable to create new Product".

Expected Behaviour
The product creation in step 4 is expected to succeed, regardless of whether or not you have browsed to the subscriptions.

Workaround
Change the delete behaviour to DeleteBehavior.ClientNoAction

Details

Issue 1:
In the DeleteProduct method, the call to Remove now throws an exception (as opposed to only when SaveChanges is called). This appears to be a breaking change in EF Core 3, so the catch block won't get hit.

Issue 2:
Even if you prevent from Remove from throwing the exception and get SaveChanges to do so, the broken relationship is still in the ChangeTracker after the product is deleted. I'm not even sure how you discard this from the ChangeTracker, or if you're even meant to? It seems like this occurs specifically when you browse to subscriptions because it results in the Subscription being tracked.

Questions

  1. Is the workaround with DeleteBehavior.ClientNoAction the right way to achieve the disabling of Cascade delete for a Radzen app? I ask because the value's documentation says "It is unusual to use this value. Consider using DeleteBehavior.ClientNoAction instead to match the behaviour of EF6 with cascading deletes disabled."
  2. Given the behaviour in Issue 2, it seems like we can't use DeleteBehavior.Restrict or another setting which results in exceptions being thrown. Are we meant to avoid these settings for specific entities?

That's a lot to read, sorry :slight_smile: And thanks for creating a product like this...it makes life the front-end prototyping so much less painful.

1 Like

Hi @BobbyJ,

This is not something specific to Radzen applications but how EF Core will handle cascading delete in general. When Radzen cascade delete is enabled we will generate Include() for every related entity in parent record delete method. For example:

        public async Task<Models.Sample.Order> DeleteOrder(int? id)
        {
            var item = context.Orders
                              .Where(i => i.Id == id)
                              .Include(i => i.OrderDetails)
                              .FirstOrDefault();

            if (item == null)
            {
               throw new Exception("Item no longer available");
            }

            OnOrderDeleted(item);

            context.Orders.Remove(item);

            try
            {
                context.SaveChanges();
            }
            catch (Exception ex)
            {
                context.Entry(item).State = EntityState.Unchanged;
                throw ex;
            }

            return item;
        }

Since Radzen is database first tool (no code first support) we have no control how the database and foreign keys are declared. If they are nullable (optional relationships) the generated Include() will tell EF Core to set null and if the foreign keys are not nullable (required relationships) the generated Include() will tell EF Core to cascade delete. Indeed using OnModelBuilding you can customize the EF behavior however you are still limited to your database/foreign keys configuration.

Hi @enchev,

Thanks for the response. It describes the behaviour when the Radzen cascade delete option is enabled quite nicely, but could you elaborate on the behaviour when it is disabled? In my case this would be when the line .Include(i => i.Subscriptions) is not generated in DeleteProduct.

When I do so and I don't use OnModelBuilding to override the cascade behaviour, deleting a Product still results in the associated Subscription being deleted. Without knowing about the default EF Core cascade behaviour, I would have expected it to error on the attempt to delete the product.

So despite (1) DB config to prevent deletion through a foreign key constraint, and (2) Radzen set to disable cascade delete, the Subscription is still deleted.

So to achieve full disabling of cascade delete for the app, the missing piece seems to be overriding the EF Core DeleteBehavior, which my original post went into.

Hope this highlights my issue more clearly. I'm just looking for guidance on whether DeleteBehavior.ClientNoAction is indeed the correct way to achieve full disabling of cascade delete.

I’m afraid I don’t have any other insight, maybe you should ask on official Microsoft forums.