private void ProcessRelationships()

in src/Core/Services/MetadataProviders/SqlMetadataProvider.cs [752:912]


        private void ProcessRelationships(
            string entityName,
            Entity entity,
            DatabaseTable databaseTable,
            Dictionary<string, DatabaseObject> sourceObjects)
        {
            SourceDefinition sourceDefinition = GetSourceDefinition(entityName);
            if (!sourceDefinition.SourceEntityRelationshipMap
                .TryGetValue(entityName, out RelationshipMetadata? relationshipData))
            {
                relationshipData = new();
                sourceDefinition.SourceEntityRelationshipMap.Add(entityName, relationshipData);
            }

            string targetSchemaName, targetDbTableName, linkingTableSchema, linkingTableName;
            foreach ((string relationshipName, EntityRelationship relationship) in entity.Relationships!)
            {
                string targetEntityName = relationship.TargetEntity;
                if (!_entities.TryGetValue(targetEntityName, out Entity? targetEntity))
                {
                    throw new InvalidOperationException($"Target Entity {targetEntityName} should be one of the exposed entities.");
                }

                if (targetEntity.Source.Object is null)
                {
                    throw new DataApiBuilderException(
                                message: $"Target entity {entityName} does not have a valid source object.",
                                statusCode: HttpStatusCode.InternalServerError,
                                subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError);
                }

                (targetSchemaName, targetDbTableName) = ParseSchemaAndDbTableName(targetEntity.Source.Object)!;
                DatabaseTable targetDbTable = new(targetSchemaName, targetDbTableName);
                // If a linking object is specified,
                // give that higher preference and add two foreign keys for this targetEntity.
                if (relationship.LinkingObject is not null)
                {
                    (linkingTableSchema, linkingTableName) = ParseSchemaAndDbTableName(relationship.LinkingObject)!;
                    DatabaseTable linkingDbTable = new(linkingTableSchema, linkingTableName);
                    AddForeignKeyForTargetEntity(
                        sourceEntityName: entityName,
                        relationshipName: relationshipName,
                        targetEntityName: targetEntityName,
                        referencingDbTable: linkingDbTable,
                        referencedDbTable: databaseTable,
                        referencingColumns: relationship.LinkingSourceFields,
                        referencedColumns: relationship.SourceFields,
                        referencingEntityRole: RelationshipRole.Linking,
                        referencedEntityRole: RelationshipRole.Source,
                        relationshipData: relationshipData);

                    AddForeignKeyForTargetEntity(
                        sourceEntityName: entityName,
                        relationshipName: relationshipName,
                        targetEntityName: targetEntityName,
                        referencingDbTable: linkingDbTable,
                        referencedDbTable: targetDbTable,
                        referencingColumns: relationship.LinkingTargetFields,
                        referencedColumns: relationship.TargetFields,
                        referencingEntityRole: RelationshipRole.Linking,
                        referencedEntityRole: RelationshipRole.Target,
                        relationshipData: relationshipData);

                    RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();

                    // Populating metadata for linking object is only required when multiple create operation is enabled and those database types that support multiple create operation.
                    if (runtimeConfig.IsMultipleCreateOperationEnabled())
                    {
                        // When a linking object is encountered for a database table, we will create a linking entity for the object.
                        // Subsequently, we will also populate the Database object for the linking entity. This is used to infer
                        // metadata about linking object needed to create GQL schema for multiple insertions.
                        if (entity.Source.Type is EntitySourceType.Table)
                        {
                            PopulateMetadataForLinkingObject(
                                entityName: entityName,
                                targetEntityName: targetEntityName,
                                linkingObject: relationship.LinkingObject,
                                sourceObjects: sourceObjects);
                        }
                    }
                }
                else if (relationship.Cardinality == Cardinality.One)
                {
                    // Example: books(Many) - publisher(One)
                    // where books.publisher_id is referencing publisher.id
                    // For Many-One OR One-One Relationships, DAB optimistically
                    // creates two ForeignKeyDefinitions to represent the relationship:
                    //
                    // #1
                    // Referencing Entity | Referenced Entity
                    // -------------------|-------------------
                    // Source Entity      | Target Entity
                    //
                    // #2
                    // Referencing Entity | Referenced Entity
                    // -------------------|-------------------
                    // Target Entity      | Source Entity
                    //
                    // One of the created ForeignKeyDefinitions correctly matches foreign key
                    // metadata in the database and DAB will later identify the correct
                    // ForeignKeyDefinition object when processing database schema metadata.
                    //
                    // When the runtime config doesn't specify how to relate these entities
                    // (via source/target fields), DAB expects to identity that one of
                    // the ForeignKeyDefinition objects will match foreign key metadata in the database.
                    // Create ForeignKeyDefinition #1
                    AddForeignKeyForTargetEntity(
                        sourceEntityName: entityName,
                        relationshipName: relationshipName,
                        targetEntityName,
                        referencingDbTable: databaseTable,
                        referencedDbTable: targetDbTable,
                        referencingColumns: relationship.SourceFields,
                        referencedColumns: relationship.TargetFields,
                        referencingEntityRole: RelationshipRole.Source,
                        referencedEntityRole: RelationshipRole.Target,
                        relationshipData);

                    // Create ForeignKeyDefinition #2
                    // when target and source entities differ (NOT self-referencing)
                    // because one ForeignKeyDefintion is sufficient to represent a self-joining relationship.
                    if (targetEntityName != entityName)
                    {
                        AddForeignKeyForTargetEntity(
                            sourceEntityName: entityName,
                            relationshipName: relationshipName,
                            targetEntityName,
                            referencingDbTable: targetDbTable,
                            referencedDbTable: databaseTable,
                            referencingColumns: relationship.TargetFields,
                            referencedColumns: relationship.SourceFields,
                            referencingEntityRole: RelationshipRole.Target,
                            referencedEntityRole: RelationshipRole.Source,
                            relationshipData);
                    }
                }
                else if (relationship.Cardinality is Cardinality.Many)
                {
                    // Example: publisher(One)-books(Many)
                    // where publisher.id is referenced by books.publisher_id
                    // For Many-Many relationships, DAB creates one
                    // ForeignKeyDefinition to represent the relationship:
                    //
                    // #1
                    // Referencing Entity | Referenced Entity
                    // -------------------|-------------------
                    // Target Entity      | Source Entity
                    AddForeignKeyForTargetEntity(
                        sourceEntityName: entityName,
                        relationshipName: relationshipName,
                        targetEntityName,
                        referencingDbTable: targetDbTable,
                        referencedDbTable: databaseTable,
                        referencingColumns: relationship.TargetFields,
                        referencedColumns: relationship.SourceFields,
                        referencingEntityRole: RelationshipRole.Target,
                        referencedEntityRole: RelationshipRole.Source,
                        relationshipData);
                }
            }
        }