Add the onDelete convention so that we match the behaviour of sql server when configuring self-referencing skip navigations. Ends up configured as ClientCascade instead of Cascade
parent
4d026e975c
commit
c73f4a3013
@ -0,0 +1,135 @@
|
|||||||
|
// ReSharper disable once CheckNamespace
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
|
||||||
|
|
||||||
|
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A convention that configures the OnDelete behavior for foreign keys on the join entity type for
|
||||||
|
/// self-referencing skip navigations
|
||||||
|
/// </summary>
|
||||||
|
public class JetOnDeleteConvention : CascadeDeleteConvention,
|
||||||
|
ISkipNavigationForeignKeyChangedConvention,
|
||||||
|
IEntityTypeAnnotationChangedConvention
|
||||||
|
{
|
||||||
|
public JetOnDeleteConvention(
|
||||||
|
ProviderConventionSetBuilderDependencies dependencies,
|
||||||
|
RelationalConventionSetBuilderDependencies relationalDependencies)
|
||||||
|
: base(dependencies)
|
||||||
|
{
|
||||||
|
RelationalDependencies = relationalDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Relational provider-specific dependencies for this service.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void ProcessSkipNavigationForeignKeyChanged(
|
||||||
|
IConventionSkipNavigationBuilder skipNavigationBuilder,
|
||||||
|
IConventionForeignKey? foreignKey,
|
||||||
|
IConventionForeignKey? oldForeignKey,
|
||||||
|
IConventionContext<IConventionForeignKey> context)
|
||||||
|
{
|
||||||
|
if (foreignKey is not null && foreignKey.IsInModel)
|
||||||
|
{
|
||||||
|
foreignKey.Builder.OnDelete(GetTargetDeleteBehavior(foreignKey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override DeleteBehavior GetTargetDeleteBehavior(IConventionForeignKey foreignKey)
|
||||||
|
{
|
||||||
|
var deleteBehavior = base.GetTargetDeleteBehavior(foreignKey);
|
||||||
|
if (deleteBehavior != DeleteBehavior.Cascade)
|
||||||
|
{
|
||||||
|
return deleteBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProcessSkipNavigations(foreignKey.GetReferencingSkipNavigations()) ?? deleteBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeleteBehavior? ProcessSkipNavigations(IEnumerable<IConventionSkipNavigation> skipNavigations)
|
||||||
|
{
|
||||||
|
var skipNavigation = skipNavigations
|
||||||
|
.FirstOrDefault(
|
||||||
|
s => s.Inverse != null
|
||||||
|
&& IsMappedToSameTable(s.DeclaringEntityType, s.TargetEntityType));
|
||||||
|
|
||||||
|
if (skipNavigation != null)
|
||||||
|
{
|
||||||
|
var isFirstSkipNavigation = IsFirstSkipNavigation(skipNavigation);
|
||||||
|
if (!isFirstSkipNavigation)
|
||||||
|
{
|
||||||
|
skipNavigation = skipNavigation.Inverse!;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inverseSkipNavigation = skipNavigation.Inverse!;
|
||||||
|
|
||||||
|
var deleteBehavior = DefaultDeleteBehavior(skipNavigation);
|
||||||
|
var inverseDeleteBehavior = DefaultDeleteBehavior(inverseSkipNavigation);
|
||||||
|
|
||||||
|
if (deleteBehavior == DeleteBehavior.Cascade
|
||||||
|
&& inverseDeleteBehavior == DeleteBehavior.Cascade
|
||||||
|
&& !(inverseSkipNavigation.ForeignKey!.GetDeleteBehaviorConfigurationSource() == ConfigurationSource.Explicit
|
||||||
|
&& inverseSkipNavigation.ForeignKey!.DeleteBehavior != DeleteBehavior.Cascade))
|
||||||
|
{
|
||||||
|
deleteBehavior = DeleteBehavior.ClientCascade;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipNavigation.ForeignKey!.Builder.OnDelete(deleteBehavior);
|
||||||
|
inverseSkipNavigation.ForeignKey!.Builder.OnDelete(inverseDeleteBehavior);
|
||||||
|
|
||||||
|
return isFirstSkipNavigation ? deleteBehavior : inverseDeleteBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DeleteBehavior DefaultDeleteBehavior(IConventionSkipNavigation conventionSkipNavigation)
|
||||||
|
=> conventionSkipNavigation.ForeignKey!.IsRequired ? DeleteBehavior.Cascade : DeleteBehavior.ClientSetNull;
|
||||||
|
|
||||||
|
bool IsMappedToSameTable(IConventionEntityType entityType1, IConventionEntityType entityType2)
|
||||||
|
{
|
||||||
|
var tableName1 = entityType1.GetTableName();
|
||||||
|
var tableName2 = entityType2.GetTableName();
|
||||||
|
|
||||||
|
return tableName1 != null
|
||||||
|
&& tableName2 != null
|
||||||
|
&& tableName1 == tableName2
|
||||||
|
&& entityType1.GetSchema() == entityType2.GetSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsFirstSkipNavigation(IConventionSkipNavigation navigation)
|
||||||
|
=> navigation.DeclaringEntityType != navigation.TargetEntityType
|
||||||
|
? string.Compare(navigation.DeclaringEntityType.Name, navigation.TargetEntityType.Name, StringComparison.Ordinal) < 0
|
||||||
|
: string.Compare(navigation.Name, navigation.Inverse!.Name, StringComparison.Ordinal) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual void ProcessEntityTypeAnnotationChanged(
|
||||||
|
IConventionEntityTypeBuilder entityTypeBuilder,
|
||||||
|
string name,
|
||||||
|
IConventionAnnotation? annotation,
|
||||||
|
IConventionAnnotation? oldAnnotation,
|
||||||
|
IConventionContext<IConventionAnnotation> context)
|
||||||
|
{
|
||||||
|
if (name is RelationalAnnotationNames.TableName or RelationalAnnotationNames.Schema)
|
||||||
|
{
|
||||||
|
ProcessSkipNavigations(entityTypeBuilder.Metadata.GetDeclaredSkipNavigations());
|
||||||
|
|
||||||
|
foreach (var foreignKey in entityTypeBuilder.Metadata.GetDeclaredForeignKeys())
|
||||||
|
{
|
||||||
|
var deleteBehavior = GetTargetDeleteBehavior(foreignKey);
|
||||||
|
if (foreignKey.DeleteBehavior != deleteBehavior)
|
||||||
|
{
|
||||||
|
foreignKey.Builder.OnDelete(deleteBehavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue