in src/Core/Resolvers/MultipleCreateOrderHelper.cs [197:326]
private static string DetermineReferencingEntityBasedOnRequestBody(
string relationshipName,
string sourceEntityName,
string targetEntityName,
DatabaseObject sourceDbObject,
DatabaseObject targetDbObject,
Dictionary<string, IValueNode?> columnDataInSourceBody,
Dictionary<string, IValueNode?> columnDataInTargetBody,
int nestingLevel)
{
RelationshipFields relationshipFields = GetRelationshipFieldsInSourceAndTarget(
sourceEntityName: sourceEntityName,
targetEntityName: targetEntityName,
sourceDbObject: sourceDbObject);
List<string> sourceFields = relationshipFields.SourceFields;
List<string> targetFields = relationshipFields.TargetFields;
// Collect column metadata for source/target columns.
Dictionary<string, ColumnDefinition> sourceColumnDefinitions = sourceDbObject.SourceDefinition.Columns;
Dictionary<string, ColumnDefinition> targetColumnDefinitions = targetDbObject.SourceDefinition.Columns;
bool doesSourceContainAnyAutogenRelationshipField = false;
bool doesTargetContainAnyAutogenRelationshipField = false;
bool doesSourceBodyContainAnyRelationshipField = false;
bool doesTargetBodyContainAnyRelationshipField = false;
// Set to false when source body can't assume a non-null value for one or more relationship fields.
// For the current relationship column to process, the value can be assumed when:
// 1.The relationship column is autogenerated, or
// 2.The request body provides a value for the relationship column.
bool canSourceAssumeAllRelationshipFieldValues = true;
// Set to false when target body can't assume a non-null value for one or more relationship fields.
bool canTargetAssumeAllRelationshipFieldsValues = true;
// Loop over all the relationship fields in source/target to appropriately set the above variables.
for (int idx = 0; idx < sourceFields.Count; idx++)
{
string relationshipFieldInSource = sourceFields[idx];
string relationshipFieldInTarget = targetFields[idx];
// Determine whether the source/target relationship fields for this pair are autogenerated.
bool isSourceRelationshipFieldAutogenerated = sourceColumnDefinitions[relationshipFieldInSource].IsAutoGenerated;
bool isTargetRelationshipFieldAutogenerated = targetColumnDefinitions[relationshipFieldInTarget].IsAutoGenerated;
// Update whether source/target contains any relationship field which is autogenerated.
doesSourceContainAnyAutogenRelationshipField = doesSourceContainAnyAutogenRelationshipField || isSourceRelationshipFieldAutogenerated;
doesTargetContainAnyAutogenRelationshipField = doesTargetContainAnyAutogenRelationshipField || isTargetRelationshipFieldAutogenerated;
// When both source/target entities contain an autogenerated relationship field,
// DAB can't determine the referencing entity. That's because a referencing entity's
// referencing fields are derived from the insertion of the referenced entity but
// we cannot assign a derived value to an autogenerated field.
if (doesSourceContainAnyAutogenRelationshipField && doesTargetContainAnyAutogenRelationshipField)
{
throw new DataApiBuilderException(
message: $"Cannot execute multiple-create because both the source entity: {sourceEntityName} and the target entity: " +
$"{targetEntityName} contain autogenerated fields for relationship: {relationshipName} at level: {nestingLevel}",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
// Determine whether the input data for source/target contain a value (could be null) for this pair of relationship fields.
bool doesSourceBodyContainThisRelationshipField = columnDataInSourceBody.TryGetValue(relationshipFieldInSource, out IValueNode? sourceColumnvalue);
bool doesTargetBodyContainThisRelationshipField = columnDataInTargetBody.TryGetValue(relationshipFieldInTarget, out IValueNode? targetColumnvalue);
// Update whether input data for source/target contains any relationship field.
doesSourceBodyContainAnyRelationshipField = doesSourceBodyContainAnyRelationshipField || doesSourceBodyContainThisRelationshipField;
doesTargetBodyContainAnyRelationshipField = doesTargetBodyContainAnyRelationshipField || doesTargetBodyContainThisRelationshipField;
// If the source entity contains a relationship field in request body which suggests we perform the insertion first in source entity,
// and there is an autogenerated relationship field in the target entity which suggests we perform the insertion first in target entity,
// we cannot determine a valid order of insertion.
if (doesSourceBodyContainAnyRelationshipField && doesTargetContainAnyAutogenRelationshipField)
{
throw new DataApiBuilderException(
message: $"Input for source entity: {sourceEntityName} for the relationship: {relationshipName} at level: {nestingLevel} cannot contain the field: {relationshipFieldInSource}.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
// If the target entity contains a relationship field in request body which suggests we perform the insertion first in target entity,
// and there is an autogenerated relationship field in the source entity which suggests we perform the insertion first in source entity,
// we cannot determine a valid order of insertion.
if (doesTargetBodyContainAnyRelationshipField && doesSourceContainAnyAutogenRelationshipField)
{
throw new DataApiBuilderException(
message: $"Input for target entity: {targetEntityName} for the relationship: {relationshipName} at level: {nestingLevel} cannot contain the field: {relationshipFieldInSource}.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
// If relationship columns are present in the input for both the source and target entities,
// we cannot choose one entity as the referencing entity. This is because for a referencing entity,
// the values for all the referencing fields should be derived from the insertion in the referenced entity.
// However, here both entities contain atleast one relationship field whose value is provided in the request.
if (doesSourceBodyContainAnyRelationshipField && doesTargetBodyContainAnyRelationshipField)
{
throw new DataApiBuilderException(
message: $"The relationship fields must be specified for either the source entity: {sourceEntityName} or the target entity: {targetEntityName} " +
$"for the relationship: {relationshipName} at level: {nestingLevel}.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
// The source/target entities can assume a value for insertion for a relationship field if:
// 1. The field is autogenerated, or
// 2. The field is given a non-null value by the user - since we don't allow null values for a relationship field.
bool canSourceAssumeThisFieldValue = isSourceRelationshipFieldAutogenerated || sourceColumnvalue is not null;
bool canTargetAssumeThisFieldValue = isTargetRelationshipFieldAutogenerated || targetColumnvalue is not null;
// Update whether all the values(non-null) for relationship fields are available for source/target.
canSourceAssumeAllRelationshipFieldValues = canSourceAssumeAllRelationshipFieldValues && canSourceAssumeThisFieldValue;
canTargetAssumeAllRelationshipFieldsValues = canTargetAssumeAllRelationshipFieldsValues && canTargetAssumeThisFieldValue;
// If the values for all relationship fields cannot be assumed for neither source nor target,
// the multiple create request cannot be executed.
if (!canSourceAssumeAllRelationshipFieldValues && !canTargetAssumeAllRelationshipFieldsValues)
{
throw new DataApiBuilderException(
message: $"Neither source entity: {sourceEntityName} nor the target entity: {targetEntityName} for the relationship: {relationshipName} at level: {nestingLevel} " +
$"provide sufficient data to perform a multiple-create (related insertion) on the entities.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
}
return canSourceAssumeAllRelationshipFieldValues ? targetEntityName : sourceEntityName;
}