DeleteBehavior.Restrict Consistency Question
See original GitHub issueRecently 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:
- Created 2 years ago
- Comments:5 (3 by maintainers)
@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
orNoAction
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.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.