Skip to content
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

Detaching All Entities After Saving, Gives "Association Has Been Severed" Error #18406

Closed
jwbats opened this issue Oct 16, 2019 · 13 comments
Closed

Comments

@jwbats
Copy link

jwbats commented Oct 16, 2019

I'm upgrading from .NET Core 2.2 to .NET Core 3.0.

I have no interested in tracking entities. I use AsNoTracking() for every retrieval.

Upon saving, entities are being tracked. I want to detach them all.

This worked in .NET Core 2.2 with EF2.2 It no longer works in .NET Core 3.0 with EF3.0.

Steps to reproduce

Have a simple model where a child entity has a non-nullable FK to the parent entity. The parent entity has a navigation property, which is a list that contains child entities.

Retrieve the parent entity, including the child entities on its navigation property.

public async Task<Account> GetAccount(string email)
{
	return await this.context.Accounts
		.AsNoTracking()
		.Include(x => x.CreditPurchases)
		.FirstOrDefaultAsync();
}

Then call:

public async Task<Account> Update(Account account)
{
	account.ModifiedOn = DateTime.UtcNow;
	EntityEntry<Account> entryAccount = this.context.Accounts.Update(account);
	int count = await this.context.SaveChangesAsync();
	this.context.DetachAll();
	return entryAccount.Entity;
}

DetachAll() implementation:

public void DetachAll()
{
	EntityEntry[] entityEntries = this.ChangeTracker.Entries().ToArray();

	foreach (EntityEntry entityEntry in entityEntries)
	{
		entityEntry.State = EntityState.Detached;
	}
}

This generates an exception that suggests using cascade deletes. I have no desire to delete anything. I merely want to stop tracking everything.

Is there another way of doing this in EF3.0, or am I doing it right and is this really a bug?

In my context's constructor, I even attempt to turn tracking off globally:

this.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

Why does this not stop tracking dead in its tracks?

Exception

InvalidOperationException: The association between entity types 'Account' and 'CreditPurchase' has been severed but the relationship is either marked as 'Required' or is implicitly required because the foreign key is not nullable. If the dependent/child entity should be deleted when a required relationship is severed, then setup the relationship to use cascade deletes. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.

Further technical details

EF Core version: 3.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.0
Operating system: Win10
IDE: Visual Studio 2019 16.5

@jwbats jwbats changed the title Detaching All Entities After Save Gives "Association Has Been Severed" Error Detaching All Entities After Saving, Gives "Association Has Been Severed" Error Oct 16, 2019
@jwbats
Copy link
Author

jwbats commented Oct 16, 2019

I do not build any relationships in my OnModelCreating(). I'm using attributes on my entity properties, to define the relationships, only.

In OnModelCreating(), I call the following method in an attempt to globally restrict DeleteBehavior:

private void SetDeleteBehaviorRestricted(ModelBuilder modelBuilder)
{
	IMutableForeignKey[] mutableForeignKeys = modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()).ToArray();

	foreach (IMutableForeignKey mutableForeignKey in mutableForeignKeys)
	{
		mutableForeignKey.DeleteBehavior = DeleteBehavior.Restrict;
	}
}

This does not stop this exception from being thrown.

@jwbats
Copy link
Author

jwbats commented Oct 16, 2019

Looks like I was able to solve this by making these settings:

	this.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Never;
	this.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never;

This is the suggested fix in the list of breaking changes.

I now no longer get an exception when detaching all entities.

@jwbats
Copy link
Author

jwbats commented Oct 16, 2019

Unfortunately, this has broken something else:

Detaching everything after save, now deletes an entity's navigator properties.

I really don't want to have to re-retrieve stuff after saving.

This isn't in the breaking changes list. Is there any way to mitigate it?

@AndriySvyryd
Copy link
Member

Duplicate of #17784

The workaround is to use

context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges;
context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;

@jwbats
Copy link
Author

jwbats commented Oct 17, 2019

Hi @AndriySvyryd,

I found that one out myself as well. But this does not solve the new problem of EF3 nulling navigational properties when detaching all entities.

I now have to re-retrieve entities for which I want navigational properties after I've saved & detached them.

Can this problem be mitigated as well? Is there already an issue for it, or shall I create one?

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Oct 17, 2019

@jwbats I think that part is by design, @ajcvickers might correct me. You could try to work around it by detaching the accounts first, but in general you would need #9118 for this scenario to work correctly.

@jwbats
Copy link
Author

jwbats commented Oct 17, 2019

@AndriySvyryd EF2.2 simply let the navigational properties sit. I liked it that way. My codebase's architecture kind of depends on it. I have a real usecase where I need this.

I want to be able to retrieve-with-props, update and then serialize-to-client-with-props without the hassle of (1) exceptions related to already-attached entities, (2) disappearing nav props and (3) having to re-retrieve to mitigate this.

I've already tried detaching the entities in different orders. It made no difference. I'd really appreciate it if there were a way to detach after saving without losing the nav props. Or a global setting such as 'DisableTrackingOnSave' would help.

The issue you refer to is still open and doesn't seem to have offered a solid mitigation for this yet. This issue is also not listed as a breaking change, afaik.

I welcome feedback from @ajcvickers in this matter.

@ajcvickers
Copy link
Member

@jwbats A few of things to consider. First, the primary purpose for tracking entities is to allow SaveChanges to send updates. In other words, if you're calling SaveChanges, then tracking is being used for its primary purpose.

Second, the recommended pattern for use of DbContext is to create a new instance for each unit-of-work. In this case, that would mean after you track your entities and call SaveChanges, then just throw your context instance away and create a new one. However, see also #15577.

Third, if there are situations where EF fixup is doing the "wrong thing", then we would appreciate issues being filed for specific cases showing how EF is doing the wrong thing.

@jwbats
Copy link
Author

jwbats commented Oct 18, 2019

@ajcvickers Thanks for the feedback.

First, what you're trying to tell me is that you're supposed to call Update() once on an entity to add it to the context, then SaveChanges() to actually save the changes and also track it... and then you can make more changes to the now-tracked object, and then you only have to call SaveChanges() if you want to save further changes. Do I understand that correctly?

Second, I'm familiar with the recommend unit-of-work pattern for DbContext. I use my DbContexts very shortly. I use dependency injection to inject the context into my generic repository. It's registered as having scope for this request only. I call that a single unit-of-work. In this generic reposity, I have a method that does Update(), SaveChanges(), DetachAll(). This way, I am able to retrieve an entity including properties, UpdateSaveDetach() it, modify it a bit, UpdateSaveDetach() it, then serialize it to the clientside. Except with EF3, the nav props are now stripped from the entity. It seems that @AndriySvyryd agrees this is a regression.

Also, when I drag the debug pointer back, create a new entity with a child prop and UpdateSaveDetach() it, then for some reason the nav prop stays on (read: different behavior when executing the same, deterministic code for a second time). I will soon try to reproduce this and, if it's really EF3's fault and not my own, create an issue out of it.

Third, what do you mean by "EF fixup doing the wrong thing"?

@kswoll
Copy link

kswoll commented Mar 7, 2020

For me, I use this in unit testing with the following pattern:

/// <summary>
/// You should call this in your tests right after Arrange and before Act.  Otherwise,
/// EF will still cache your data so your tests might pass but the code could fail
/// in real life.  (For example, relationships could be non-null in your test but null
/// when in production)
/// </summary>
public static async Task SaveAndDetach(this DbContext db)
{
    await db.SaveChangesAsync();
    foreach (var entry in db.ChangeTracker.Entries().ToArray())
    {
        entry.State = EntityState.Detached;
    }
}

Not sure yet what my workaround should be.

@kswoll
Copy link

kswoll commented Mar 7, 2020

Oh sorry, so far it appears that AndriySvyryd's workaround seems to work.

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
@harounhajem
Copy link

This is a bad idea. Creating and deleting DBContext fills up your memory, with allocated and pinned variables, especially if you are doing concurrent work. It looks like EF Dotnet is broken. Creating an re-creating DBContext will result in the garbage collector kicking in and after a while GC will run every second to deal with that.

@jwbats
Copy link
Author

jwbats commented Apr 11, 2023

I don't believe this issue has anything to do with (re)creating DBContext.

Mine is injected in a class that's added with AddScoped() in startup, meaning I get one instance of it per request.

This is how the official docs recommend it, iirc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants