-
Notifications
You must be signed in to change notification settings - Fork 3.2k
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
Implement RejectChanges() in DbContext #14594
Comments
I think this may be possible if there exists a state(backup) of database before and after each call for SaveChanges, and RejectChanges(state) return the database to the marked state. |
The recommended approach is to use a single DbContext instance for each unit-of-work, disposing it when done. If you need to discard all changes in a unit-of-work, then do so by disposing the context without saving. |
A use case I ran into:
My options are:
Issue with 1) is that EF Core doesn't have a recommended way to even do this: #10694 (comment), so it's both a recommended way to accomplish a task, while being unsupported. I agree 2) is kind of gross, without the context being designed for that (at least that's what I got from this issue), it may be doing some awful things we don't want it to. If it was a first-class supported flow this is would be super easy.
Maybe I'm just missing something... but it seems like all options have pretty poor downsides of either support or code maintainability, so I think some more supported solution to this should be considered a bit more. |
+1. Not all DbContexts are made equal and some are long-lived objects that can't be disposed of very easily. Some are singletons, even, and they can't be disposed of without refactoring them into not being singletons in the first place. Replacing them with factories and providers sure sounds like a pain - one that should be mitigated by the framework in this case. Another option would be to let each DbContext expose a method through which you can create a scoped sub-context, integrating the factory pattern seamlessly. It's also worth mentioning that ScopedTransactions don't work at all here, despite old StackOverflow threads suggesting otherwise, and that manually iterating the ChangeTracker's entries sounds pretty risky, if not plain wrong. This thread in particular is a great example of how confusing this all is - no two answers are the same! |
I have to agree with G3 and other requests here. Because the UoW pattern crosses knowledge boundaries, we can't really ensure we don't save at the end of our scoped operation without a UoW-bound flag. Otherwise, we have to ensure our exceptions surface all the way up, beyond the last |
Also, FYI, I see that RejectChanges() could easily lead to misunderstandings about the state of the context. Can we then apply more changes after rejection? etc. My prior comment about boundaries still applies, and I'm not sure what the solution is. |
As a possible work-around I would like to mention this library: EFCore.DbContextFactory. |
With Blazor Server, I have a common flow like this:
In case of any exception during step 3, I really want to roll back all modified entities to the state as they were initially. Currently, I am using the trick from https://stackoverflow.com/a/22098063/685808, but it feels hackish. With the rise of Blazor's popularity, that kind of workflow will happen more often. It makes sense to either provide some support for it in EF Core or suggest some workarounds in the documentation. |
Does |
What if my first call to SaveChanges failed, then I wanted to keep using the same context to do some other work, such as logging to database? In this case, my second call to SaveChanges, which is supposed to do ONLY logging would fail, because it would include all the previous changes that have failed the first call. The whole thing is indeed a unit of work. |
@nicholasyin : In that specific case I recommend using a separate context for logging. which probably has other benefits in your app as well. |
Logging is just an example. The point is to have a unified calling style to save changes. So it should accommodate for both normal and exceptional scenarios. Therefore, using a separate context would require me to have a second context anyway, even for normal cases. |
I hear you @nicholasyin . I appreciate your desire to keep the "number of contexts" down, but you really probably DO want two contexts for that sort of operation. You want to be able to complete logging operations independent of business transactions. Can you give a different example where that wouldn't be the case? |
logging operations should probably be fire-and-forget, not dependent on saving business data... and the logger definitely shouldn't have access to the context where business work is being done. Not that you would do so, but I've seen a developer call .SaveChanges on a logger with shared context before. Not pretty. |
@syndicatedshannon Thanks for the reply. As I mentioned, logging is simple an example and I'm aware of the proper way of implementing logging. After all, the original intention is about the necessity of a RejectChanges() method or some mechanism to reset the context to a 'clean' state. Retrieving a new context instance would probably suffice and function the same as resetting an existing context to the clean state. |
This really should exist, and the semantics of This is needed when building data-entry-style user interfaces (in Desktop and Blazor apps): The user checks a checkbox, leading to a save operation. If that fails, the change needs to be reverted, so the user interface isn't incorrectly displaying the change after that. The knowledge of what the original state was is known to EF, so EF should cover that job. The alternative would be to create a new context, attach a clone of the object as modified and only on success modify the original object for the user interface. This is cumbersome in this simple example, it gets absurd if we're talking about more complex scenarios like additions and removals. The missing reload functionality is related to this. |
For those souls finding this ticket, here's an implementation that will work for EF7 and later: static void RevertChangesEf7(this DbContext context)
{
foreach (var entry in context.ChangeTracker.Entries().ToArray())
{
switch (entry.State)
{
case EntityState.Added:
entry.State = EntityState.Detached;
break;
case EntityState.Deleted:
case EntityState.Modified:
entry.State = EntityState.Modified;
entry.State = EntityState.Unchanged;
break;
default:
break;
}
}
} This will, on EF7, revert all properties of all entities, including the navigation properties. This code was given on StackOverflow a long time ago, but it only started working properly very recently, because earlier versions of EF did no fix the navigation properties on resetting the entity states - although it did change them in the first place when modifying non-navigational properties. I implemented a more complex version of this for EF Core 6 (before I realized it was a trivial problem on EF7) which I have put into my Reconciler library so that the tests can live somewhere. |
@jtheisen : This workaround doesn't restore navigation properties if you use Lazy Loading with Microsoft.EntityFrameworkCore.Proxies or Microsoft.EntityFrameworkCore.Infrastructure.Internal.LazyLoader. @ajcvickers : |
Revert deleted objects. Navigation-Properties are not restored due to dotnet/efcore#14594
Revert deleted objects. Navigation-Properties are not restored due to dotnet/efcore#14594
Story
There are many user requests for a function in DbContext class to reject or discard changes. See as well:
Microsoft sample code: How to undo the changes in Entity Framework 4.1 and later
I first implemented a wrong version which I found online which caused terrible data loss. I was able to fix it with a similar solution as the Microsoft sample code is, see above.
Request
I would appreciate if a function
DbContext.RejectChanges()
or a similar one would be implemented in the official version of Entity Framework Core.Further technical details
EF Core version: 2.1.4
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows
IDE: Visual Studio 2017 15.8.6
The text was updated successfully, but these errors were encountered: