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);
}
}
}