Skip to content

Commit

Permalink
Update DeleteBehavior to allow client-only cascades
Browse files Browse the repository at this point in the history
Issue #12661
  • Loading branch information
ajcvickers committed Apr 10, 2019
1 parent 948e683 commit 4b9f3b3
Show file tree
Hide file tree
Showing 13 changed files with 551 additions and 1,195 deletions.
19 changes: 14 additions & 5 deletions src/EFCore.Relational/Migrations/Internal/MigrationsModelDiffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1965,11 +1965,20 @@ private ValueConverter GetValueConverter(IProperty property)
=> TypeMappingSource.GetMapping(property).Converter;

private static ReferentialAction ToReferentialAction(DeleteBehavior deleteBehavior)
=> deleteBehavior == DeleteBehavior.Cascade
? ReferentialAction.Cascade
: deleteBehavior == DeleteBehavior.SetNull
? ReferentialAction.SetNull
: ReferentialAction.Restrict;
{
switch (deleteBehavior)
{
case DeleteBehavior.SetNull:
return ReferentialAction.SetNull;
case DeleteBehavior.Cascade:
return ReferentialAction.Cascade;
case DeleteBehavior.NoAction:
case DeleteBehavior.ClientNoAction:
return ReferentialAction.NoAction;
default:
return ReferentialAction.Restrict;
}
}

private static object[,] ToMultidimensionalArray(IReadOnlyList<object> values)
{
Expand Down
9 changes: 6 additions & 3 deletions src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -975,7 +975,8 @@ private void SetProperty(
&& valueType == CurrentValueType.Normal
&& (!asProperty.ClrType.IsNullableType()
|| asProperty.GetContainingForeignKeys().Any(
fk => (fk.DeleteBehavior == DeleteBehavior.Cascade)
fk => (fk.DeleteBehavior == DeleteBehavior.Cascade
|| fk.DeleteBehavior == DeleteBehavior.ClientCascade)
&& fk.DeclaringEntityType.IsAssignableFrom(EntityType))))
{
if (value == null)
Expand Down Expand Up @@ -1177,7 +1178,8 @@ public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled, bool for
if (_stateData.IsPropertyFlagged(property.GetIndex(), PropertyFlag.Null))
{
if (properties.Any(p => p.IsNullable)
&& foreignKey.DeleteBehavior != DeleteBehavior.Cascade)
&& foreignKey.DeleteBehavior != DeleteBehavior.Cascade
&& foreignKey.DeleteBehavior != DeleteBehavior.ClientCascade)
{
foreach (var toNull in properties)
{
Expand All @@ -1202,7 +1204,8 @@ public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled, bool for
}
}

var cascadeFk = fks.FirstOrDefault(fk => fk.DeleteBehavior == DeleteBehavior.Cascade);
var cascadeFk = fks.FirstOrDefault(fk => fk.DeleteBehavior == DeleteBehavior.Cascade
|| fk.DeleteBehavior == DeleteBehavior.ClientCascade);
if (cascadeFk != null
&& (force
|| (!isCascadeDelete
Expand Down
5 changes: 3 additions & 2 deletions src/EFCore/ChangeTracking/Internal/StateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -910,11 +910,12 @@ public virtual void CascadeDelete(InternalEntityEntry entry, bool force)
{
if (dependent.EntityState != EntityState.Deleted
&& dependent.EntityState != EntityState.Detached
&& fk.DeleteBehavior != DeleteBehavior.Restrict
&& fk.DeleteBehavior != DeleteBehavior.ClientNoAction
&& (dependent.EntityState == EntityState.Added
|| KeysEqual(entry, fk, dependent)))
{
if (fk.DeleteBehavior == DeleteBehavior.Cascade
if ((fk.DeleteBehavior == DeleteBehavior.Cascade
|| fk.DeleteBehavior == DeleteBehavior.ClientCascade)
&& doCascadeDelete)
{
var cascadeState = dependent.EntityState == EntityState.Added
Expand Down
89 changes: 70 additions & 19 deletions src/EFCore/DeleteBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,22 @@ namespace Microsoft.EntityFrameworkCore
/// <para>
/// Note that the in-memory behavior for entities that are currently tracked by
/// the <see cref="DbContext" /> can be different from the behavior that happens in the database.
/// See the <see cref="ClientSetNull" /> behavior for more details.
/// </para>
/// </summary>
public enum DeleteBehavior
{
/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the values of foreign key properties in
/// dependent entities are set to null. This helps keep the graph of entities in a consistent
/// state while they are being tracked, such that a fully consistent graph can then be written to
/// the database. If a property cannot be set to null because it is not a nullable type,
/// then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// This is the same as the <see cref="SetNull" /> behavior.
/// dependent entities are set to null when the related principal is deleted.
/// This helps keep the graph of entities in a consistent state while they are being tracked, such that a
/// fully consistent graph can then be written to the database. If a property cannot be set to null because
/// it is not a nullable type, then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database
/// is to generate an error if a foreign key constraint is violated.
/// This is the same as the <see cref="Restrict" /> behavior.
/// </para>
/// <para>
/// This is the default for optional relationships. That is, for relationships that have
Expand All @@ -48,9 +45,10 @@ public enum DeleteBehavior
/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the values of foreign key properties in
/// dependent entities are not changed. This can result in an inconsistent graph of entities
/// where the values of foreign key properties do not match the relationships in the
/// graph.
/// dependent entities are set to null when the related principal is deleted.
/// This helps keep the graph of entities in a consistent state while they are being tracked, such that a
/// fully consistent graph can then be written to the database. If a property cannot be set to null because
/// it is not a nullable type, then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
Expand All @@ -63,36 +61,89 @@ public enum DeleteBehavior
/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the values of foreign key properties in
/// dependent entities are set to null. This helps keep the graph of entities in a consistent
/// state while they are being tracked, such that a fully consistent graph can then be written to
/// the database. If a property cannot be set to null because it is not a nullable type,
/// then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// dependent entities are set to null when the related principal is deleted.
/// This helps keep the graph of entities in a consistent state while they are being tracked, such that a
/// fully consistent graph can then be written to the database. If a property cannot be set to null because
/// it is not a nullable type, then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database is
/// the same as is described above for tracked entities. Keep in mind that some databases cannot easily
/// support this behavior, especially if there are cycles in relationships.
/// support this behavior, especially if there are cycles in relationships, in which case it may
/// be better to use <see cref="ClientSetNull"/> which will allow EF to cascade null values
/// on loaded entities even if the database does not support this.
/// </para>
/// </summary>
SetNull,

/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the dependent entities
/// will also be deleted when <see cref="DbContext.SaveChanges()" /> is called.
/// For entities being tracked by the <see cref="DbContext" />, dependent entities
/// will deleted when the related principal is deleted.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database is
/// the same as is described above for tracked entities. Keep in mind that some databases cannot easily
/// support this behavior, especially if there are cycles in relationships.
/// support this behavior, especially if there are cycles in relationships, in which case it may
/// be better to use <see cref="ClientCascade"/> which will allow EF to perform cascade deletes
/// on loaded entities even if the database does not support this.
/// </para>
/// <para>
/// This is the default for required relationships. That is, for relationships that have
/// non-nullable foreign keys.
/// </para>
/// </summary>
Cascade
Cascade,

/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, dependent entities
/// will deleted when the related principal is deleted.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database
/// is to generate an error if a foreign key constraint is violated.
/// </para>
/// </summary>
ClientCascade,

/// <summary>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the values of foreign key properties in
/// dependent entities are set to null when the related principal is deleted.
/// This helps keep the graph of entities in a consistent state while they are being tracked, such that a
/// fully consistent graph can then be written to the database. If a property cannot be set to null because
/// it is not a nullable type, then an exception will be thrown when <see cref="DbContext.SaveChanges()" /> is called.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database
/// is to generate an error if a foreign key constraint is violated.
/// </para>
/// </summary>
NoAction,


/// <summary>
/// <para>
/// Note: it is unusual to use this value. Consider using <see cref="ClientSetNull"/> instead to match
/// the behavior of EF6 with cascading deletes disabled.
/// </para>
/// <para>
/// For entities being tracked by the <see cref="DbContext" />, the values of foreign key properties in
/// dependent entities are not changed when the related principal entity is deleted.
/// This can result in an inconsistent graph of entities where the values of foreign key properties do
/// not match the relationships in the graph.
/// </para>
/// <para>
/// If the database has been created from the model using Entity Framework Migrations or the
/// <see cref="DatabaseFacade.EnsureCreated" /> method, then the behavior in the database
/// is to generate an error if a foreign key constraint is violated.
/// </para>
/// </summary>
ClientNoAction
}
}
Loading

0 comments on commit 4b9f3b3

Please sign in to comment.