dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.63k stars 3.15k forks source link

EF adds object instance to navigation property when object instance is deleted #33914

Open sam-wheat opened 3 months ago

sam-wheat commented 3 months ago
// In my code I am passing a user object to a method.  The object has been edited and is in a state that
// represents how it is supposted to saved to disk.  In this example the user has zero UserDataRoles defined
// that means that if any UserDataRoles exist on disk they should be deleted.
// However for some reason EF adds the UserDataRoles to the user object when the role 
// object State is set to deleted.

User user = db.Users.First(); 
db.Entry(user).State = EntityState.Modified;
db.SaveChanges(); 
Debug.Assert(user.UserDataRoles.Count == 0); // Passes
UserDataRole role = db.UserDataRoles.Where(x => x.UserId == user.UserId).First();
Debug.Assert(user.UserDataRoles.Count == 0); // Passes
db.Entry(role).State = EntityState.Deleted;  // EF adds role to user.UserDataRoles here
Debug.Assert(user.UserDataRoles.Count == 0); // Fails. Why is role added to user.UserDataRoles?

Versions: net8 EF Core 8.0.6

EFProblem.zip

ajcvickers commented 2 months ago

@sam-wheat The repro code seems to be buggy:

Unhandled exception. System.InvalidOperationException: Sequence contains no elements
   at System.Linq.ThrowHelper.ThrowNoElementsException()
   at lambda_method9(Closure, QueryContext)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at EFProblem.Program.Main(String[] args) in D:\code\repros\EFProblem\EFProblem\EFProblem\Program.cs:line 25
sam-wheat commented 2 months ago

Sorry about that. I am unable to create databases on client's server so I could not run that code. Fixed here:
EFProblem.zip

ajcvickers commented 2 months ago

Note for triage: based on this, https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-7.0/breaking-changes?tabs=v7#deleted-fixup, I think we maybe should not be doing fixup here, but this would be a breaking change.

leaderanalytics commented 2 months ago

@ajcvickers Based on the link you posted, if I was setting the state to modified, the code would be working correctly?

db.Entry(role).State = EntityState.Modified;  // EF adds role to user.UserDataRoles here

Honestly this seems like an absolute minefield. Is there a way to opt out of this? I my case I am passing an entity to a method that has been modified by the user. If the navigation property has no child entities it is because the user has removed them and the user intends for them to be deleted from disk. If EF is changing the entity behind my back I have to implement additional code that has no purpose other than to undo what I don't want EF to do in the first place.

The way EF is (was?) working was just fine - if I want child entities on the navigation property I will use .Include in my query.

ajcvickers commented 2 months ago

The way EF is (was?) working was just fine

Can you explain what you mean by this? Which behavior has changed, and from which version?

leaderanalytics commented 2 months ago

I do not want to be critical but fix-up behavior seems like a minefield of surprises for the developer. How can EF know the developer's intent? In my case my intent is to have the navigation property set to empty. When EF changed it, it broke my code.

ajcvickers commented 2 months ago

@leaderanalytics I tried your code on EF Core 3.1, 5. 6. 7, and 8, and it fails the same way on all versions. If this is a regression, it would be great to know that.

leaderanalytics commented 2 months ago

IDK if it is a regression. As a rule I try to perform as few operations as possible with each instance of dbcontext to try to avoid problems such as this.