-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Severing required navigation deletes the dependent entity #33703
Comments
I have noticed that if you don't include the Type entity in the query under the comment // Map to entity (done at server side)
dbContext.ChangeTracker.Clear(); // Clearing to mimic a new API request scope
var existingTestCase = dbContext.TestCases
.Include(x => x.TestStages)
.ThenInclude(x => x.TestSteps)
.ThenInclude(x => x.Children)
//.ThenInclude(x => x.Type) <---- Works as expected after commenting this out!
.First(); |
The
After the
This is detected by EF Core as severing a navigation property--since EF saw it as first non-null, and it was then changed to null. If the |
That sounds wrong to me, considering the step is still reachable by being linked to TestStage 1 it should not be considered as being Deleted, and there can be many business reasons to not map the navigation entity(it may not exist in the DTO) while it being included in the original query, but we still have the FK mapped which should be enough. Unless I'm missing something? |
@soloham If you never change the navigation property value, then EF will use only the FK value. But if the navigation is changed, then EF will reflect that change. It can be argued both ways whether this is "correct" but at this point the behavior is not going to change anyway, since it would be a big breaking change. |
@ajcvickers I would like to understand the argument for the current behaviour, how would it ever be valid to mark an entity as Deleted if a linked navigation property is removed while keeping the FK as is? There are very real scenarios as I have shared in the issue where a previously loaded entity with navigations included is mapped onto by a DTO that doesn't include that navigation(very valid/common for DTOs) I understand the point around this being a massive breaking change but could it not be and would request that it be put behind a feature flag similar to let's say |
EF supports changing the navigation properties as a way to change relationships. If the relationship is required, and the FK property is non-nullable, then severing a relationship results in that dependent becoming deleted. For example: using (var context = new SomeDbContext())
{
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();
context.Add(new TestStep { Type = new StepType { Name = "S1" } });
await context.SaveChangesAsync();
}
using (var context = new SomeDbContext())
{
var step = await context.TestSteps.Include(e => e.Type).SingleAsync();
step.Type = null; // Sever the relationship to the Type.
await context.SaveChangesAsync(); // TestStep cannot exist without a StepType, so it is deleted.
}
public class SomeDbContext : DbContext
{
public DbSet<TestStep> TestSteps => Set<TestStep>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
.UseSqlServer("Data Source=localhost;Database=BuildBlogs;Integrated Security=True;Trust Server Certificate=True;ConnectRetryCount=0")
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
}
public class TestStep
{
public int Id { get; set; }
public int TypeId { get; set; }
public StepType Type { get; set; }
}
public class StepType
{
public int Id { get; set; }
public string Name { get; set; }
} |
@ajcvickers Thanks for that. I still don't get the use case for this behaviour. Not sure on what basis reacting to a severance of an undeleted required navigation entity by deleting the dependant would make sense, If however, the navigation entity was deleted thus severing the relationship with the dependant it would make sense for that to cascade to delete the dependent entity. But if EF knows the navigation entity is undeleted, the dependant is still reachable in the data model, and that it has an FK to the navigation entity it should consider that as Unchanged, or if it has to react to the severance, the more cautious approach would be to throw an exception demanding the required navigation as opposed to assuming on behalf of the user to delete the dependent entity which can lead to unintentional data loss as it has for me in production here. I really feel this behaviour needs re-considering, and as a minimum, there needs to be a flag to allow the user to control what EF does in such a scenario. Anyhow, with the knowledge of what's going on, I have worked around this for my use case. If you decide in favour of making this change, I would be interested in contributing to this change, and would be great to get a headstart on where to begin in the codebase. Thanks for looking into this! |
The behavior of EF is identical if the relationship is severed from one side or the other, or using the foreign key. |
@ajcvickers For the record, I don't think this issue has been given the attention it deserves - there are still unanswered questions from my comments above which clearly outline real-world data loss risks of the current behaviour. At the very least I expected a justification for the current risky behaviour but sounds like we're saying that's just how it is. |
@soloham You have a different opinion about how EF Core should handle modification of relationships. I have discussed this kind of thing with people for 16 years now. There are lots of internally consistent mental models that could be used to reason about what should happen when a relationship is severed. EF has one that was chosen more than 16 years ago (we didn't change it in EF Core), and even if you come up with something that it super compelling, we can't change it now without it being a breaking change. What's more it would be a breaking change that introduced multiple models of how change tracking works, and that's not a good way to go with EF. |
@ajcvickers I get that and totally respect there may be different justifiable opinions on how EF should do certain things considering the complex use case it is addressing and I always have and will continue to use EF in my projects due to the value it adds. However, what I wanted to understand here was the reasoning/justification for the opinion and decision driving the current behaviour and understand how that makes sense in the real world, surely this analysis was done? I haven't seen that and/or any example to suggest why the current behaviour is useful enough to ignore the major risks I have encountered and shared here. What it feels I got was this is how EF works, and it can't change now. I hope it's ok to challenge these opinions as old as they may be especially if it turns out they have major risks associated with them, breaking change or not. At the core of what I don't understand is how severing a required navigation entity necessitates the deletion of the dependent given EF knows that the navigation and the dependent entities are not deleted? and how that's better than throwing an FK constraint violation exception?
We already have a lot of precedence for configuring how and what the change tracker does in various scenarios, how would this be any different? and in my opinion, is a major selling point of EF |
@soloham As I understand it, the "major risks" are that an entity gets put in a Deleted state when you don't think it should. EF does this if a navigation representing a required relationship is severed by setting a navigation to null, removing an entity from a navigation collection, or setting the FK value to null. This is a fundamental behavior in EF's change tracking. The idea that changing a relationship is the same depending on what side it is done from, or if it is done with an FK instead of a navigation, is not going to change. |
Correct, I'm considering data loss on production a major risk.
I'm not suggesting the behaviour of severing an undeleted required relationship be different depending on how you do it, I'm suggesting what it is currently needs re-considering regardless of where you do it from and that if a required relationship is severed without deleting the navigation entity(thereby not invoking cascade delete on the dependant) and dependant entity is saved, it should be an FK constraint violation(what SQL Server would do) exception as a minimum as opposed to deleting that dependant entity.
My question still stands as to why this approach, knowing that it has the risk outlined above, has been adopted(regardless of where from the severance happens)? |
EF Core deletes orphaned dependents of required relationships that are identifying or configured for cascade delete. If you don't think that EF should put entities in the Deleted state by convention when they cannot exist anymore, then I would suggest that you don't use EF Core. This is one of the fundamental built in behaviors. Why does EF Core do this? Because it's by far the most common behavior people want for this scenario. EF6 did not do this, and it was a major pain point having to write code to manually do it. If you configure the relationship to not cascade delete, then EF won't do this. We don't currently support configuring cascade delete and not configuring deletion of orphans, or vice versa, but that is tracked by #10066. |
@ajcvickers Reading up on the issue you have linked, I can now see the argument for the current behaviour i.e when the child is being removed from the navigation collection of the parent explicitly -but since EF treats and identifies
Well, you say that but the linked issue proves that EF intentionally does not always put entities that can't exist anymore in a deleted state based on configuration and can throw a very helpful exception to explain why:
Which is exactly what I'm asking for here, in my scenario where I'm using automapper it doesn't make sense that EF delete orphaned entities, for others it may be the opposite and I think once you separate out orphan deletion from parent cascade behaviour and have a dedicate configurable orphan deletion behaviour, you will have resolved this issue as well and I would expect an exception like the following:
Considering all this, I think this issue is still valid and should be closed only when #10066 is resolved or this can be consolidated into that issue. |
If you want to treat this as a duplicate of #10066, that's fine by me. I don't see anything additional that this issue adds to that. |
@ajcvickers The important difference is that this is the inverse of what's being asked for by the OP in that issue, so as long as the output of that results in an orphan delete configuration option that also caters for this scenario, I'm also fine by this being considered a "duplicate" of that. |
To be clear regarding the difference... From #10066,
The request as I understand it is to allow deleting orphans regardless of cascade delete behaviour, what this adds to that is for the current behaviour to still exist but only move that away from being dependant on cascade delete configuration to a new orphan delete configuration option. Both issues are configuring the same thing i.e orphan deletion but in very different ways thus making this not simply a duplicate unless this configuration option is added/tracked in that issue as well. |
Steps to reproduce
Getting the scenario from my project codebase into a stripped-down version to demonstrate the bug was tricky. Still, I have successfully managed to do so and the below snippet can be tested to see that TestStep with Id 2 when reparented from TestStep 1 to Test Stage 1, is being marked as deleted by EF.
Output
Expected
Include provider and version information
EF Core version: 8.0.4
AutoMapper version: 13.0.1
AutoMapper.Collections version: 10.0.0
AutoMapper.Collections.EntityFrameworkCore version: 10.0.0
Target framework: .NET 8.0
Operating system: Win 11
IDE: Visual Studio 2022
The text was updated successfully, but these errors were encountered: