@@ -650,256 +650,168 @@ private void AddForeignKeyEdges()
650650 {
651651 if ( command . EntityState is EntityState . Modified or EntityState . Added )
652652 {
653- if ( command . Table != null )
654- {
655- foreach ( var foreignKey in command . Table . ReferencingForeignKeyConstraints )
656- {
657- if ( ! IsModified ( foreignKey . PrincipalUniqueConstraint . Columns , command ) )
658- {
659- continue ;
660- }
661-
662- var principalKeyValue = ( ( ForeignKeyConstraint ) foreignKey ) . GetRowForeignKeyValueFactory ( )
663- . CreatePrincipalEquatableKeyValue ( command ) ;
664- Check . DebugAssert ( principalKeyValue != null , "null principalKeyValue" ) ;
665-
666- if ( ! predecessorsMap . TryGetValue ( principalKeyValue , out var predecessorCommands ) )
667- {
668- predecessorCommands = [ ] ;
669- predecessorsMap . Add ( principalKeyValue , predecessorCommands ) ;
670- }
671-
672- predecessorCommands . Add ( command ) ;
673- }
674- }
675-
676- for ( var i = 0 ; i < command . Entries . Count ; i ++ )
677- {
678- var entry = command . Entries [ i ] ;
679- foreach ( var foreignKey in entry . EntityType . GetReferencingForeignKeys ( ) )
680- {
681- if ( ! CanCreateDependency ( foreignKey , command , principal : true )
682- || ! IsModified ( foreignKey . PrincipalKey . Properties , entry )
683- || ( command . Table != null
684- && ! IsStoreGenerated ( entry , foreignKey . PrincipalKey )
685- && foreignKey . GetMappedConstraints ( ) . Any ( ) ) )
686- {
687- continue ;
688- }
689-
690- var principalKeyValue = foreignKey . GetDependentKeyValueFactory ( )
691- . CreatePrincipalEquatableKey ( entry ) ;
692- Check . DebugAssert ( principalKeyValue != null , "null principalKeyValue" ) ;
693-
694- if ( ! predecessorsMap . TryGetValue ( principalKeyValue , out var predecessorCommands ) )
695- {
696- predecessorCommands = [ ] ;
697- predecessorsMap . Add ( principalKeyValue , predecessorCommands ) ;
698- }
699-
700- predecessorCommands . Add ( command ) ;
701- }
702- }
653+ AddForeignKeyPredecessors ( command , predecessorsMap , principal : true ) ;
703654 }
704655
705656 if ( command . EntityState is EntityState . Modified or EntityState . Deleted )
706657 {
707- if ( command . Table != null )
708- {
709- foreach ( var foreignKey in command . Table ! . ForeignKeyConstraints )
710- {
711- if ( ! IsModified ( foreignKey . Columns , command ) )
712- {
713- continue ;
714- }
715-
716- var dependentKeyValue = ( ( ForeignKeyConstraint ) foreignKey ) . GetRowForeignKeyValueFactory ( )
717- . CreateDependentEquatableKeyValue ( command , fromOriginalValues : true ) ;
718- if ( dependentKeyValue != null )
719- {
720- if ( ! originalPredecessorsMap . TryGetValue ( dependentKeyValue , out var predecessorCommands ) )
721- {
722- predecessorCommands = [ ] ;
723- originalPredecessorsMap . Add ( dependentKeyValue , predecessorCommands ) ;
724- }
725-
726- predecessorCommands . Add ( command ) ;
727- }
728- }
658+ AddForeignKeyPredecessors ( command , originalPredecessorsMap , principal : false ) ;
659+ }
660+ }
729661
730- // Also handle FKs with no mapped constraints (e.g. TPC with abstract principal mapped to no table)
731- foreach ( var entry in command . Entries )
732- {
733- foreach ( var foreignKey in entry . EntityType . GetForeignKeys ( ) )
734- {
735- if ( ! CanCreateDependency ( foreignKey , command , principal : false )
736- || ! IsModified ( foreignKey . Properties , entry )
737- || foreignKey . GetMappedConstraints ( ) . Any ( ) )
738- {
739- continue ;
740- }
662+ foreach ( var command in _modificationCommandGraph . Vertices )
663+ {
664+ if ( command . EntityState is EntityState . Modified or EntityState . Added )
665+ {
666+ AddForeignKeyEdges ( command , predecessorsMap , principal : false ) ;
667+ }
741668
742- var dependentKeyValue = foreignKey . GetDependentKeyValueFactory ( )
743- ? . CreateDependentEquatableKey ( entry , fromOriginalValues : true ) ;
669+ if ( command . EntityState is EntityState . Modified or EntityState . Deleted )
670+ {
671+ AddForeignKeyEdges ( command , originalPredecessorsMap , principal : true ) ;
672+ }
673+ }
674+ }
744675
745- if ( dependentKeyValue != null )
746- {
747- if ( ! originalPredecessorsMap . TryGetValue ( dependentKeyValue , out var predecessorCommands ) )
748- {
749- predecessorCommands = [ ] ;
750- originalPredecessorsMap . Add ( dependentKeyValue , predecessorCommands ) ;
751- }
676+ /// <summary>
677+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
678+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
679+ /// any release. You should only use it directly in your code with extreme caution and knowing that
680+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
681+ /// </summary>
682+ private static void AddForeignKeyPredecessors (
683+ IReadOnlyModificationCommand command ,
684+ Dictionary < object , List < IReadOnlyModificationCommand > > predecessorsMap ,
685+ bool principal )
686+ {
687+ // This call doesn't need the IsStoreGenerated check because the constraint-level factory
688+ // (IRowForeignKeyValueFactory) works from column modifications and always produces a usable key value,
689+ // even for store-generated keys. The model-level factory below works from IUpdateEntry, which may hold
690+ // temporary sentinel values for store-generated keys, so it needs to register those separately to ensure
691+ // the dependent side can find the principal via either factory.
692+ foreach ( var ( _, keyValue ) in GetForeignKeyConstraintValues ( command , principal , fromOriginalValues : ! principal ) )
693+ {
694+ AddPredecessor ( predecessorsMap , keyValue , command ) ;
695+ }
752696
753- predecessorCommands . Add ( command ) ;
754- }
755- }
756- }
757- }
758- else
759- {
760- foreach ( var entry in command . Entries )
761- {
762- foreach ( var foreignKey in entry . EntityType . GetForeignKeys ( ) )
763- {
764- if ( ! CanCreateDependency ( foreignKey , command , principal : false )
765- || ! IsModified ( foreignKey . Properties , entry ) )
766- {
767- continue ;
768- }
697+ // Handle the cases where there is no relational model and where there is no mapped foreign key constraint (TPC)
698+ foreach ( var ( _, keyValue ) in GetForeignKeyValues (
699+ command , principal , fromOriginalValues : ! principal , checkStoreGenerated : principal ) )
700+ {
701+ AddPredecessor ( predecessorsMap , keyValue , command ) ;
702+ }
703+ }
769704
770- var dependentKeyValue = foreignKey . GetDependentKeyValueFactory ( )
771- ? . CreateDependentEquatableKey ( entry , fromOriginalValues : true ) ;
705+ /// <summary>
706+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
707+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
708+ /// any release. You should only use it directly in your code with extreme caution and knowing that
709+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
710+ /// </summary>
711+ private void AddForeignKeyEdges (
712+ IReadOnlyModificationCommand command ,
713+ Dictionary < object , List < IReadOnlyModificationCommand > > predecessorsMap ,
714+ bool principal )
715+ {
716+ foreach ( var ( constraint , keyValue ) in GetForeignKeyConstraintValues ( command , principal , fromOriginalValues : principal ) )
717+ {
718+ AddMatchingPredecessorEdge ( predecessorsMap , keyValue , command , constraint , checkStoreGenerated : ! principal ) ;
719+ }
772720
773- if ( dependentKeyValue != null )
774- {
775- if ( ! originalPredecessorsMap . TryGetValue ( dependentKeyValue , out var predecessorCommands ) )
776- {
777- predecessorCommands = [ ] ;
778- originalPredecessorsMap . Add ( dependentKeyValue , predecessorCommands ) ;
779- }
721+ // Handle the cases where there is no relational model and where there is no mapped foreign key constraint (TPC )
722+ foreach ( var ( foreignKey , keyValue ) in GetForeignKeyValues (
723+ command , principal , fromOriginalValues : principal , checkStoreGenerated : false ) )
724+ {
725+ AddMatchingPredecessorEdge ( predecessorsMap , keyValue , command , foreignKey , checkStoreGenerated : ! principal ) ;
726+ }
727+ }
780728
781- predecessorCommands . Add ( command ) ;
782- }
783- }
784- }
785- }
786- }
729+ private static IEnumerable < ( IForeignKeyConstraint Constraint , object KeyValue ) > GetForeignKeyConstraintValues (
730+ IReadOnlyModificationCommand command ,
731+ bool principal ,
732+ bool fromOriginalValues )
733+ {
734+ if ( command . Table == null )
735+ {
736+ yield break ;
787737 }
788738
789- foreach ( var command in _modificationCommandGraph . Vertices )
739+ var constraints = principal ? command . Table . ReferencingForeignKeyConstraints : command . Table . ForeignKeyConstraints ;
740+ foreach ( var constraint in constraints )
790741 {
791- if ( command . EntityState is EntityState . Modified or EntityState . Added )
742+ var columns = principal ? constraint . PrincipalUniqueConstraint . Columns : constraint . Columns ;
743+ if ( ! IsModified ( columns , command ) )
792744 {
793- if ( command . Table != null )
794- {
795- foreach ( var foreignKey in command . Table . ForeignKeyConstraints )
796- {
797- if ( ! IsModified ( foreignKey . Columns , command ) )
798- {
799- continue ;
800- }
801-
802- var dependentKeyValue = ( ( ForeignKeyConstraint ) foreignKey ) . GetRowForeignKeyValueFactory ( )
803- . CreateDependentEquatableKeyValue ( command ) ;
804- if ( dependentKeyValue is null )
805- {
806- continue ;
807- }
808-
809- AddMatchingPredecessorEdge (
810- predecessorsMap , dependentKeyValue , command , foreignKey , checkStoreGenerated : true ) ;
811- }
812- }
813-
814- // ReSharper disable once ForCanBeConvertedToForeach
815- for ( var entryIndex = 0 ; entryIndex < command . Entries . Count ; entryIndex ++ )
816- {
817- var entry = command . Entries [ entryIndex ] ;
818- foreach ( var foreignKey in entry . EntityType . GetForeignKeys ( ) )
819- {
820- if ( ! CanCreateDependency ( foreignKey , command , principal : false )
821- || ! IsModified ( foreignKey . Properties , entry ) )
822- {
823- continue ;
824- }
745+ continue ;
746+ }
825747
826- var dependentKeyValue = foreignKey . GetDependentKeyValueFactory ( )
827- ? . CreateDependentEquatableKey ( entry ) ;
828- if ( dependentKeyValue == null )
829- {
830- continue ;
831- }
748+ var foreignKeyValueFactory = ( ( ForeignKeyConstraint ) constraint ) . GetRowForeignKeyValueFactory ( ) ;
749+ var keyValue = principal
750+ ? foreignKeyValueFactory . CreatePrincipalEquatableKeyValue ( command , fromOriginalValues )
751+ : foreignKeyValueFactory . CreateDependentEquatableKeyValue ( command , fromOriginalValues ) ;
832752
833- AddMatchingPredecessorEdge (
834- predecessorsMap , dependentKeyValue , command , foreignKey , checkStoreGenerated : true ) ;
835- }
836- }
753+ if ( keyValue is null )
754+ {
755+ Check . DebugAssert ( ! principal , "null principal keyValue" ) ;
756+ continue ;
837757 }
838758
839- if ( command . EntityState is EntityState . Modified or EntityState . Deleted )
759+ yield return ( constraint , keyValue ) ;
760+ }
761+ }
762+
763+ private static IEnumerable < ( IForeignKey ForeignKey , object KeyValue ) > GetForeignKeyValues (
764+ IReadOnlyModificationCommand command ,
765+ bool principal ,
766+ bool fromOriginalValues ,
767+ bool checkStoreGenerated )
768+ {
769+ // ReSharper disable once ForCanBeConvertedToForeach
770+ for ( var i = 0 ; i < command . Entries . Count ; i ++ )
771+ {
772+ var entry = command . Entries [ i ] ;
773+ var foreignKeys = principal
774+ ? entry . EntityType . GetReferencingForeignKeys ( )
775+ : entry . EntityType . GetForeignKeys ( ) ;
776+ foreach ( var foreignKey in foreignKeys )
840777 {
841- if ( command . Table != null )
778+ var properties = principal ? foreignKey . PrincipalKey . Properties : foreignKey . Properties ;
779+ if ( ! CanCreateDependency ( foreignKey , command , principal )
780+ || ! IsModified ( properties , entry )
781+ || ( command . Table != null
782+ && ( ! checkStoreGenerated || ! IsStoreGenerated ( entry , foreignKey . PrincipalKey ) )
783+ && foreignKey . GetMappedConstraints ( ) . Any ( ) ) )
842784 {
843- foreach ( var foreignKey in command . Table . ReferencingForeignKeyConstraints )
844- {
845- if ( ! IsModified ( foreignKey . PrincipalUniqueConstraint . Columns , command ) )
846- {
847- continue ;
848- }
785+ continue ;
786+ }
849787
850- var principalKeyValue = ( ( ForeignKeyConstraint ) foreignKey ) . GetRowForeignKeyValueFactory ( )
851- . CreatePrincipalEquatableKeyValue ( command , fromOriginalValues : true ) ;
852- Check . DebugAssert ( principalKeyValue != null , "null principalKeyValue" ) ;
853- AddMatchingPredecessorEdge (
854- originalPredecessorsMap , principalKeyValue , command , foreignKey ) ;
855- }
788+ var foreignKeyValueFactory = foreignKey . GetDependentKeyValueFactory ( ) ;
789+ var keyValue = principal
790+ ? foreignKeyValueFactory . CreatePrincipalEquatableKey ( entry , fromOriginalValues )
791+ : foreignKeyValueFactory . CreateDependentEquatableKey ( entry , fromOriginalValues ) ;
856792
857- // Also handle FKs with no mapped constraints (e.g. TPC with abstract principal mapped to no table)
858- // ReSharper disable once ForCanBeConvertedToForeach
859- for ( var entryIndex = 0 ; entryIndex < command . Entries . Count ; entryIndex ++ )
860- {
861- var entry = command . Entries [ entryIndex ] ;
862- foreach ( var foreignKey in entry . EntityType . GetReferencingForeignKeys ( ) )
863- {
864- if ( ! CanCreateDependency ( foreignKey , command , principal : true )
865- || foreignKey . GetMappedConstraints ( ) . Any ( ) )
866- {
867- continue ;
868- }
869-
870- var principalKeyValue = foreignKey . GetDependentKeyValueFactory ( )
871- . CreatePrincipalEquatableKey ( entry , fromOriginalValues : true ) ;
872- Check . DebugAssert ( principalKeyValue != null , "null principalKeyValue" ) ;
873- AddMatchingPredecessorEdge (
874- originalPredecessorsMap , principalKeyValue , command , foreignKey ) ;
875- }
876- }
877- }
878- else
793+ if ( keyValue != null )
879794 {
880- // ReSharper disable once ForCanBeConvertedToForeach
881- for ( var entryIndex = 0 ; entryIndex < command . Entries . Count ; entryIndex ++ )
882- {
883- var entry = command . Entries [ entryIndex ] ;
884- foreach ( var foreignKey in entry . EntityType . GetReferencingForeignKeys ( ) )
885- {
886- if ( ! CanCreateDependency ( foreignKey , command , principal : true ) )
887- {
888- continue ;
889- }
890-
891- var principalKeyValue = foreignKey . GetDependentKeyValueFactory ( )
892- . CreatePrincipalEquatableKey ( entry , fromOriginalValues : true ) ;
893- Check . DebugAssert ( principalKeyValue != null , "null principalKeyValue" ) ;
894- AddMatchingPredecessorEdge (
895- originalPredecessorsMap , principalKeyValue , command , foreignKey ) ;
896- }
897- }
795+ yield return ( foreignKey , keyValue ) ;
898796 }
899797 }
900798 }
901799 }
902800
801+ private static void AddPredecessor (
802+ Dictionary < object , List < IReadOnlyModificationCommand > > predecessorsMap ,
803+ object keyValue ,
804+ IReadOnlyModificationCommand command )
805+ {
806+ if ( ! predecessorsMap . TryGetValue ( keyValue , out var predecessorCommands ) )
807+ {
808+ predecessorCommands = [ ] ;
809+ predecessorsMap . Add ( keyValue , predecessorCommands ) ;
810+ }
811+
812+ predecessorCommands . Add ( command ) ;
813+ }
814+
903815 private static bool IsStoreGenerated ( IUpdateEntry entry , IKey key )
904816 {
905817 var keyProperties = key . Properties ;
@@ -1149,7 +1061,7 @@ private void AddMatchingPredecessorEdge<T>(
11491061 T keyValue ,
11501062 IReadOnlyModificationCommand command ,
11511063 IForeignKeyConstraint foreignKey ,
1152- bool checkStoreGenerated = false )
1064+ bool checkStoreGenerated )
11531065 where T : notnull
11541066 {
11551067 if ( predecessorsMap . TryGetValue ( keyValue , out var predecessorCommands ) )
0 commit comments