diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs index d40b7ed107..c84d90c1a5 100644 --- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs @@ -1807,14 +1807,19 @@ private void FillInferredFkInfo( // that this source is related to. foreach ((string targetEntityName, List fKDefinitionsToTarget) in relationshipData.TargetEntityToFkDefinitionMap) { - // // Scenario 1: When a FK constraint is defined between source and target entities in the database. // In this case, there will be exactly one ForeignKeyDefinition with the right pair of Referencing and Referenced tables. // Scenario 2: When no FK constraint is defined between source and target entities, but the relationship fields are configured through config file // In this case, two entries will be created. // First entry: Referencing table: Source entity, Referenced table: Target entity - // Second entry: Referencing table: Target entity, Referenced table: Source entity - List validatedFKDefinitionsToTarget = GetValidatedFKs(fKDefinitionsToTarget); + // Second entry: Referencing table: Target entity, Referenced table: Source entity + if (!TryGetValidatedFKs(fKDefinitionsToTarget, out List validatedFKDefinitionsToTarget)) + { + _logger.LogWarning("Cannot support multiple-create due to mismatch in the metadata inferred from the database and the " + + "metadata inferred from the config for a relationship defined between the source entity: {sourceEntityName} and " + + "target entity: {targetEntityName}.", sourceEntityName, targetEntityName); + } + relationshipData.TargetEntityToFkDefinitionMap[targetEntityName] = validatedFKDefinitionsToTarget; } } @@ -1830,11 +1835,14 @@ private void FillInferredFkInfo( /// the pair of (source, target) entities. /// /// List of FK definitions defined from source to target. - /// List of validated FK definitions from source to target. - private List GetValidatedFKs( - List fKDefinitionsToTarget) + /// Stores the validate FK definitions to be returned to the caller. + /// true if valid foreign key definitions could be determined successfully, else return false. + private bool TryGetValidatedFKs( + List fKDefinitionsToTarget, out List validatedFKDefinitionsToTarget) { - List validatedFKDefinitionsToTarget = new(); + // Returns true only for MsSql for now when multiple-create is enabled. + bool isMultipleCreateEnabled = _runtimeConfigProvider.GetConfig().IsMultipleCreateOperationEnabled(); + validatedFKDefinitionsToTarget = new(); foreach (ForeignKeyDefinition fKDefinitionToTarget in fKDefinitionsToTarget) { // This code block adds FK definitions between source and target entities when there is an FK constraint defined @@ -1842,7 +1850,7 @@ private List GetValidatedFKs( // Add the referencing and referenced columns for this foreign key definition for the target. if (PairToFkDefinition is not null && - PairToFkDefinition.TryGetValue(fKDefinitionToTarget.Pair, out ForeignKeyDefinition? inferredFKDefinition)) + PairToFkDefinition.TryGetValue(fKDefinitionToTarget.Pair, out ForeignKeyDefinition? dbFKDefinition)) { // Being here indicates that we inferred an FK constraint for the current foreign key definition. // The count of referencing and referenced columns being > 0 indicates that source.fields and target.fields @@ -1850,13 +1858,31 @@ private List GetValidatedFKs( // In this scenario, higher precedence is given to the fields configured through the config file. So, the existing FK definition is retained as is. if (fKDefinitionToTarget.ReferencingColumns.Count > 0 && fKDefinitionToTarget.ReferencedColumns.Count > 0) { - validatedFKDefinitionsToTarget.Add(fKDefinitionToTarget); + if (isMultipleCreateEnabled) + { + if (!AreFKDefinitionsEqual(dbFKDefinition: dbFKDefinition, configFKDefinition: fKDefinitionToTarget)) + { + return false; + } + else + { + // If multiple-create is enabled, preferenced is given to relationship metadata (source.fields/target.fields) + // inferred from the database. + validatedFKDefinitionsToTarget.Add(dbFKDefinition); + } + } + else + { + // If multiple-create is not enabled, preferenced is given to relationship metadata (source.fields/target.fields) + // inferred from the config. + validatedFKDefinitionsToTarget.Add(fKDefinitionToTarget); + } } // The count of referenced and referencing columns being = 0 indicates that source.fields and target.fields // are not configured through the config file. In this case, the FK fields inferred from the database are populated. else { - validatedFKDefinitionsToTarget.Add(inferredFKDefinition); + validatedFKDefinitionsToTarget.Add(dbFKDefinition); } } else @@ -1896,7 +1922,45 @@ private List GetValidatedFKs( } } - return validatedFKDefinitionsToTarget; + return true; + } + + /// + /// Helper method to compare two foreign key definitions inferred from the database and the config for equality on the basis of the + /// referencing -> referenced column mappings present in them. + /// The equality ensures that both the foreign key definitions have: + /// 1. Same set of referencing and referenced tables, + /// 2. Same number of referencing/referenced columns, + /// 3. Same mappings from referencing -> referenced column. + /// + /// Foreign key definition inferred from the database. + /// Foreign key definition generated based on relationship metadata provided in the config. + /// true if all the above mentioned conditions are met, else false. + private static bool AreFKDefinitionsEqual(ForeignKeyDefinition dbFKDefinition, ForeignKeyDefinition configFKDefinition) + { + if (!dbFKDefinition.Pair.Equals(configFKDefinition.Pair) || dbFKDefinition.ReferencingColumns.Count != configFKDefinition.ReferencingColumns.Count) + { + return false; + } + + Dictionary referencingToReferencedColumnsInDb = dbFKDefinition.ReferencingColumns.Zip( + dbFKDefinition.ReferencedColumns, (key, value) => new { Key = key, Value = value }).ToDictionary(item => item.Key, item => item.Value); + + // Traverse through each (referencing, referenced) columns pair in the foreign key definition sourced from the config. + for (int idx = 0; idx < configFKDefinition.ReferencingColumns.Count; idx++) + { + string referencingColumnNameInDb = configFKDefinition.ReferencingColumns[idx]; + if (!referencingToReferencedColumnsInDb.TryGetValue(referencingColumnNameInDb, out string? referencedColumnNameInDb) + || !referencedColumnNameInDb.Equals(configFKDefinition.ReferencedColumns[idx])) + { + // This indicates that either there is no mapping defined for referencingColumnName in the second foreign key definition + // or the referencing -> referenced column mapping in the second foreign key definition do not match the mapping in the first foreign key definition. + // In both the cases, it is implied that the two foreign key definitions do not match. + return false; + } + } + + return true; } ///