in src/Core/Resolvers/SqlMutationEngine.cs [1183:1372]
private void ProcessMultipleCreateInputField(
IMiddlewareContext context,
object? unparsedInputFields,
ISqlMetadataProvider sqlMetadataProvider,
MultipleCreateStructure multipleCreateStructure,
int nestingLevel)
{
if (multipleCreateStructure.InputMutParams is null || unparsedInputFields is null)
{
throw new DataApiBuilderException(
message: "The input for a multiple create mutation operation cannot be null.",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
// For One - Many and Many - Many relationship types, processing logic is distinctly executed for each
// object in the input list.
// So, when the input parameters is of list type, we iterate over the list
// and call the same method for each element.
if (multipleCreateStructure.InputMutParams.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
List<IDictionary<string, object?>> parsedInputItems = (List<IDictionary<string, object?>>)multipleCreateStructure.InputMutParams;
List<IValueNode> unparsedInputFieldList = (List<IValueNode>)unparsedInputFields;
int parsedInputItemIndex = 0;
foreach (IDictionary<string, object?> parsedInputItem in parsedInputItems)
{
MultipleCreateStructure multipleCreateStructureForCurrentItem = new(
entityName: multipleCreateStructure.EntityName,
parentEntityName: multipleCreateStructure.ParentEntityName,
inputMutParams: parsedInputItem,
isLinkingTableInsertionRequired: multipleCreateStructure.IsLinkingTableInsertionRequired)
{
CurrentEntityParams = multipleCreateStructure.CurrentEntityParams,
LinkingTableParams = multipleCreateStructure.LinkingTableParams
};
Dictionary<string, Dictionary<string, object?>> primaryKeysOfCreatedItems = new();
IValueNode? nodeForCurrentInput = unparsedInputFieldList[parsedInputItemIndex];
if (nodeForCurrentInput is null)
{
throw new DataApiBuilderException(
message: "Error when processing the mutation request",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
ProcessMultipleCreateInputField(context, nodeForCurrentInput.Value, sqlMetadataProvider, multipleCreateStructureForCurrentItem, nestingLevel);
parsedInputItemIndex++;
}
}
else
{
if (unparsedInputFields is not List<ObjectFieldNode> parameterNodes)
{
throw new DataApiBuilderException(
message: "Error occurred while processing the mutation request",
statusCode: HttpStatusCode.BadRequest,
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
}
string entityName = multipleCreateStructure.EntityName;
Entity entity = _runtimeConfigProvider.GetConfig().Entities[entityName];
// Classifiy the relationship fields (if present in the input request) into referencing and referenced relationships and
// populate multipleCreateStructure.ReferencingRelationships and multipleCreateStructure.ReferencedRelationships respectively.
DetermineReferencedAndReferencingRelationships(context, multipleCreateStructure, sqlMetadataProvider, entity.Relationships, parameterNodes);
PopulateCurrentAndLinkingEntityParams(multipleCreateStructure, sqlMetadataProvider, entity.Relationships);
SourceDefinition currentEntitySourceDefinition = sqlMetadataProvider.GetSourceDefinition(entityName);
currentEntitySourceDefinition.SourceEntityRelationshipMap.TryGetValue(entityName, out RelationshipMetadata? currentEntityRelationshipMetadata);
// Process referenced relationships
foreach ((string relationshipName, object? relationshipFieldValue) in multipleCreateStructure.ReferencedRelationships)
{
string relatedEntityName = GraphQLUtils.GetRelationshipTargetEntityName(entity, entityName, relationshipName);
MultipleCreateStructure referencedRelationshipMultipleCreateStructure = new(entityName: relatedEntityName, parentEntityName: entityName, inputMutParams: relationshipFieldValue);
IValueNode node = GraphQLUtils.GetFieldNodeForGivenFieldName(parameterNodes, relationshipName);
ProcessMultipleCreateInputField(context, node.Value, sqlMetadataProvider, referencedRelationshipMultipleCreateStructure, nestingLevel + 1);
if (sqlMetadataProvider.TryGetFKDefinition(
sourceEntityName: entityName,
targetEntityName: relatedEntityName,
referencingEntityName: entityName,
referencedEntityName: relatedEntityName,
out ForeignKeyDefinition? foreignKeyDefinition,
isMToNRelationship: false))
{
PopulateReferencingFields(
sqlMetadataProvider: sqlMetadataProvider,
multipleCreateStructure: multipleCreateStructure,
fkDefinition: foreignKeyDefinition,
computedRelationshipFields: referencedRelationshipMultipleCreateStructure.CurrentEntityCreatedValues,
isLinkingTable: false,
entityName: relatedEntityName);
}
}
multipleCreateStructure.CurrentEntityCreatedValues = BuildAndExecuteInsertDbQueries(
sqlMetadataProvider: sqlMetadataProvider,
entityName: entityName,
parentEntityName: entityName,
parameters: multipleCreateStructure.CurrentEntityParams!,
sourceDefinition: currentEntitySourceDefinition,
isLinkingEntity: false,
nestingLevel: nestingLevel);
//Perform an insertion in the linking table if required
if (multipleCreateStructure.IsLinkingTableInsertionRequired)
{
if (multipleCreateStructure.LinkingTableParams is null)
{
multipleCreateStructure.LinkingTableParams = new Dictionary<string, object?>();
}
// Consider the mutation request:
// mutation{
// createbook(item: {
// title: "Book Title",
// publisher_id: 1234,
// authors: [
// {...} ,
// {...}
// ]
// }) {
// ...
// }
// There exists two relationships for a linking table.
// 1. Relationship between the parent entity (Book) and the linking table.
// 2. Relationship between the current entity (Author) and the linking table.
// To construct the insert database query for the linking table, relationship fields from both the
// relationships are required.
// Populate Current entity's relationship fields
List<ForeignKeyDefinition> foreignKeyDefinitions = currentEntityRelationshipMetadata!.TargetEntityToFkDefinitionMap[multipleCreateStructure.ParentEntityName];
ForeignKeyDefinition fkDefinition = foreignKeyDefinitions[0];
PopulateReferencingFields(sqlMetadataProvider, multipleCreateStructure, fkDefinition, multipleCreateStructure.CurrentEntityCreatedValues, isLinkingTable: true);
string linkingEntityName = GraphQLUtils.GenerateLinkingEntityName(multipleCreateStructure.ParentEntityName, entityName);
SourceDefinition linkingTableSourceDefinition = sqlMetadataProvider.GetSourceDefinition(linkingEntityName);
_ = BuildAndExecuteInsertDbQueries(
sqlMetadataProvider: sqlMetadataProvider,
entityName: linkingEntityName,
parentEntityName: entityName,
parameters: multipleCreateStructure.LinkingTableParams!,
sourceDefinition: linkingTableSourceDefinition,
isLinkingEntity: true,
nestingLevel: nestingLevel);
}
// Process referencing relationships
foreach ((string relationshipFieldName, object? relationshipFieldValue) in multipleCreateStructure.ReferencingRelationships)
{
string relatedEntityName = GraphQLUtils.GetRelationshipTargetEntityName(entity, entityName, relationshipFieldName);
MultipleCreateStructure referencingRelationshipMultipleCreateStructure = new(entityName: relatedEntityName,
parentEntityName: entityName,
inputMutParams: relationshipFieldValue,
isLinkingTableInsertionRequired: GraphQLUtils.IsMToNRelationship(entity, relationshipFieldName));
IValueNode node = GraphQLUtils.GetFieldNodeForGivenFieldName(parameterNodes, relationshipFieldName);
// Many-Many relationships are marked as Referencing relationships
// because the linking table insertion can happen only
// when records have been successfully created in both the entities involved in the relationship.
// The entities involved do not derive any fields from each other. Only the linking table derives the
// primary key fields from the entities involved in the relationship.
// For a M:N relationships, the referencing fields are populated in LinkingTableParams whereas for
// a 1:N relationship, referencing fields will be populated in CurrentEntityParams.
if (sqlMetadataProvider.TryGetFKDefinition(
sourceEntityName: entityName,
targetEntityName: relatedEntityName,
referencingEntityName: relatedEntityName,
referencedEntityName: entityName,
out ForeignKeyDefinition? referencingEntityFKDefinition,
isMToNRelationship: referencingRelationshipMultipleCreateStructure.IsLinkingTableInsertionRequired))
{
PopulateReferencingFields(
sqlMetadataProvider: sqlMetadataProvider,
multipleCreateStructure: referencingRelationshipMultipleCreateStructure,
fkDefinition: referencingEntityFKDefinition,
computedRelationshipFields: multipleCreateStructure.CurrentEntityCreatedValues,
isLinkingTable: referencingRelationshipMultipleCreateStructure.IsLinkingTableInsertionRequired,
entityName: entityName);
}
ProcessMultipleCreateInputField(context, node.Value, sqlMetadataProvider, referencingRelationshipMultipleCreateStructure, nestingLevel + 1);
}
}
}