-
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
Update DeleteBehavior to be more consistent and understandable #12661
Comments
@roji Unfortunately, the DeleteBehavior enum and its actions have become very confusing due to evolution of the simple thing we used to have into a more complex thing which tried not to break the simple thing. This doc is an attempt to explain it, but it's not very good either. In a nutshell, Restrict and ClientSetNull are essentially the same in terms of database behavior, but ClientSetNull retains all the original fixup behavior. Restrict is intended to tell EF to do nothing for you--that is, no fixup. Except we later decided it will do some fixup, but it won't set an FK to null just because a navigation was set to null. And it has bugs as well. I'm going to mark this for re-triage so we can come up with a plan. |
@ajcvickers I suspected that there was a mix of database-side logic and EF Core client-side entity tracking logic... Will look forward to understand better and try to help. As a guiding principle, I think it's important to allow users to set up their database with whatever (supported) referential action they choose, i.e. not have EF Core block anything as it seems to be doing now. Although I do realize that there's important interaction with what happens client-side, and I can see how this whole question isn't trivial at all. |
Consider separating out the client and store parts of this. |
Current behaviorTop-level API calls look like: modelBuilder
.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.OnDelete(DeleteBehavior.Cascade); Values are:
Goals
Proposal AMinor changes; less breaking.
public enum DeleteBehavior
{
ClientSetNull,
[Obsolete] Restrict,
SetNull,
Cascade,
Default,
ClientCascade,
NoFixup = Restrict
} Pros:
Cons:
Propsal BMore breaking; may be cleaner. Introduce: public enum ClientDeleteBehavior
{
Default,
SetNulls,
Cascade
CascadeExcludeOrphans, // Needs a better name!
NoFixup
} public enum StoreDeleteBehavior // Same as ReferentialAction
{
NoAction,
Restrict,
Cascade,
SetNull,
SetDefault
} Obsolete existing APIs and instead allow, for example: modelBuilder
.Entity<Blog>()
.HasMany(e => e.Posts)
.WithOne(e => e.Blog)
.OnDelete(ClientDeleteBehavior.Cascade, StoreDeleteBehavior.Restrict); Pros:
Cons:
|
Notes from the design meeting: We will go with: public enum DeleteBehavior
{
SetNull, // Cascading nulls on client and in database
ClientSetNull, // Cascading nulls on client only (default for optional)
Cascade, // Cascading deletes on client and in database (default for required)
ClientCascade, // Cascading deletes on client only
NoAction, // No action in the database; default behavior on the client
ClientNoAction, // No action on the client and on the database (current Restrict behavior)
Restrict, // No action in the database, default behavior on the client
} Notes:
|
See #10066 for delete orphans configuration. |
@ajcvickers is this a breaking change in preview 4 that still needs to be documented? |
No, it's not merged yet and won't be in preview4. |
We're just upgrading from EF Core 2.1 to 3.1, and this breaking change broke our app. How could you come up with "Restrict" not actually restricting any more? |
The default behavior is |
@markusschaber so it seems that If so, then yes - this is truly terrible design decision... would've made so much more sense to name it |
Here's the code that translates DeleteBehavior to the database action:
|
In npgsql/efcore.pg#508, a user pointed out that Npgsql doesn't scaffold OnDelete actions correctly from existing PostgreSQL databases. Some investigation shows that in RelationalScaffoldingModelFactory, the database model's foreign key's ReferentialAction is translated to a DeleteBehavior, but that translation seems to lose some values: all actions apart from
Cascade
andSetNull
get translated toClientSetNull
. I haven't dived yet to understand exactly what's going on, but this seems to make it impossible to roundtrip a database (scaffold and recreate from model). Can you guys please shed some light on why this is being done?In addition, it seems that the
DeleteBehavior
enum does not have aSetDefault
member, unlike theReferentialAction
enum which does. This makes it impossible to create foreign keys withSET DEFAULT
actions, is this intended?Finally, note that on PostgreSQL
NO ACTION
(the default) andRESTRICT
aren't identical - they differ subtly in thatRESTRICT
isn't deferrable.The text was updated successfully, but these errors were encountered: