question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DeleteBehavior.Restrict Consistency Question

See original GitHub issue

Recently I noticed that the outcome when using DeleteBehavior.Restrict on a foreign key was dictated by whether the dependant entity that would be orphaned had been loaded previously in the DbContext or not.

The behavior detailed is here : https://dotnetcoretutorials.com/2021/11/30/the-confusing-behaviour-of-ef-core-ondelete-restrict/

If I have two entities like so :

class BlogPost
{
	public int Id { get; set; }
	public string PostName { get; set; }
	public ICollection<BlogImage> BlogImages { get; set; }
}

class BlogImage
{
	public int Id { get; set; }
	public int? BlogPostId { get; set; }
	public BlogPost? BlogPost { get; set; }
	public string ImageUrl { get; set; }
}

And the FK is configured like so :

modelBuilder.Entity<BlogImage>()
    .HasOne(x => x.BlogPost)
    .WithMany(x => x.BlogImages)
    .OnDelete(DeleteBehavior.Restrict);

If I have code that essentially inserts a record with a child, and then deletes it all within the same context :

var context = new MyContext();
context.Database.Migrate();

var blogPost = new BlogPost
{
	PostName = "Post 1", 
	BlogImages = new List<BlogImage>
	{
		new BlogImage
		{
			ImageUrl = "/foo.png"
		}
	}
};

context.Add(blogPost);
context.SaveChanges();

Console.WriteLine("Blog Post Added");

var getBlogPost = context.Find<BlogPost>(blogPost.Id);
context.Remove(getBlogPost);
context.SaveChanges(); //No error

Console.WriteLine("Blog Post Removed");

There is no error in this scenario because it knows about the BlogImage that has a FK to BlogPost because it was previously loaded in the context. Therefore, when the delete is called it actually sets the BlogPostId on the BlogImage to be null.

However, if I refresh the context like so :

var context = new MyContext();
context.Database.Migrate();

var blogPost = new BlogPost
{
	PostName = "Post 1", 
	BlogImages = new List<BlogImage>
	{
		new BlogImage
		{
			ImageUrl = "/foo.png"
		}
	}
};

context.Add(blogPost);
context.SaveChanges();

Console.WriteLine("Blog Post Added");

context = new MyContext(); // <-- Create a NEW DB context

var getBlogPost = context.Find<BlogPost>(blogPost.Id);
context.Remove(getBlogPost);
context.SaveChanges();

Console.WriteLine("Blog Post Removed");

This will throw an error saying that it would orphan child entities.

So the behavior is consistent in that it always happens (Isn’t completely random), but it’s also totally dependant on whether entities were previously loaded in the context or not. In large scale projects, especially web projects with middleware/filters etc, where the context is generally per request, it would be hard to always be sure the entity has or hasn’t been loaded.

The documentation is however describing the above scenarios correctly, so no issues there :

For entities being tracked by the DbContext, the values of foreign key properties in dependent entities are set to null when the related principal is deleted

So my question is, is this designed well and behaving as people expect? I personally (and from many other stack overflow questions/answers on the subject) would expect that a DeleteBehavior.Restrict always restricts the delete if it was to orphan records. The previous loading of the entity into the context really shouldn’t have any bearing on it.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
ajcvickerscommented, Dec 3, 2021

@mindingdata For both SQL Server and PostgreSQL (and all other relational databases that I know of), creating the a foreign key constraint with “restrict” or “no action” means that the database will take no action when deleting a principal referenced by a foreign key constraint. Contrast this with “cascade delete” where the database will take an action; that is, delete the dependent rows. Since the database takes no action, the constraint is violated, and the database generates an error. That’s what is happening when the exception above is thrown by SqlClient.

Setting Restrict or NoAction in EF Core tells EF Core that the database foreign key constraint is configured this way, and, when using migrations, causes the database foreign key constraint to be created in this way. What it doesn’t do is change the fixup behavior of EF Core; that is, what EF does to keep entities in sync when the graph of tracked entities is changed. This fixup behavior has been the same since legacy EF was released in 2008. For most, it is a major advantage of using an OR/M.

Starting with EF Core, we do allow you to disable this fixup when deleting principal entities by specifying ClientNoAction. The “client” here refers to what EF is doing to tracked entities on the client, as opposed to the behavior of the foreign key constraint in the database. But is is uncommon to do this; most of the time the fixup behavior helps keep changes to entities in sync.

0reactions
mindingdatacommented, Dec 3, 2021

So in MSSQL, setting DeleteBehavior.Restrict does not restrict deletes in any way is essentially what we’re saying?

So correct me if I’m wrong :

On MSSQL. Setting DeleteBehavior.Restrict does not restrict deletions, and it acts like DeleteBehavior.SetNull On PostgresSQL. Setting DeleteBehavior.Restrict does restrict deletions, and it acts like DeleteBehavior.NoAction

Even if this is the behavior working as intended. I’m really really not sure it’s what any user expects. A majority (Although a small majority) of people using EF Core will be using it against MSSQL, and for the “Restrict” behaviour to not restrict anything does not seem intuitive.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What is different between DeleteBehavior.NoAction and ...
My question is what is different between NoAction and Restrict value? I read Microsoft document and descriptions of both of them are same!!!...
Read more >
Update DeleteBehavior to be more consistent and ...
While the pairs of values are not completely consistent, we think they will direct people to choose an appropriate behavior. The behavior of...
Read more >
DeleteBehavior Enum (Microsoft.EntityFrameworkCore)
Indicates how a delete operation is applied to dependent entities in a relationship when the principal is deleted or the relationship is severed....
Read more >
The Confusing Behaviour Of EF Core OnDelete Restrict
I was recently helping another developer understand the various “OnDelete” behaviors of Entity Framework Core. That is, when a parent entity ...
Read more >
Good explanation of cascade (ON DELETE/UPDATE) ...
RESTRICT prevents deletion of a referenced row. NO ACTION means that if any referencing rows still exist when the constraint is checked, an ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found